Lesson 1 of 14
Unit Testing And TDD 101
Roy Explains TDD (external link, opens in new tab) and unit testing (external link, opens in new tab) from scratrch, including how to setup the environment for flowing development.
In this lesson
Roy Osherove defines a unit test (external link, opens in new tab) and the three properties of a good one (external link, opens in new tab), readability (external link, opens in new tab), maintainability (external link, opens in new tab) and trust (external link, opens in new tab), and explains why unit testing efforts fail when any of them is missing. Writing NUnit (external link, opens in new tab) tests in Visual Studio for a calculator class, he covers three-part test names (external link, opens in new tab), readable inputs, factory methods (external link, opens in new tab) instead of setup methods (external link, opens in new tab), and Assert.Throws (external link, opens in new tab) instead of the ExpectedException attribute. He also separates unit from integration tests (external link, opens in new tab), runs tests with TestDriven.NET, and adds features test first: a failing test, the simplest passing code (external link, opens in new tab), then refactoring.
Transcript
[00:26] (external link, opens in new tab) To teach you. We have Roy Osherove who ordered the book the art of unit testing (external link, opens in new tab) which is really great. We have a couple of copies of the company if anyone want to read it but over to you. >> You just bought two copies for the whole company. Okay. >> Yeah. I I specifically asked for people to bring some laptops so that we might have later after after the early breakfast
[00:58] (external link, opens in new tab) Some some workshops. I'd like to start first of all by moving this screen a little bit. How many people here have tried to write a unit test (external link, opens in new tab)? Okay, how many people here have succeeded in working with unit tests?
[01:31] (external link, opens in new tab) About half of the people who tried. How many people here have just heard of it? Okay. How many people here work with JavaScript or UI related stuff? >> Okay. And the rest what do you work on? >> Just .NET stuff. C#. >> Yeah. >> Yeah. Java >> desktop or web. >> Web. Okay.
[02:03] (external link, opens in new tab) So this is about test-driven development (external link, opens in new tab). But the day kind of looks like this or the the first half of the day. We're going to talk about unit testing as a separate activity from testdriven development cuz they are separate skills. Test-driven development is basically when you write those tests. So if you don't know how to write good tests, you can do test-driven development with really crappy tests as well, which can actually hurt your project. So we're going to learn how what it means to write a good unit test (external link, opens in new tab), what a unit test actually means,
[02:38] (external link, opens in new tab) And what are the properties of a good unit test. Readability (external link, opens in new tab), maintainability (external link, opens in new tab), and trust (external link, opens in new tab). So you actually trust the result of a test. But the nice thing about test-driven development as an activity is that even though it started as part of something called extreme programming, you can take this activity as it is and implement it wherever you want regardless of the methodology that your organization uses because TDD kind of like debugging. It's an is a personal activity.
[03:10] (external link, opens in new tab) It's something that you can choose to do wherever you are. So it's always a choice. It's also a choice not to do it. Even if someone forces you to do it. So I've seen organizations where people have actually failed the company when people forced them to do TDD and they didn't want to. So I don't know if you know this but don't want to do something they easily don't do it even if someone tells them to do it. So >> they can fail their manager by by doing exactly what they were told.
[03:42] (external link, opens in new tab) Right. Not a second more. Oh, it's 5:00. I have to go home now. I know it's not working the build, etc. All that stuff. So, it's always a choice. What we're going to do, we're going to talk about the choices that we make when we when people do test room development and why a lot of efforts when for people who do unit testing in TDD fail. But to understand that, let's try to define what a unit test is. This is the most basic definition.
[04:12] (external link, opens in new tab) It's not a good definition, but it's good enough to start. A unit test is a test of a small functional piece of code. A better definition is that it's a piece of code that invokes another piece of code. But that other piece of code will only usually have logic inside it. So if we have a method called is login okay and returns a boolean it will probably have logic inside it such as an if or switch or loops anything like that.
[04:42] (external link, opens in new tab) We wouldn't want to write tests for something that doesn't have logic at all. So for properties getter setters that don't have logic probably wouldn't want to write a direct unit test for those. So if I give you this method description and I I don't even tell you what the body of the method is, but we assume that it's a regular method. How many tests do you think we can write for such a method? What's the minimum amount of unit tests that we can have for this method?
[05:17] (external link, opens in new tab) Any thoughts? By the way, if I didn't if I didn't make myself clear, is this is going to be more of a discussion than just a a lecture. So, what's the minimum amount? Anyone? >> Two. >> Two. Cuz it's a boolean returns true or false. It's a good start. Anyone else have a different number? >> Four. >> Four. Why? Because you have two parameters. So, you could probably combine the par parameters in four different ways. >> Okay. So if you combine the parameters in four different ways regardless of the two that we already had that's six or did you mean four including what she said?
[05:58] (external link, opens in new tab) >> So what would you send in for these parameters? >> Either a string null or an empty string. >> Okay. A regular string a null or an empty string. Okay. So that's three for this. And then >> same for that. >> The same for that. >> Okay. So that's six. >> Plus the other two. So that's eight. >> Lots more. >> Well, I'm not saying what's the maximum is. What's the minimum? >> What is the minimum of that? >> No, but still I mean valid username, valid password, valid combination, username, password.
[06:34] (external link, opens in new tab) >> Mhm. >> Exceptions. >> Exceptions. Okay. So let's try to get consensus. Who thinks it's the minimum is two? Raise your hand. Who thinks the minimum is three, four, 5, 6, 7, 8? More than eight. And the rest of you just didn't raise their hands. So, good morning. Please get a coffee.
[07:04] (external link, opens in new tab) Important to listen. Let's let's do the absolute simplest thing (external link, opens in new tab). Let's say it's it's three somewhere between two and four. Okay, we have to have at least three unit tests for this thing cuz we want to check the two boolean values and at least a parameter or something, maybe an exception. So, how many methods that contain logic do you have in your project today?
[07:35] (external link, opens in new tab) More than 100. Raise your hand if it's more than more than 500. >> Yeah. >> More than a thousand. >> Okay. >> How many unit tests should your project have at the minimum, do you think? Simple math. More than 500 tests, more than a thousand, more than 2,000. Okay. Who has more than a thousand tests in their projects today? >> Almost.
[08:06] (external link, opens in new tab) He's like almost pregnant. >> Yeah, exactly. >> It's either or. >> Yeah, if you count in the exception test acceptation >> expectation. So that means that you have like 500 or 400 methods with logic project. >> So it's pretty small project. >> Yeah, that project is pretty small. >> Okay, so that's one project. How many projects do you guys have?
[08:37] (external link, opens in new tab) >> Five a piece. Five for each person. Five projects for each person. >> Yeah, about. But we combine. >> Okay. So, how many total projects does this company have, do you think? >> 30 maybe. >> 30. Okay. >> One out of 30. Pretty good. I >> think more than 30 I think. >> One out of more than 30. Pretty good. >> Good project. >> Why do you think that is? >> It doesn't really matter. It's just a question. >> So, why do you think that is? >> The time issue. I think the customer doesn't want to pay for the service.
[09:07] (external link, opens in new tab) >> The customer doesn't want to pay. So, so we don't want to do it because it takes time. >> The customer, we we don't have the time to do it >> because we let the customer choose whether we want to write unit tests or not. It's like asking the customer, the car costs $20,000 with wheels. What's he going to ask next? Okay, how much is without wheels? Okay, it's like the same question. But, you know, that's a philosophical question. Forget about that.
[09:38] (external link, opens in new tab) Let's say that we most projects do have enough time at least to try some of these things. To understand that, we have to understand what people mean when they say that unit testing is actually good for you. Actually try to to see if that that's true. is when when people like me come to a company, they say, "Look, unit testing will make your lives better,
[10:08] (external link, opens in new tab) Especially if you do it in TDD." Because when you have 2,000 unit tests, it's easier to find bugs in your code. Who thinks that's true? Okay, who thinks that's not necessarily true, though? I think so. Let me give you an example. What if your tests have bugs? Is it easier to find bugs then or is it much worse? Because you do not test your tests. So the if you have a bug in your test, the last place you look for is in the test.
[10:42] (external link, opens in new tab) So it actually takes longer to find bugs if your tests are wrong. So that's not necessarily true. And how do you know if your tests have bugs? You are you going to write a test for your test? And how do you know the test for your test that doesn't have bugs? So it's a very recursive problem. So testdriven development helps with that because with TDD, maybe you've heard of this. You start with a failing test and then you make the test pass. And by seeing the test fail first and then pass.
[11:15] (external link, opens in new tab) You actually get to test the test (external link, opens in new tab) manually, but it's still a test. So you know that it's it's it's failing when it should and it's passing when it should. So, it's one of the only ways I know to actually make sure your tests don't have bugs. It's easier to maintain your code when you have 2,000 unit tests. Who thinks that's true? Are you afraid to raise your hand now? Okay. Who thinks that's not true? Why? >> Because if you have to change something, you have to do it and
[11:52] (external link, opens in new tab) Do the process. you have to change something, you actually have more code to maintain. So that's not necessarily true. So imagine 2,000 unit tests on a on a code base. It could be easily take another half, another 50% of the codebase added. Imagine that you have 20 tests creating an instance of a class and now you've gone ahead and added a parameter to the constructor of the class. So now you have 20 tests that don't compile. You have to go through each one and actually change them.
[12:23] (external link, opens in new tab) And that sucks because you don't want to do that. That takes too much time. So when you have enough tests, they actually begin to slow down your project instead of helping. That's one of the first projects I ever did with unit test had that problem. So after about 6 months, we actually started removing some of the test that we worked so hard to create. So this is absolutely not true by default. It's only easier to maintain if we learn some practices that make the tests more maintainable
[12:55] (external link, opens in new tab) Such as refactoring in the tests themselves. So this could be true but this is one of the biggest problems with unit testing and TDD is that organizations try to implement these things and they get into the maintenance problem and it's it's if you do it enough you it feels like walking in concrete. you want to fix something in the code and you're just afraid of breaking so many tests for the wrong reason that you don't want to do it. So you start hating the tests
[13:25] (external link, opens in new tab) Instead of them helping you. You start hating them and that's usually not a good idea. So we're going to learn what it means to be maintainable. It's easier to understand your code when you have 2,000 unit tests. That might need more explanation. When people say talk about unit testing and TDD, they also refer to it as documentation for your code. Live executable documentation. So you should theoretically be able to get a new person in your team, tell them look at this at the test for this class.
[13:56] (external link, opens in new tab) We need to fix a bug and they should just look at the test and understand magically everything that's happening and be able to fix it. But in reality, the tests are so poorly readable with really bad names that no one can really understand what's going on. So names of tests can be as bad as names of methods. If you've ever come across methods named do this, do it. Then we also have tests like that. Test one, test two, test two.
[14:29] (external link, opens in new tab) Two, etc. So it's pretty horrible the state of testing because people don't pay attention to the readability of the tests. So this could be true but by default it's not true. Only when all these three things are true do actually unit testing and TDD help you. If even one of them is false then it breaks down the other two and unit testing begins to be a burden instead of helping. So you can maybe start seeing why so many organizations are actually trying to implement unit testing in TDD and failing
[15:01] (external link, opens in new tab) Maybe failing after a few months maybe failing in the beginning because they try to either do too many things at the same time or they don't understand these problems so they don't learn how to make things more maintainable more readable and they don't trust their tests. An example of mistrust is when you have a bunch of tests and you run them and you get a green light, everyone everything passes and you still get the urge to debug your code just to make sure that means you don't trust your tests. Okay, if you are in that position, something was wrong
[15:36] (external link, opens in new tab) Because the test maybe you've experienced the test that are false positives or anything like that. TDD helps with that feeling to make sure that you actually trust the things that you run so that when they pass you actually good about yourself. So get getting back to the question of why do people not have enough tests? Well, part of it is because they get slowed down by these problems and part of it is because
[16:06] (external link, opens in new tab) It takes a lot of time to to have enough tests in your project. because people don't use usually frameworks to do the their test. They create their own frameworks. They create their own forms. And I usually tell people that everyone I I know has already done unit testing cuz I don't know a single developer that does not check their code before giving it to someone else. Everyone does it. The only difference is is it automated? Is it structure? Can anyone else test the code that you just did or are you the only one?
[16:37] (external link, opens in new tab) Cuz when I started before doing unit testing with automated tests, I used to create forms, you know, with buttons. Text one, text two, button one, button two. It looked like a Picasso painting. Only I knew the exact order of pushing all these things. And at some point, magically, I knew that something was working, but no one else on my team was able to use that form. And of course, I didn't remember to do it two months later. So most people do test their code but they don't do it in a repeatable way.
[17:07] (external link, opens in new tab) They don't do it on all their code because it takes time to reinvent the wheel every time and create those forms and consoles. So they they just choose the things that scare them the most and they test those. But there is another problem with why people don't with another reason why people don't add a lot of tests is because when you write the code you're not objective about whether it's working or not. I think we all know this.
[17:38] (external link, opens in new tab) If you write a code you're when you test it your objective is to prove it works. Okay? So you're expecting it not to fail. So you're only doing the simplest thing to prove to yourself that you did a good job. That's a very human thing. That's why sometimes we say that QA is good to be a separate part because it's a different person who's not who's more objective about your code. So they're actually looking for your code to fail, which is a better thing to do.
[18:09] (external link, opens in new tab) But with TDD, we have several practices that make sure that we don't we don't become too subjective. We become a bit more objective. So we try to see our code fail instead of pass. And when we do that, we get more tests. We actually try to prove that something doesn't work. And then we get better code coverage (external link, opens in new tab) and all that stuff. But that's not easy to do. We need a practice to learn how to do that. But the the most fundamental shift when people start doing testing in automated way is framework.
[18:46] (external link, opens in new tab) So there are a bunch of frameworks for doing unit testing in practically every language you can think of. Most of them are open source and free. Some of them are not free. I think maybe just one or two in .NET world. The first one started with Smalltalk. Anyone here ever tried Smalltalk by the way? Okay. So there is one when I started doing unit testing I was working in VB5 and VB6. So I started with a framework called VBUnit about 10 years ago.
[19:17] (external link, opens in new tab) I had no idea what I was doing. It was pretty horrible experience but the I remember the things I remember from then still apply today in terms of how do I write tests. What I'm trying to say is that all these frameworks have a standard test architecture. So if you've written tests with one of these frameworks before and you know a different language, writing tests in that different language should be seamless, should be very easy because the idea of how to write the test, how to structure them, how to execute
[19:49] (external link, opens in new tab) Is about the same in all these frameworks. So they're collectively called the xUnit frameworks (external link, opens in new tab) and everything that represents X is the name of the language at the beginning. So SUnit etc. Today we're going to cover NUnit (external link, opens in new tab) which is an open- source free framework for .NET. However these are just examples. You can apply a lot of the lessons here to any programming language that you use either Java or JavaScript.
[20:22] (external link, opens in new tab) There is for example it's not written here but for JavaScript there are several unit test frameworks. The a really big one used was a cube cube unit though it's considered a bit old these days. The one that's that everyone is talking about in JavaScript these days is called Jasmine. So if we if we get time to do workshops today the JavaScript developers I will ask you to try to use Jasmine for your for your workshop.
[20:53] (external link, opens in new tab) Okay. But most of the demos will be innet. What do all these frameworks provide? What do they give us? Well, they give us a structured way to write the tests. Usually as methods inside classes and those methods will have attributes on them and those classes will have attributes on them. They give us a structured way to run the tests. So those attributes will be used by two types of runners either command line runner or a UI runner. And all these runners what they do is they look at the compiled code that you that you created if it's compiled and they look for these attributes that mark a class that contains tests and when they find those they execute all the tests in that class with se several simple rules in between
[21:40] (external link, opens in new tab) And they allow you to also review the results. So if a test failed it will tell you the line number where things failed, what actually failed. Maybe you expected one thing but you got another. So you should be able to get the feedback on whether something works or doesn't work very quickly and go to the code that doesn't work pretty quickly. And all these are done in the same way across frameworks and the point is so that so that you can share this knowledge. If I wrote a unit test then anyone else in my team can run the unit test.
[22:15] (external link, opens in new tab) Anyone else can can see the feedback because the feedback is the same. It's same output type. So anyone can take action based on it. So it's no longer me creating reinventing the wheel. Before we do that, here is how you in you create a class containing tests with net. By the way, what do you guys use? MSTest or NUnit these days? >> NUnit. NUnit. Okay. So, NUnit is definitely the preferable framework to MSTest because it has more features and it's
[22:51] (external link, opens in new tab) It's more stable. It's also faster than MSTest. It's also free and open source. So, you can actually extend it, see the code, understand how it was written, more extensible. To create a class that contains test, you just create a regular class in your code, hopefully in a separate test project. You add a reference to nunit.framework framework DL and from there you import an attribute called test fixture and test fixture is a name inherited from early days of JUnit because NUnit was built on the idea of JUnit from Java but Java was first in unit testing That was bit of a late second.
[23:32] (external link, opens in new tab) I don't like this name for me. It took a while to understand what fixture fixture means. So I just call it test class in my head. This is a test class. And that's one thing that MSTest, right the name of the attribute is more readable. But that's basically the only problem that I see. So we put a test fixture otherwise the test won't get recognized. Then we create a public void method in our class. Every method that is public and void, doesn't take arguments and has this attribute on it
[24:08] (external link, opens in new tab) Will be executed automatically by the test runner (external link, opens in new tab). This is by the way an example of a really bad name for a test. So here's an example of the badly named test and what a test might look like. So every test will usually have three parts. So they're collectively called the arrange, act, and assert or the AAA pattern.
[24:39] (external link, opens in new tab) The arrange is the part where you arrange things to be tested on. You might create an instance of this, create an instance of that, set up multiple settings on your application model, create them in memory. The act part is actually acting on that object model. So invoking something or changing something and the assert part is the the part that's the most important is the part that decides whether the test failed or passed.
[25:10] (external link, opens in new tab) This is a class called assert part of the nunit.framework DLL. That's the class you're going to use most of the time. It has a lot of methods with a lot of overloads for checking various things. The assert are equal are one of the most used methods in there. The first parameter is the expected value. The second parameter is the actual value. A lot of people get those mixed up. Nothing will happen if you get it mixed up except that the messages that you get from the framework will look really weird.
[25:43] (external link, opens in new tab) Like in here, it might say, "I expected one, but it was three." You're going to look at the message and say, "No, but it was supposed to be three." So if you got these parameters wrong, you're going to get a lot of confusion. The last part of the most of the assert messages the methods is the message that will be sent if the test fails. You should never ever use this parameter.
[26:14] (external link, opens in new tab) And the reason is that instead of this the name of the test should reflect what's supposed to happen. If the name of the test is good enough, you won't need to write this message. Okay? So, this and the test name (external link, opens in new tab) are synonymous. And usually the test name is more important because when you have 2,000 tests and 10 of them fail, the first thing that you see is the name of the test. The messages are harder to read. The name of the test will contain most of the information that you that you can convey.
[26:48] (external link, opens in new tab) So, we're just going to do a short demo and in that short demo, we're going to write our first unit test for a real project. I'm going to create a new project here. Actually I'm going to create a new solution because usually when we talking about projects and tests we will have separate tests in a separate project.
[27:21] (external link, opens in new tab) So let's call it we're not doing test driven development yet. I'm just showing how to write unit test. But for now, actually going to create an existing product. It's going to be a class library just for demo purposes. product.logic. How's that? And I'm also going to show you how I use the environment in Visual Studio to change things.
[27:55] (external link, opens in new tab) How many people here use ReSharper in Visual Studio? Okay. How many people here really know ReSharper? It's an interesting question. So I'll I'll show you how I try to use it and I would recommend learning all the shortcuts so that you can be much more productive when you write code. So let's create a class. I'm just going to rename it to my calculator.
[28:28] (external link, opens in new tab) So it's a very complicated class as you can guess. public void add int a int b and of course it's an int. Sorry. Return a + b. That's not it. Not used to the >> alt good, right? >> I think has a Swedish keyboard there. >> So where is it? It should be it should be there.
[28:59] (external link, opens in new tab) Test. Well, there it is. Okay. And if we have the just such a simple method, by the way, ReSharper tells me all this stuff is not used. I'm just going to alt enter to get rid of this. No need to have mess in my projects. To write the test for this, I'm going to create a new project. It's not going to be a test project because that's the MSTest version of this.
[29:30] (external link, opens in new tab) We don't want that because it it creates a lot of extra stuff that we don't need. So, we're just going to create a regular class library. What should be the name of the test project? Usually, it's the name of the project that you want to tests or unit tests. So my product logic unit tests
[30:01] (external link, opens in new tab) And the reason I'm saying unit test and not just the word tests is because I separate the idea of a unit test for from anything else. For example, an integration test (external link, opens in new tab). We'll talk about these differences soon, but for now you should know that you you probably have two types of test projects for each project. integration test projects and unit test projects and they should be separate for a lot of good reasons and one of them is being able to trust the results. So now if I want to test my calculator
[30:34] (external link, opens in new tab) And I want to prove that this method works what do I name the class that contains the tests? My convention is the name of the class that you're testing if you're testing just a class tests. So my calculator tests and of course it needs a test fixture attribute. We don't have nunit here. So what I'm going to do I'm going to use NuGet. Who heard of this NuGet?
[31:07] (external link, opens in new tab) For those of you who don't know what it is, it's a plugin for Visual Studio that allows you to install things dynamically from the web and add the references to the project. If you if you ever done Ruby, it's kind of like Ruby gems, only for .NET. So I can go to tools, library, package manager. Awesome name by the way, package manager console. It's here at the bottom and I can just say install package and NUnit.
[31:39] (external link, opens in new tab) I can also have a UI to see all the packages and explore. This is just a much faster way to do it. So what what it's going to do now is it's going to go out to the web, look for the package, download it. Hopefully it will find that package. Download the package. Add each reference. And the interesting is that it by default adds different weird references to it that we don't actually need. Sorry. First of all, one of the basic things that happen is that you add it to the wrong project.
[32:15] (external link, opens in new tab) By default, it's not set up to test projects. So you can see my product logic just got a bunch of new references. So I'm just going to delete them. I'm going to tell the package manager to be a bit smarter and do the same thing. Okay. So now here are the references that we have.
[32:45] (external link, opens in new tab) We only need one of these. So the first one is the nunit framework. This is the important one. This is the one we're going to use. NUnit mocks is an internal assembly that was used to test nunit itself. Okay. It's not a mocking framework (external link, opens in new tab) that should be used in a public production hub because it doesn't have all the features of today's frameworks and it's discouraged from using. So we actually going to remove this in a second.
[33:18] (external link, opens in new tab) The third one is called PNUnit and it's NUnit with parallel test support. We don't care about this yet. This is a more advanced scenario. So we don't want we don't need this. We just need the first one. I'm just going to delete these two. Okay. So now that I have the references, I'm going to use the test fixture. And if you haven't seen what I just did, I don't have a using yet. So, and so the test fixture word doesn't get recognized.
[33:49] (external link, opens in new tab) So, I can use a special intellisense setting from ReSharper this in this case it's control alt space to add to get a list of things that I have a reference but not a using to. So, test fixture you might want to write this down if you want things to you want to know this later. So, test fixture and I also get the using for free. Next thing I'm going to do is I'm going to write my first test. So, how am I going to write this test? Well, it's going to be real a really simple one.
[34:22] (external link, opens in new tab) We're going to prove that when you add two numbers, it sums those numbers up. Now, this is not usually what I would write a unit test for. And the reason is because there is no logic here. There is only a mathematical formula. We don't test mathematical formulas. We only test logic. But for the for demo purposes, it's okay because it's very very simple. Test
[34:54] (external link, opens in new tab) Public void. What should be the name of the test? >> Some one sum one and three. Any other suggestions? >> That was a good suggestion. Although it's missing a few things. >> Maybe something that was expected. >> Was expected. >> Yeah. So what do you expect? >> Three. >> Three. Okay. >> Or you expect a sum. >> You expect a sum. But he said some. So here's the way I structure the name of the test.
[35:24] (external link, opens in new tab) This will help you if you've never written test before. The way I think about what test should convey. Three parts of knowledge. The thing that you're testing or the unit. It could be a method. It could be multiple methods. it could be multiple classes. So it's not a unit in terms of amount of code. It's a unit of work (external link, opens in new tab). It's a scenario in the system. In this case, the whole scenario is contained in one method calling add. So add is the thing that I'm testing. Then I'm going to put an underscore so that it's easier for the reader of the test to understand to separate these parts.
[35:59] (external link, opens in new tab) Add followed by the scenario. So I'm testing add by doing something with it. So add given two numbers. I could say given one and two, given two and three, whatever, but that's an implementation detail. I want it to feel like a requirement. So if you call add with two positive numbers, it sums them up. Might be a requirement or two numbers. Doesn't have to be positive. So it's it's the explanation of the implementation, not the implementation.
[36:33] (external link, opens in new tab) So add with two numbers and then the third part is the expected behavior sums them up. So if I know how to read these three parts, I can turn it into an English sentence. Calling add with two numbers sums them up. I can also say maybe something like this returns the sum. It's also another way. There are many ways to name this but the three parts should remain even if you don't have the underscores. Even if you've thought about doing BDD (external link, opens in new tab), if you've ever heard of this, BDD also contains the three these three parts of knowledge.
[37:08] (external link, opens in new tab) They just exist in different places. Okay? If you only have two of these parts or just one, the reader of the test is not going to understand some of the things. So, a lot of tests might look like this with the name add two numbers. So, the reader will be reading this and saying add two numbers. Okay. What's supposed to happen? So now they have to read your test to understand it. And if it's a good test name, you don't have to read the code for the test to understand the requirements. Sometimes it might look like this.
[37:40] (external link, opens in new tab) Add returns the sum. Okay, when does it returns a sum? What should I send in? What's what's the point? Okay, so the scenario is important. And of course, sometimes it's this two numbers returns a sum. Okay, but what's being tested? And the reason it's important is because that sometimes in a test we will have multiple invocations of methods which one is the important one which one is the meaningful one for our purposes. So it should we so it's guide it's like a guide map telling us what what happens
[38:16] (external link, opens in new tab) And you can see that ReSharper doesn't like my naming convention but that's not a new thing. So, I'm just going to shut him up by Alt Enter. Inspection options. Get a hint. That's much better. Now, the icon that you see on the left is from a product called Test Lint, which is a free product, and it's kind of like a test review (external link, opens in new tab). So, it will tell you if you have problems in your test. So, what it says right now, if I click on it,
[38:47] (external link, opens in new tab) No asserts. Okay. And no asserts. Sometimes you forget to write in a ser especially when you're just learning. So that might help. >> What was it called? >> Test Lint. Yeah, it's part of a commercial product, but this part is free. And we might talk about these things later. Right now, it's just installed on my system. So you might see this icon. I wanted you to know what it is. We're not going to talk about it anymore, though, because the whole point is that you don't need the splint hopefully after these four hours.
[39:19] (external link, opens in new tab) Okay. Now another of a few more important things about the name. If the name is good enough then it will help you when you write the test. Okay. So sometimes especially when you're learning you're getting so caught up and deep inside the guts of the system of how to do this and make use mock objects (external link, opens in new tab) and do this and do this framework that you forget what it is you were trying to do. That's when you actually look back up in the name of the test and say okay so this is what the assert should be should do.
[39:54] (external link, opens in new tab) So the last part should tell you how the assert should look like. The middle part should tell you what the act should look like. Okay. Or maybe the middle part also tell you about the arrange part. What is the scenario? So it's worth it to spend a good amount of time on the name. Okay. I I've I've had times where I just sat there and thought about the name, especially if I was doing pairing for 10 minutes, sometimes
[40:24] (external link, opens in new tab) 5, 10, 15 minutes just considering what the name is. What it means if you cannot write a good name, sometimes it means that you don't know how to test something. Sometimes it means that you're not sure what it is you're trying to test. So coming up with the name is basically the coming up with the design of the test in your head. When you can express the design in words, then you can actually start writing the test. So the name is one of the most important things. If you start out with a really bad name and you end up with a you might end up with a really crappy test because you don't have any guiding light about where you're trying to get to.
[41:01] (external link, opens in new tab) But right now it's a really simple test. Let's create the instance of the calculator. My calculator I don't have a reference to that project. So ReSharper is smart enough to tell me, hey, you want to add a reference and use that. Yes. So my calculator C equals new my calculator. That's the arrange part. And now I'm actually creating an empty line so that the arrange and the add part are separate. I think it's more readable that way.
[41:33] (external link, opens in new tab) C.add. >> Well, it takes two ins. >> Yeah, >> it won't compile. >> But I know it's meant to be fake. >> Yeah, but failing is not the same as non-compiling. Failing is that I run it and I see it fail. Okay.
[42:04] (external link, opens in new tab) And we're not doing TDD yet anyway. Good try. What should I send in? One and one. Why is this a problem? Okay, so let's consider readability again. Okay, imagine that the reader of your test is a serial killer who knows where you live. >> It will give you some motivation to change.
[42:35] (external link, opens in new tab) So imagine this serial killer looking at your test and saying, "hm, very nice name. I like the way they write the name and see add one and one. Hm, this must be important because they're sending in the same number twice." So it looks like a bit like magic. I wonder what it means. I shall go to their house and ask them. Okay, you don't want that. So you want things to be obvious. So sending in the same variable twice as inputs for a test is usually not a good idea unless that's specifically your scenario.
[43:08] (external link, opens in new tab) Okay? For example, if this was a divide method or subtract, the order of the parameters would matter a lot. And if they're the same, sometimes you'd get a false positive because it would equal zero no matter what the order is if it's subtract subtract. You don't want that. You want to be able to have variability in the inputs. So, what should I send in? >> One and two. >> One and two. Other suggestions? >> One and five. >> One and five.
[43:38] (external link, opens in new tab) Oh, that's much better. Do I hear seven? >> One and zero. Interesting. One and zero. Well, I have a problem with zero. I have a problem with five. Two is much better for me. Notice how much time I invest in getting a good input because readability is much more important in tests than it is in code because you're going to have much more tests for the same piece of code. And you need these to be executable documentation.
[44:11] (external link, opens in new tab) So one and two is much better than one and five. Why? Because if the serial killer comes in and says, "So you're sending in one and five. Why five? What's so important about five? I shall go to their house again and ask them. But one and two, I can see the pattern. Oh, just two numbers, consecutive numbers. One and zero, oh, it's going the the other direction. And zero usually has special meaning in inputs. So don't use zero if you don't have to.
[44:42] (external link, opens in new tab) One and two are good because they're the first simple numbers right after zero and they're consecutive. So your brain is designed to see patterns. So one and two is better. Then again, I mean, >> if we're going this way for readability, we shouldn't do it this way at all. We shouldn't take care variables with a name. >> We could we could >> tell them what the meaning of them is. >> That's true. However, if you have this as a consistent way of writing tests, it becomes just like you have loops with I and J.
[45:16] (external link, opens in new tab) Everyone knows what I and J means, right? You don't have to give them a good name usually in loops. So, it's the same thing. If you always have one and two or you have strings that say some name instead of Henry. >> Yeah, but you write like the one you like some positive number or whichever. >> So you can when do I use a variable name? When I absolutely have no good inputs that don't feel magical. That's when I would give them a good name like some positive number. And then I can actually do this some
[45:47] (external link, opens in new tab) Positive. Okay. But usually one and two is just good enough. I've never had problems with one and two. >> And it seems also when I was thinking one and zero like one aspect of the method that's good. >> But when you read tests, one and two is just more readable. It's just my experience. So if you if you read enough tests, you're going to see one and two just becomes second nature. Okay. And this is now now we're being practical cuz we take this all the way to chaos like variable names for everything like expected and all that stuff.
[46:20] (external link, opens in new tab) And then becomes unreadable again. >> Yeah. But isn't that a bit bit dangerous because I mean there are scenarios where it's actually important that it is specifically one and two pass by ordering a list >> and then I would give them a good name that says an int that is below two for example that I would specifically state that the value is important. If the value is not important I give default values. These are default values. So I use because these are used much more then I would use these for the default value and I would use variable names when it's important when the values are important.
[47:01] (external link, opens in new tab) So one and two. So we have another line in our test. Let's make sure that we have a value. So extract variable int result separate with another line. And the last part is the assert. assert are equal. We can see all these overloads. The first one is the expected. What should I write down? >> Three. >> Three. Why not write down 1 + 2? >> Cuz you're expecting three. >> Yeah, it's the same thing.
[47:33] (external link, opens in new tab) >> What's the problem? >> I have logic. >> The serial killer will come to your house. >> The serial killer will say one. No, I didn't understand it. I'm expecting three. What's the problem? >> Readability. >> Readability. Seems pretty readable. It's just one and two. >> It's not clear enough. >> You're you're you're not testing. >> Well, here's the result. This is what it look like.
[48:04] (external link, opens in new tab) So, big problem here is there are two problems. First, I have logic here. So I could actually have a bug in the test (external link, opens in new tab). I don't want to have any logic in a test. So that's the first reason. The second reason is that if you're creating the thing that you're expecting on dynamically, which I am one and two instead of just using three, then most likely I'm repeating the same logic from production code. So if production code has a bug and I'm repeating the bug in my test, my test will pass.
[48:35] (external link, opens in new tab) I will never know that I have a bug. Okay? So it's usually a bad idea to dynamically create the expected value. By the way, Test Lint will tell me this says logic in test in this case. So three is much better. When we talk about reusing, maybe I needed one and one in multiple places in my test. I would still probably use one and one in multiple places because here the readability is more important than than the maintainability of the test.
[49:11] (external link, opens in new tab) If you can't read it, it doesn't really matter. You can maintain it or not. You won't be able to maintain it if you can't read it. So now that I have these tests, how do I run them? So I can use the default NUnit runner, but no one uses that anymore inside Visual Studio. People use the command line runner in automated builds (external link, opens in new tab) but for us there are several ways. First we have ReSharper install. So you can see these green icons. So I can run this test like this.
[49:43] (external link, opens in new tab) It will compile my code and I can see the results Now, I'm not sure what why it's taking so much time, but now that we have the results, you can see a green bar. When people say red green refactor, they mean that green bar. Personally, I don't use the ReSharper test runner. It's just my preference. Lately, they fix all the bugs that I had a problem with ReSharper, so I would have no problem using it on a real project.
[50:18] (external link, opens in new tab) But I usually choose a different runner. So, you can choose whatever you need. So the runner that I use is called TestDriven.NET. It's free for personal use. It costs money if it's commercial for a commercial project. But it has at least one advantage over ReSharper test runner which I'm going to show in a second. First of all, how do I use test driven? I can right click on a test and I see the rocket icon. That's how I run it.
[50:48] (external link, opens in new tab) And based on where I click, it's going to run the test in that in that scope. So if I right click on the class, it will run all the tests in a class. If I right click on a project or in a solution, it will run all the tests in that solution. So usually what I do is I run all the tests in this project and the output is not visual. It's in the console. One passed, zero failed. Sorry, the name of the tool TestDriven.NET.
[51:20] (external link, opens in new tab) That's also also the URL of the site. It's really really fast. Jamie Cansdale is a used to be an MVP for Microsoft. And he's a really smart guy. So he made this work for any version of Visual Studio, even the express version of Visual Studio, which doesn't support plugins. Okay. So, he made a plugin that loads in Express which doesn't support plugins.
[51:51] (external link, opens in new tab) So then Microsoft legal approached him and said, "You're a bad guy. We hate you. Please stop." They took his MVP away. And but he's still working on these things. You know, I think that he cuz they didn't like that he allowed people to do unit test. So, obviously that's very understandable. So anyway, we were trying to run this test. How do I use test driven in my TDD process?
[52:22] (external link, opens in new tab) First of all, I don't like to use the mouse too much. So I the first thing I do when I install a new IDE on my machine is to set a shortcut to rerun the test in my project. So the way you do it in Visual Studio is tools, options, keyboard. And this here, this is a magical place. You can find all the actions in Visual Studio that you can ever think of. And if they already have a shortcut, you can see that shortcut. So, if you've ever wondered how to
[52:53] (external link, opens in new tab) Close a tool window, this is the shortcut for that. Okay. But for me, I'm just going to look for rerun tests. And there's one shortcut from testdriven looking like this. Going to press alt R cuz that's the one I like. U, it will tell me that alt R is currently taken by a very, very important shortcut in Visual Studio, image.ctangle
[53:24] (external link, opens in new tab) Tool in the Visual C image editor. I'm going to take a risk and assign it to me. Once I do that, I can change my production code. For example, no matter where I am, if I press Alt R, it will run the test. I don't have to move to my test to run them. And I get a very quick feedback without needing to do a lot of work. So, if I do this, a plus one error, hold R, no need to even save because it saves and compiles for you.
[53:56] (external link, opens in new tab) I can see the results on the bottom really quickly. Another nice thing about it is that if you see results that look like this. So that's the line number and the column number, you can double click and you'll get directly to that line. You can see this >> was all our context based >> context or was it >> the context is global so it will work everywhere. >> Okay. You can choose to only have it work in specific context, but I don't touch that stuff.
[54:28] (external link, opens in new tab) Just say global. Don't care. It's called brute force engineering. So this is expected three, but was four. And you'll notice I didn't have to write a message on the assert to get this. This is built in to all the frameworks. So if the test name is good enough and the messages are good enough, all I need to do is to look at the name of the test say add two numbers return failed. that will usually tell me that I might not even need to look at the test itself, but I can go directly to the code.
[55:05] (external link, opens in new tab) Questions so far, >> how how do you read the test? >> How do I read it? >> Yeah, I start reading the asset code first. >> Yeah. >> And then the logic. >> if you if you didn't write the test, I would suggest reading it from the bottom up. So you would start from the bottom and say okay this is the assert. The line just before it is usually the act part and the top line is the arrange. But I would start with the name and then I would start with the assert because the name is the most important part. Other questions?
[55:37] (external link, opens in new tab) Okay. So you see it's pretty simple to write a test but it's not maintainable. For example, I'm instantiating my calculator directly inside the test. That's usually not a good idea. I would refactor this. And we're going to see exactly how we're going to do this. But I only do this when I have two or more tests passing that use the constructor. And the reason is when I do TDD, for example, the point is to drive new development.
[56:09] (external link, opens in new tab) I don't refactor my tests until at least I have two of them that need the same thing. Otherwise, I just leave it alone because there's only one usage of the constructor. So no need for a problem later. Let's see some more attributes that built are built into NUnit. The first one is called setup. And setup is allows you to create a method my setup.
[56:43] (external link, opens in new tab) And in my setup it gets executed once before each test in your project. So the setup if I run this you can see that setup gets executed first and then any tests. So if I had two tests setup would be executed twice. Okay once before each test in my file. This is important because this is a good place to initialize things or maybe not. We'll see in a second. So what I can do to avoid this maintainability problem is to just delete the whole thing,
[57:18] (external link, opens in new tab) Declare it at the class level and here sorry I would say C equals equals new my calculator. So now my test looks like this. If I run the tests,
[57:51] (external link, opens in new tab) You can see that I still get the same error from before. Let me fix that for a second r. See the feedback is really quick. So now I've solved my maintainability problem because if tomorrow I add a parameter or something changes in my constructor, I only have to change one place instead of 50 tests. So that's a good thing. But I've created another problem in the process. And that problem is
[58:22] (external link, opens in new tab) The serial killer problem again. Sorry. Imagine that this is the bottom of a really long file. The serial killer looks at this and says C.add add. Where did C come from? Where did you initialize it? Do I get the same instance every time? I have to do this. I hate hovering with my mouse over variables. Where is my axe? So, you don't want that. We want the reader to always be able to see the test and understand anything they need just from looking at the test, not to look in two places.
[58:55] (external link, opens in new tab) And here, I'm forcing them to look in two places. So, I don't like to use setups for that. Instead, what I'm going to do is I'm going to declare my calculator directly inside my test equals need my calculator, but instead of refactoring it to a setup, I'm going to refactor it to a factory method.
[59:26] (external link, opens in new tab) So, Ctrl Alt M in my case a method get new C. You can name it whatever way you want as long as it implies to the reader you're getting a new instance of a calculator. So now I've solved the maintain maintainability problem because I just need to change one place. I just need to make sure that all tests never created directly. you just get a factor.
[59:57] (external link, opens in new tab) And I've also solved the readability problem because the reader knows that C is always new. If they want to, they can go and look, but they don't need to. They just need to know what is what is C. Sometimes the ReSharper will tell me use var and to which I will say no, I don't want to use var. Never tell me that because in tests if you use var now you don't know what is the type that is returned from the factory method. So it's I I would suggest not to use it.
[1:00:29] (external link, opens in new tab) Let's just run the tests again. Make sure everything's great. Awesome. So now I have a maintainable readable test. Questions so far? >> Yeah. If you I will call it call C my later and use what >> sorry >> you can use bar and have a better name >> I can have a better name for C >> yeah it might >> that's true
[1:01:01] (external link, opens in new tab) But then what's the point the same thing >> yeah but >> just because it could have a a different name from the actual type because the actual type has to be there. The name could can be a million things. So, it's it's something that so much depends on the style of the person writing the test that I wouldn't recommend to allow VAR at all. It's like a red line. Once you cross it, you can just go crazy. Okay. So, I'm not even going to let that that that hole in the fence.
[1:01:35] (external link, opens in new tab) But I can see what you mean. Maybe I would do it if I had a lot of experience and all my team would be as consistent as I am. Okay. But it's much easier to say never use var say has a variable name as the same name as the type that you're creating. Okay. >> Okay. Other questions? >> Yeah. When you were running the test >> Mhm. >> and it worked >> Sure. >> you only got like what was it in the bottom? It's like one pass.
[1:02:05] (external link, opens in new tab) >> Mhm. You don't get the output when it's passed or you only get the output when it failed or what? >> I always get the output. >> But when it failed, you get like, you know, the sum was three. Your result was three, but the sum was four. >> Yes. >> But now you don't get that >> because I don't have any errors. >> Yeah, that's what I'm asking. >> So I only I only get output for errors that happened. I don't see all the tests that were executed cuz maybe I have 4,000 tests. >> Yeah. >> Okay. But if you really want to know that you know you have to like trust your test and that it's passes when >> you have to trust your test and now that's the reason where TDD really helps because you you felt the test fail and you felt it pass so you actually trust it.
[1:02:46] (external link, opens in new tab) So cuz you he's your friend now cuz you you you passed something together, right? And like you wrote it and then it passes and then okay, let's debug it just in case. >> No, it actually failed. You went through something with that test. You know it. >> Okay. Okay. Okay, could you please run it run using ReSharper so we can see the difference? >> So ReSharper has a nicer view. You can see that's the difference. ReSharper you will know all the things that were executed.
[1:03:16] (external link, opens in new tab) >> So what is the benefit with the TestDriven.NET? Is it only speed or? >> Well for me the benefit is that it feels faster. I don't know if it's actually faster. It's cleaner in terms of output. I just care something failed. I don't need to know about everything that I executed. So this feels like almost like like a waste. Why did I have to go through painting all these colors? You know, all these things take time. And when I'm doing TDD, the cycle can sometimes be seconds. Okay, this go fixed alt Run get the output.
[1:03:46] (external link, opens in new tab) Okay, I just need to know one line of code. If there is more than one line, I just need to know what failed. Okay. But I will admit that some people like this better and I have no problem with that. This so you will find your own your own your own taste. For me there is one extra benefit to TestDriven.NET that ReSharper doesn't do and that's that's one of the main reasons that I have it installed always. So what if you wanted to debug something with ReSharper?
[1:04:16] (external link, opens in new tab) You would put a break point here, say debug, and you would break into the test. Awesome. Not a problem. Not a problem. As I said, okay. I have no idea why it's not P. Oh, there it is. Easy. Okay. So, I can go through and just do whatever I need. If you wanted to do the same with test driven, you would say test with debugger.
[1:04:53] (external link, opens in new tab) Same thing. So that's not a problem either. What TestDriven.NET allows also is to debug production code without tests. So I can actually go into this method here, put a break point and say right click test with debugger. And if I have parameters, they will get the default value. But I don't need a test. I don't need a console. I don't need anything to start debugging production code. So if I need to send in parameter values, I can either change them in the watch window or I can create my own like private
[1:05:29] (external link, opens in new tab) Void test and add one and two. Okay. put a break point here and say test with debugger. So I have an entry point where wherever I need it. So that's a really helpful thing. Okay, no tests needed. That's one of the cool features that I like about it. Okay,
[1:06:00] (external link, opens in new tab) Other questions >> in the test. So I mean I see the logic manufacturer method but what would you use setup for? >> Okay. So I would use setup for when I have maybe static singleton somewhere that I need to reset before each test stuff like that that all tests need to have a clean system. But if you cannot you cannot reset an instance of a singleton. So you has to reset it manually. So either in a setup or a tear down (external link, opens in new tab) method which is the opposite of setup.
[1:06:32] (external link, opens in new tab) Happens after each test. Okay, that's the only times I would use setup and tear down. >> But say like you have five tests, each of the tests will use a database connection set up like test the database connection. >> Okay, so >> if I have tests that use database connections, I would not call them unit test. I would put them in a separate project called integration tests. And integration test, I will probably use setup for a lot more stuff because I need to set up database connections and all that.
[1:07:02] (external link, opens in new tab) This might be a good time to talk about the differences. So what is a unit test now that we know what kind of what it looks like and feels like a unit test is something that runs only in memory. It doesn't touch file system or at least not directly as not specifically by us wanting it. It doesn't touch databases, web services. It runs fast. It's automated. It's repeatable. It's always the same. So if for example if you use daytime now in your unit test then every time you run it it's a different test because it's always a different daytime now.
[1:07:39] (external link, opens in new tab) So you should use hard-coded values instead of dynamic values should not use random numbers. You should use always the same inputs. Now I'm not saying that randoms and daytime now are bad. I'm just saying that for a unit test they will change the meaning of what the test does every time. A unit test is always repeatable the same way it was. You can do these randoms and all that in integration tests. So integration test is everything else. Okay, they can run slower because they have database connections.
[1:08:09] (external link, opens in new tab) They have more dependencies. And the separation is important because what we want is to have a safe green zone (external link, opens in new tab) for our developers. So the scenario that I'm looking for is that a developer comes to work this morning, gets the latest version from source control. What do you guys use? TFS >> get >> subversion >> subversion. Awesome. So you get the latest version from subversion and you have a test project and you right click and you run all the tests in that project and they should all be passing.
[1:08:41] (external link, opens in new tab) There are there are no integration tests whatsoever. They're only unit test. And because they're unit test, they're only in memory. There are no dependencies. Everything is under your control. They should all be passing. And if one of them is not, you should really feel concerned. Should actually be worried. Okay, what happens if you mix up unit in integration tests? Well, integration tests have a lot of dependencies. So, sometimes your computer doesn't have a database or a connection or you're not connected to the Outlook server or a caching or web service or that crap. So, you run all the tests and some of them fail and then someone comes to you and says it's okay, don't worry about it.
[1:09:17] (external link, opens in new tab) You're just missing a database connection. So you just go on with your life thinking that you have better things to do than fix that failing test because you assume that it's failing for the wrong reason. So you don't trust the results of the entire test run. We want to be able to trust the result. So maybe some of them are actually failing for the right reason and we don't know. So at the very least by separating we have a unit test project that should be green 100% so that at least we know that a subset of them are working and we fully trust the results.
[1:09:50] (external link, opens in new tab) Now with integration test project we would have maybe instructions on how to make sure that all the tests pass for that project but we expect some of them to fail usually on a new machine. For a unit test we expect them never to fail. there's a big difference in the expectations. Okay. And that's the difference between trust and no trust in that regard is that if you feel the need to debug again, then it means you don't really trust the results. So I want developers to have that green zone.
[1:10:23] (external link, opens in new tab) That's my speech about integration versus unit. Any questions about that? Okay. Another important attribute that we need to learn about nunit is first of all let me clean this up. What happens if we have this kind of logic? If a is smaller than zero,
[1:11:03] (external link, opens in new tab) Is not going to give me negatives. Okay, let's say we have this logic. How do we test this logic? We could just run all the tests. Oh, so I what I did is alt R is rerun the test or it repeats the last action. We're just going to rerun all the tests here. So now Alt R will always do this method, this technique. If I want to write a test for this, I'm going to need to check that it throws an exception. But before I'm going to write a new test, I realize that if I'm going to write a new test, I'm going to have to write all this crap again.
[1:11:39] (external link, opens in new tab) So, let me create a snippet with ReSharper so that I don't have to do that. I'm going to copy all this. Go to the ReSharper live templates. Create a user template. We'll see what that means in a second. Now, what I want is that every time I type the word test and press tab, I get this. Okay, just an empty test for now. So, I control S to save. And now if I do test, you can see that I can see it here.
[1:12:14] (external link, opens in new tab) If I type tab, I get this. It's better than nothing, but still not good enough. So I'm I'm going to go to my template and I'm going to make this dynamic. So I'm going to put to make something dynamic in my template, I'm going to put it with two dollar signs. So dollar here, dollar here. Now it's dynamic. I go back to my code. and I type tab, I get this. That's awesome. So, I'm just going to go back,
[1:12:44] (external link, opens in new tab) Make everything else dynamic as well. So, that I'm going to make this the this is the unit scenario and what am I left with? result expected behavior cuz sometimes you don't get a result. Sometimes something changes in the system.
[1:13:15] (external link, opens in new tab) Expected behavior. Just to make things just a little more perfect, I'm going to use a special word called end. And this is my where my cursor will end up. So now I write test and I have this. So we want to test add with first param negative should throw
[1:13:45] (external link, opens in new tab) Enter and I get here. Okay. So now writing a test isn't such a great big work anymore. I was going to use worse words but this is recorded. Okay. So this is an example of how I would name something that's supposed to throw an exception. If I if it's important what's in the message, I might also add that. So one way to do it the the way Java developers used to do something like that for example is
[1:14:18] (external link, opens in new tab) Get new cal extract variable C C do add the first parameter should be a negative. So what should I send in? >> Minus one. >> Minus one. It's the closest one to zero. That's negative. So the first one that's negative. What should be the second one? >> One. >> Zero. >> Two.
[1:14:51] (external link, opens in new tab) Remember the serial killer. You see this? What do you see? >> Y is one. >> Why not Q is one? Because -1 and 1 seems very magical. - 1 and 0 seems magical cuz 0 is magical. -1 and 2 seems like it's consecutive, but it's not. -1 and 3 looks weird. I don't have a good input for this. So that's where I would use any positive. Okay. Create an local variable
[1:15:22] (external link, opens in new tab) Int any positive equals 1. Okay, that's where it makes sense. Because I only care about the first one. So, what does my assert look like? I don't care about the result because that line should throw an exception. So, what I can do is I can controll J to wrap something in a try catch. Catch exception E. Now, if I get the exception return, everything's great.
[1:15:56] (external link, opens in new tab) Because I'm expecting an exception here. And if I get all the way down here below the catch, then I would actually say assert. So that's the pattern. It's a horrible way to write a test, but that's the pattern that we would want to have. Let's run the all the tests. to past. Of course, it's passed because the code already works.
[1:16:26] (external link, opens in new tab) Let's let's see what happens if the code doesn't work. Put it in a comment. You can see that the test has failed. Expected exception (external link, opens in new tab). That's my message. That's the name of the test. But that's not how we want to write things.
[1:16:56] (external link, opens in new tab) So nunit has a special attribute called expected exception which takes in the type of the exception that you expect. And then what you do is just you remove all that crap here. And what it does is that it runs the whole test inside one big try catch block. And it does basically what we did.
[1:17:27] (external link, opens in new tab) Now if it doesn't get an exception, it fails. If it does, it passes, which is exactly it's a bit hard to think about if you've never done this before. Okay, so two passed. What happens if I don't get the exception here? Sorry. So you can see the test failing. System exception was expected.
[1:18:06] (external link, opens in new tab) There's a problem with putting this whole thing as an attribute. What do you think the problem is? and he thoughts, why wouldn't I want to use that? I used to I like using this attribute, but there's a problem. I can actually get a false positive for my test. Can you imagine how >> you get another exception? >> I get an exception before I even expecting this one. So notice that the attribute is in the whole test.
[1:18:37] (external link, opens in new tab) So maybe get new kelp throws an exception. So suddenly my test is passing even though the line that I'm testing never got executed. So that's a big problem. This is a trust issue. Okay, it's a trust issue because any line in the test can throw it. So even though you have expected exception, don't use it. Instead, what we want to do is to use Assert.Throws. Who here has experience with using lambda expressions?
[1:19:08] (external link, opens in new tab) Okay, sum. Okay, so I'm going to explain how it how it works because it's important to know to know this for this syntax. First of all, this is what it would look like if you wrote things in .NET 2. Assert throws type of exception. And the parameter assert throws takes in is a delegate.
[1:19:40] (external link, opens in new tab) So it could be a delegate to a method. For example, I can create a method like this. And this method would basically execute this line. Okay? And if the whole method threw an exception, then I would I would the test would pass. However, if I did the whole thing in a separate method, I would still need to create the instance of the calculator and still I would have the same problem as before. Any line there could throw the exception.
[1:20:10] (external link, opens in new tab) So, it's not a good idea to create your own method there. Instead, what I can do is I can create a delegate. Now, what's nice about anonymous delegates like this or anonymous methods is that I can take this line as is and even though it's in the scope of an anonymous method, it will actually know about the instances outside of this scope.
[1:20:43] (external link, opens in new tab) Okay. So now I only have one line that's being tested to throw an exception. If any other line gets throws an exception, then my test will actually fail. Let me run this first. Two passed. Let's see what happens if I don't throw the exception again.
[1:21:15] (external link, opens in new tab) Okay, failed expected system exception but was null. What happens if I throw an exception here? Throw new exception. My test actually failed. System exception, right? It doesn't care about this exception. It only cares when that line throws an exception. So how do we move from delegates to lambda expressions? You can think of lambda expressions as just a simpler way to use that or shorter shorter delegate notation.
[1:21:53] (external link, opens in new tab) ReSharper will help you with this. I'll just hold enter to lambda. Looks like this. So what does it look like? It's a method that doesn't have a name. So it doesn't have any parameters. So inside the braces, you're not going to see any parameters. After the arrow is the body of the method that can only have one line. If I wanted the method to have more than a single line, I can do this. So now it kind of looks again like a delegate.
[1:22:24] (external link, opens in new tab) Now, if I replace the whole thing with delegate, we're back. Okay. So, it's just a shorter notation and then I don't have to do the the the semicolon and all that stuff. So, some people find this more readable. I actually find the other way more readable. As long as you're consistent, it doesn't really matter which style you use. Okay, so I actually I will actually go back and ask ReSharper to use an anonymous method instead.
[1:23:01] (external link, opens in new tab) Okay, I know it's just more readable to me this way. Questions about throwing exceptions and asserts. so now that we have a basic working understanding of unit testing, we know some things about readability and about maintainability and about trust. We can maybe start talking a little bit about testdriven development.
[1:23:31] (external link, opens in new tab) Let's see if I forgot anything. Oh yeah, just just another note about tear down. Tear down. If you do find yourself using something like this tear down, remember tearown is something that gets executed after each test. So if we look at the tests after and after, well, how do you know it was after?
[1:24:01] (external link, opens in new tab) Going to have to trust me on that one. the single >> to reset singleton instances, static instances? When else would you use it? To clean out system files, stuff like that. Okay.
[1:24:32] (external link, opens in new tab) But if you're cleaning out system files, it usually means what you have is not a unit test. So, I would say that if you're looking at someone else's test and I would highly recommend that you review other people's tests to learn from and to teach and you see them using a tear down, 80% of the time it's not because they're cleaning out statics. Okay? 80% of the time is because they're cleaning out stuff and it's so it's like a tell that they're doing integration testing. So if you see a tear down most of the time it means that maybe there is one or two integration tests mixed in and again the the point is so that we can separate them.
[1:25:12] (external link, opens in new tab) Okay. So what we're going to do now when is the break just so I know. >> Oh it's 11. >> It's okay. So we still have >> Yeah. We're going to go check on it. >> Okay. So what we're going to know, we're going to talk a little bit about TDD. Let's see if I forgot anything. Test room development can actually make mean many things.
[1:25:43] (external link, opens in new tab) Some people call TDD test driven design, some people call it test-driven development. Some people say TDD, but they just mean that they have a lot of tests, so they actually write them after. So the definition really changes among many people. The definition, the way I use this word is that we write test first. We might have some design up front, although not a lot, just enough to get us started to fill in the blanks with TDD.
[1:26:14] (external link, opens in new tab) And the design could change over time. Okay. Some people will say that it's test-driven design in the purest form. If you don't have any design up front, you just start with a failing test and see what happens. Realistically, I've never done this on a real project with no design up front. So I can't say that I've succeeded with something like that. I know that some people have, but some people have not. Some people have failed with no design up front. The design they came up with was bit convoluted and stuff like that.
[1:26:46] (external link, opens in new tab) So there is no one way to try this. But we're what we're going to talk about right now is the process of driving new code given that you might have some notion of what things could look like. But it's going to be really really flowing. So we could change things as time as as as we realize that. Another thing to understand about this process is that it feels very forced. So sometimes it feels stupid. But there are three types of learnings,
[1:27:18] (external link, opens in new tab) Three levels when you learn something new. The first thing is that when you learn something new, the best way is to to copy the actions of someone else almost blindly, even if you feel stupid. The second level is to understand why you're doing the things you're doing, why they make sense. The third level is to improvise, to actually change the way you work, but keep the concepts that you already know the same. So for example, you might start out with TDD just making things work with simple rules sometimes not realizing that there is benefit behind these rules.
[1:27:53] (external link, opens in new tab) Eventually you're going to come up to to to realize that it actually helpful. And later you might say, you know what, now that I know this and I know the concepts, I don't like the way that you taught us to write tests. I like BDD better. Awesome. So then you will have the same concepts. you will just use a different way to express them. For example, the naming convention that I use is just a naming convention. The three parts of knowledge in the name still exist if you do BDD. Okay? They still exist in most other forms if you choose to test in any other way.
[1:28:26] (external link, opens in new tab) But you will improvise and you will find a way to make sure that these things exist. So that's the third level of learning. First we have to talk about the practices and what are the simple rules that we that we're looking for to make sure that we stay on the road and as we go on I will explain the benefits and the reasons and the concepts behind them so with TDD we start with a failing test that means that as far as we're concerned production code is read only we cannot change production code that is we cannot change functionality
[1:29:01] (external link, opens in new tab) We can refactor So we can rename a method but we cannot change what it does. So no functional code without a failing test. If I see you writing code and I don't see a failing test, I'm going to ask you what the hell are you doing? The second part is once you have a failing test, that is you made it compile, you saw it fail, you make the test pass without touching the test. Okay? So if you're changing the test to make it pass, you're lying to yourself. Okay? So if your test is expecting three and you're getting two and you're just changing the assert to expect two, you're doing it wrong.
[1:29:38] (external link, opens in new tab) You should not touch the test and it should magically pass as you change the production code. The third part is refactoring. So now that you have a passing test and all the other tests are passing, you can go ahead and refactor the code. What's the definition of refactoring? Refactoring is changing existing code without changing the functionality of that code. So if you rename a method, that's refactoring.
[1:30:08] (external link, opens in new tab) If you take a long method and you extract it into five private methods, that's refactoring as well. There are tools to help with it, but it's just a concept. You don't have to have a tool to do refactoring. You do refactoring because when you solve things, when you made the tests pass, you made it pass as simply as possible, which is possibly the most difficult thing you're ever going to try to attempt if you have a lot of experience. Because simplicity is something that goes against our instincts when we try to write code.
[1:30:39] (external link, opens in new tab) We try to write a code that is generic that answers a lot of possibilities that makes very little assumptions about what's supposed to happen always checks what's going to happen on its own and reacts dynamically. When we're doing TDD, I actually try to make as many assumptions as possible about the tests and make the code as dumb as possible and as simple as possible. Which is very unintuitive.
[1:31:11] (external link, opens in new tab) However, this simplicity leads to several results. It makes us think about tests that we would never have thought about before. Gets us better code coverage and it keeps our code simpler than we thought we could. Sometimes we work on such a complex piece of code when we don't need to and then it's not ready on time and then we have to move on to the next test when it could have been done already. Okay. So, because we plan for the future so much, make it better is about taking all that simplicity that might look pretty ugly now because you just solved things just trying to make things work and you make them readable.
[1:31:50] (external link, opens in new tab) You make them maintainable. Okay? You don't change the functionality though. So, the the the first part and the second part, you can actually treat yourself to two different people if you really want to think about it that way. The person writing the test is someone who really cares about quality and wants to prove that there is something wrong with the system. They want to prove that there is a feature missing. For example, the ad method is missing from the system. That's a bug. Or the ad method only handles the first parameter when dealing with negatives.
[1:32:22] (external link, opens in new tab) Doesn't handle the second parameter. That's a bug. So they write a test that fails that proves that it's a bug because if the bug didn't exist then the test would pass. The person making the test pass is a very different person. Two ways to describe it's the laziest developer in the world who needs to go home in 5 minutes. They just need to make all the tests pass in those five minutes. What is the simplest thing they could do to make all of them pass?
[1:32:55] (external link, opens in new tab) Or you can think about it, it's like your kid brother or your your your 11year-old nephew who just learned programming. How would they solve it? Okay, the simplest way possible in a very naive way. So two different people making the test and making it pass is what's required in a way. It's like a mind shift and that's a very tough thing to to do but it's part of the learning TDD.
[1:33:27] (external link, opens in new tab) Okay. So what we're going to do is we're going to add a feature using testdriven development. The feature we're going to add is a missing feature because right now we're only taking care of the first parameter. So what we want to do is we want to add the feature for the second parameter. The only difference right now is that we all the code is already built for us. So all we need is to create a failing test that recreates the bug.
[1:33:58] (external link, opens in new tab) And most of the time what happens is especially if you work on an existing project. You find the bug, you want to do something with TDD. The first thing you want to do is you try to recreate the bug with a failing test. That's a good way to learn TDD because it's much easier to know that you have a bug that you actually have to prove and write a failing test than to start a new feature. So let's see what it feels like. So we know that only a is taken care of. So what we're going to do is we're going to write another test
[1:34:29] (external link, opens in new tab) Unit add the scenario is second param negative and the expected behavior is >> for us. Okay. So get new calc extract the variable C. Assert throws an exception
[1:35:01] (external link, opens in new tab) Control shift space to help me generate this stuff. And I'm gonna say C do add any positive and minus one. Okay. And yeah.
[1:35:40] (external link, opens in new tab) Is it better to declare it here or before the assert throws? Any thoughts? >> Before or? >> Why? >> Because that can throw an exception. >> A range. >> Can declaring an in throw an exception? What do you think? >> Usually no. Although it feels better, it feels more right to do it here. So I would do it here as well. Although if I saw this there, I wouldn't have that such a that big a problem. I'm trying to be practical, not blindly look at this. But if I saw something that could throw an exception, anything but declaring something which is like a value object, I would have a problem with that.
[1:36:17] (external link, opens in new tab) >> But is it the declaration of any positive part of the range? >> It could be part of the range, but in this case, it just looks like it. But in this case, it's part of the assert because we're using it as part of the app. Sorry, we're using it in the app part. Assert throws in the only place where the act and the assert are basically in the same call. Okay. Any other time I would separate them. Okay. >> I'm actually also just thinking that just for readability, it's easier to keep the delegate method as small as possible because it's like harder to follow.
[1:36:54] (external link, opens in new tab) >> That's true. That's true. I I agree with that fully. You could do this and re remember I have no problem with looking at the test and saying maybe this, maybe that, like tweaking it, making it just a bit more readable here. It's good to spend time on these things. Okay? It's this kind of attention to detail, the things that you care about so much that you just told me that makes those tests readable and trustworthy (external link, opens in new tab). Okay? Because you really should care about all those little things.
[1:37:24] (external link, opens in new tab) So, it's really good to hear these. >> Why do you have throw and throws? >> You bastard. >> That's just a small one. >> That's true. I actually noticed this when I wrote it. I said, "Shouldn't be throw. It should be throws." Awesome. Good. Good. Good catch. What else? Okay, let's just run this. It should fail. Now, if it didn't fail, what would that mean? >> You failed.
[1:37:55] (external link, opens in new tab) I haven't failed. I actually succeeded if it didn't fail. Why? Because I found the bug either in my understanding of what I'm trying to test or in the test. Okay? So, this is actually a win for me if this did not fail first. If it failed first, it's also a win for me because now I get to fix it. Okay? Either way, it's a good thing because doing the failure first gets me an understanding of that I'm actually understanding the problem. Okay, a lot of times we jump into something without really understanding what it is we're trying to solve.
[1:38:30] (external link, opens in new tab) Okay, so we expected blah but was not. So now we need to go ahead and jump. Now the code has become read write not read only. Go ahead and jump into that. Let's see if F12 will work. Nope. What's the simplest way for me to make all the tests pass? Now I'm the world's laziest developer. What do you think? What What would your nephew do?
[1:39:00] (external link, opens in new tab) What would Jesus do, etc. All that stuff. What would I do? What do you think? Can I just change it to B? I can't because another test will fail. And I know this. If I didn't know this, I might do it and see what happens. That's what one of the nice things about tests. Maybe I would realize that the first parameter doesn't need to be negative anymore. Something in the system changed. Who cares? But I can try. I can I can do a what if. Say, what if I do this?
[1:39:30] (external link, opens in new tab) What if I move this to the first line? Okay, nothing happened. Still works. Awesome. So what should I do? >> I know it seems obvious. I know it seems obvious. Or >> Hold on. I'm sure it's here somewhere. >> It's not. >> It's right there. >> It's horrible.
[1:40:07] (external link, opens in new tab) Okay, close. Okay, so that was a really easy fix and now we fixed the bug. So that's that's what it looks like in the most happiest path. We didn't need to change anything about our habits and sometimes it will feel like that. Okay. But let's try to add a feature for this where it will also remember the last result. Okay.
[1:40:38] (external link, opens in new tab) So if I add something and I get back a three, I want to be able to have some kind of property or a method that tells me what was the last result that was returned. Sounds good. So let's add that using TDD. So first of all, I cannot change this code anymore. All my tests are passive. Actually, I would say it's logical step because I mean what I'm thinking now is maintainability because I'm thinking all of a sudden add is a very bad name this method.
[1:41:09] (external link, opens in new tab) >> It should be add positive numbers or something. >> Okay, >> how do I maintain the tests now? Because I do want to rename the method. So that's where the refactoring tools come in. If you want to rename this method, you just press F2, say add positives, you run the tests. >> Now it should compile. >> Oh, cuz it's smart enough to change all the colors as well. >> Okay. So it's not that bad.
[1:41:39] (external link, opens in new tab) Okay. So you can see that add positives. >> Yeah, but I was I'm thinking yeah, that matter. But now the test methods shouldn't be add second negative. >> That's true. >> Add positive. Yes. So now we're in. >> That's true. But in that case, you can go ahead and just find and replace everything that's add underscore. That's where the naming convention also helps because you always have the underscore between those two. And it helps because it's structured. So for example, other things that this structure brings, you want to look at all the tests.
[1:42:11] (external link, opens in new tab) You see them all here and they're structured by the name. So you see all the tests for a specific method or unit structured by the name as well. So you can look for it this way. It's just easier. I haven't met cases where it was that big of a problem. I have met cases where you broke a whole bunch of scenarios because you you removed the whole feature from the system and that's where you actually have to start working just remove some of the tests because they don't matter anymore. But that's that's something you can live with because it doesn't happen that often.
[1:42:50] (external link, opens in new tab) Okay, so we wanted to add a feature that remembers the last the last numbers that were the last result that was added. So if I were to get a design up front, then I would know the name of this method or a property. If I didn't, I can just make it up. And that's one of the nice thing. If it's small enough, then I don't need a design for it. I can just use TDD to design something that I would want to use. So here's an example test.
[1:43:20] (external link, opens in new tab) What is the unit of work if I want to add something that remembers the last value that was replied or resulted? Is it the add method? What do you think? Well, it can no longer just be the ad method because it has to be more than a single method. Okay. But what we can do is we can name the unit or the unit of work in this case into
[1:43:51] (external link, opens in new tab) Result memory. So it's no longer the name of a method, it's the name of a feature in the system. Okay? You can choose to name it however you want as long as you're consistent. that once it's a feature then the feature is named in a specific way. For me I can just name it result memory and for me it makes sense. For some people it would make sense to name it as the name of the method and some other extra stuff after it. But that is between you and your project team.
[1:44:24] (external link, opens in new tab) So result memory calling add. Notice that now I have to say calling add result is saved for later. Okay, I'm not saying where it's saved yet. I can I can say this if I have a design. So for example, I can say result is saved in last result property or something like that. So I can do that as well.
[1:44:55] (external link, opens in new tab) So the naming can be kind of elastic. You can change it based on what you know or don't know yet. So get new cal extract variable. See you can see that it's not a big deal. I try not to copy tests from one test to the other. Copying tests results a lot of times in really messy tests because you don't know what to remove or you forget because you're new to this stuff. So you end up with more mess. So it's better to to learn to make yourself more productive.
[1:45:26] (external link, opens in new tab) Maybe add a snippet for the asserts etc. You can add a snippet for creating a calculator. Right? No one prevents you from doing that. So C do add positives. Do we care about the positives? Do we care which positives it is? Then we just use we care only for the assert. But the do the positives mean anything in this in this scenario?
[1:45:56] (external link, opens in new tab) Doesn't mean doesn't say which positives. So it's any positive. So we'll use the default values. Okay. I don't care about the result. Actually I do care about the result. Maybe there are se several ways I can check this. I can assert that the result is equal to what's happened there. I can check that what h what what is in that other property is equal to three. two good ways. Both of them are pretty readable. I would love to know what you think about both of them.
[1:46:26] (external link, opens in new tab) But here's the part where I say if I could do anything with the system, how would I want to assert something like that? Cuz it doesn't exist yet. So I'm playing God right now. Okay, I can do whatever I need. So assert as if everything you need is already there. So it might not even compile. Assert are equal let's say three for now
[1:46:56] (external link, opens in new tab) And C dot last result. Okay. So what I'm doing here, I'm not even going to make it compile first. I'm just looking at this test right now. Sometimes I'm going to get the parts that don't compile at the beginning, the range part.
[1:47:29] (external link, opens in new tab) Sometime I'm going to get them in the assert. Sometimes I might have multiple things that don't compile. The important thing here is that I'm using something that doesn't exist yet. And I'm not going to make the test compile until I fully finish writing the whole test. Okay? So I might have several red things here, but I'm not going to bother making them compile until I'm happy with the way this test is written because this is a stage that I get to say, you know what, let's result is not a really good name or maybe so it should be a method
[1:48:00] (external link, opens in new tab) That takes a parameter maybe I'm missing something here and changing it while it doesn't exist yet has zero cost because it doesn't exist yet. Okay. So what we want is here to define what is success. Success is being able to do this, do that and all that stuff and getting better result. Now that I've defined success and I'm happy with the way this test is written and I'm happy with the way the API looks and feels then I can make it compile and see it fail.
[1:48:30] (external link, opens in new tab) Okay, this is a design phase because in this design suddenly I am the user of my new API. So I get to feel what it's like to use it. So I get to change it if it's not usable. For example, if I have parameter orders and I say, you know what, the big number should be first and the the lower number should be second. Feels more natural to me. And I could change it if that method didn't exist yet. So it's a design issue. If I'm happy with the way the design looks,
[1:49:01] (external link, opens in new tab) I just alt enter to create it. So I can choose it as a property. I can choose it as a field. I'm assuming it's going to be a property. Make it an auto property. Okay, now that it's compiling, I'm going to make it fail. That is, it should fail by default cuz I haven't added any code to make it not fail. Okay, so expected three but was zero. So, we've just passed
[1:49:33] (external link, opens in new tab) The first stage. Now, we get to be the world's laziest programmer. We have 5 minutes to to go to lunch. What is it that we want to do? >> Make the test work. >> Yeah, we want to make all the tests work. So, how can we make them work in the simplest way possible?
[1:50:05] (external link, opens in new tab) >> It's just hard code the results. >> Are you sure? >> I know you're using one and two for the whole. >> Yeah, that's true. Is that something you can live with? >> Yeah, just for you know just for the just just to entertain you. I'm going to do that. How do I return it from a get method? Let's turn it into a property with a backing field. Hey, you know what? Like this. >> Yeah.
[1:50:37] (external link, opens in new tab) >> Awesome. >> World's best programmer. However, if I run all the tests. Okay, I guess the feature is working. >> There I fixed it. There's actually a blog of people who fix things this way. So this is awesome. Why is this awesome? First of all, it's awesome because I got to be the world's laziest developer for a second.
[1:51:08] (external link, opens in new tab) That felt good. Second is that this is horrible. And it's horrible, but I cannot change it because I don't have any failing test unfortunately. So if I want to change this horrible thing, I need to have a failing test first because that's the first rule of TDD, right? So this is read only. I wish I could change it, but you need to prove to me that something's wrong. So now I have to write another test that proves to me that I need to change it.
[1:51:42] (external link, opens in new tab) Okay, first rule of TDD. Okay, so if I cannot change it, what should I do? >> Create a test. create a new test. And what would this new test do? Exactly the same thing but with a different input. So it would expect a different value. So if I'm going to write the same test twice and it's the same test just different inputs and outputs, what I can do is I can use another feature of nunit which is called test case.
[1:52:15] (external link, opens in new tab) So instead of test I'm going to change it into test case and test case accepts parameters which get sent in as parameters into the test so that my test case right now is one and two and it expects three. Where did I get the one and two? I'm going to extract a parameter here. We'll call it a. And you can see a in the parameter list now.
[1:52:46] (external link, opens in new tab) Call this B and this is going to be the expected. So now we just wrote the same test only now it's parameterized. So the first value here goes to the first parameter. Second value goes to the second parameter. And what's cool about this is that I can have multiple attributes on the same test. So I can do this one and three equals four. Why did I use one and three? because I can see the pattern one and two one and three.
[1:53:18] (external link, opens in new tab) So my brain sees this and said okay that's the next logical thing. I can also say if I did one and two and then two and two it's the same input. So I don't want to use that. Now if I run all the tests actually now I have two tests running here. So I say expected four but was three and you can see also tells me which of those cases were broken. If you use ReSharper it tells you that there are two tests here.
[1:53:48] (external link, opens in new tab) Okay, so now I have a failing test that tells me that what I did was a bad assumption because that's what essentially I did. My assumption here was that I always need to return three because I'm always going to get one and two. Bad assumption. So, what I'm going to do is I'm going to undo the last result and I'm going to do the simplest thing could possibly work to make all the tests pass.
[1:54:22] (external link, opens in new tab) Last result equals a plus b. That make sense? The ReSharper tells me, "Dude, this this will never work. So, you might as well do this." Now, I could refactor to return last result, but that's too many actions to do. If I can just do it in one action, why not? Let's run all the tests.
[1:54:52] (external link, opens in new tab) They will pass. Now, I get to refactor. I say, you know what? That's not good enough. Let's just return last result here. So, return last result. rerun all the tests, make sure I didn't break anything. Now, this is kind of the beginning of where TDD might might take us. It's a very simple method with very simple inputs and outputs, but it starts to I I I'm hoping you get to start to understand what it feels like to do a little bit of it.
[1:55:27] (external link, opens in new tab) Any questions so far? >> Nothing. Okay. So, let's take >> like 15 minutes. >> Yeah, 15 minute break. >> there's actually this pika in the kitchen. >> Mhm. >> Pika pika.