 It's summer, so it must be time for Phil Jepixie to come on Toolbox for another series. This year, we're really going to test your patience. Hi, welcome to Visual Studio Toolbox. I'm your host, Robert Green, and joining me today is Phil Jepixie. Hey, Phil. Hey, Robert. This is now becoming one of my favorite summer traditions. Absolutely. You come in and we do a series of episodes. Yeah, look forward to it every year. Last year was design patterns. Yeah, we got nine of them, I think. Nine of them, I think, and great episodes and a lot of great conversations. This year, we're doing unit testing. We are. Not only how, but why. Yep. Right? Focusing, I think, more on the how in the later episodes. But I think in this one, we want to set the stage of why it matters. Yeah. I hope we don't just spend too much time explaining why it matters, and that we get to focus more on the mechanics of doing it. Okay. Particularly, we're going to look at, well, Kendra Havens and I did an episode a few weeks back on unit testing tools inside Visual Studio, but we're going to go more about how you do the unit testing itself. We'll look at x-unit and n-unit. Mock you. Mock you, and I also want to talk about how you get started doing more with unit testing, given that in all likelihood, you've got an existing code base, right? So we want to talk about that kind of stuff, and as always, want your feedback. Last year, we had some great conversations with folks not only who liked what we said, but some folks who disagreed with what we said, which is awesome. We expect that. We encourage that. Look forward to the conversation on unit testing. It's something that's fascinating with the patterns series. This isn't mechanics where everybody does exactly the same way. Right. It's more of an art than a science. So I am not saying that the way that I do it is the ultimate right way. It's the right way for me. Right. And that's how I've been doing. I've been doing unit testing ever since, really ever since any unit came out with .NET, and manual testing before that. So for people who maybe feel like they're not doing enough testing, that's what this series is for. If you're absolutely totally doing it and you just want to watch to figure out how you can maybe improve or how you can suggest that we do it better, that's great. But we're going to focus on, I don't write enough unit tests. I don't think anybody does. Yeah. I mean, I mandate that my teams write unit tests around their things, and I mandate that I write unit tests around my code, and it's still not enough. Right. Things come up. You have deadlines, you have, you know, it's just software development, right? Things come up. Right. And then there's always the, are you spending time writing features, or are you spending time writing tests? Right. There's, it's true, but it's not true, this dichotomy of the time, the feature work versus other. Right. And, you know, are you being paid to write the feature, are you paid to write the test, and you've got something due Friday at noon, it's now Friday at 10, you finally got the feature working, right? What are you going to do in your last two hours? So I would argue that both of the statements are wrong, what you're getting paid to do is deliver value through quality software. Right. Right. And quality software is tested. It is working. Yep. One thing to stress though is that with unit testing, and what we do as developers, we're actually testing the code to make sure it behaves as we expected to. Right. Which might be completely different than what the customer is expecting. Sure. You're not testing the feature, you're not testing that the feature meets the spec or meets what the customer thought, right? You are testing the code that you wrote, and then there's an assumption that you have coded to spec. But you wrote particular code that's going to do a particular thing, and that's what you're testing. Yeah. And I would, I would also argue that unit tests are part of your production code, it just doesn't get released. Right. Sure. So I know you've done some episodes with IntelliTest and some of those things. Sorry, I'm unblinding myself from some cables right now. I feel like I'm a bit of a kuzovine. One of the biggest things that I think people miss on in testing is scenarios, right? And when we were on last, we did an episode, kind of a whirlwind fly through episode on unit testing. We mentioned the Calculator app, right? Right, you know. And so let's switch over to code and look at some things and just to get set up. And I've got a bunch of projects here. These will be linked to in the show notes. They're all in my GitHub repo. But I've got this project here, Channel 9 kickoff. And I just want to show you what I have installed. This is my standard load, if you will, for unit testing. Now this is an ASP.NET core class library. It could also be full.NET framework. These things work in both. So we have MOQ, which is a mocking framework. And we're going to talk in great detail in a later episode on that, but I want to give a little show and tell of that. So again, the idea of mox is that if you're calling a service or you're calling a database or something, you want to test the code without having to test the connection to the database or without having to literally connect to the database in your test. So the nice thing about mox is you can say, assume I called the database or the service and it returned to X. Now, what do you do with that? And there's a couple reasons for that. First and foremost, it brings code isolation. So I am literally only testing my system under test. If I'm expecting a certain behavior from my code, let's say I've got a repository, it brings back a list of customers. And I want to test, how do I handle the controller if I get no customers back? Well, if you're actually connecting to the database, then you have to delete all the customers in the database to run that test. Well, now I want to test what happens if I bring back a million customers. Well, now I have to add a million customers in the database to run that test. And now I can't have both tests passing, without a bunch of work. So we want that isolation. The other thing is flat out performance. If I'm not testing the actual data access layer, but I'm testing something that depends on it, connecting to a database and doing those retrieves or populating records takes time. Yeah. And if you've got a thousand unit tests, you don't want to use up all your Azure minutes just running the test. Yeah, also it takes resources, it's spinning the meter. Yeah, absolutely. So we're going to talk about that. X unit is a framework that I prefer to use these days. James Newkirk and Brad Wilson were instrumental in N unit. And this is, for lack of a better term, I'm sure somebody's going to put something in the show notes and not like the way I say this, but I think it's a next generation of unit testing libraries. There is a lot of things in N unit that work great at the time and I was a big fan of N unit for the longest time, but we've learned, we've evolved how we do testing. Some of the changes, for example, in N unit, you can do an expected exception attribute, which says anywhere in this test, if this exception gets thrown, then the test passes. Well, what if that exception is getting thrown in your range part of your test, you never call the system under test. Should that test really pass? The answer is no, right? Cause we wanted to actually be thrown at the right time. So there's some things that were dropped out of or not ported, sorry, rewritten in X unit that we've just learned over time that have become kind of anti-patterns in the way we use to do things. And this is not knocking the creditors then unit. We learn, we evolve, we adapt, right? And then the X unit.runner.visualstudio just enables us to run X unit tests in Visual Studio through the test window. And there's also team city support and resharper support and things like that. Today I'm going to be showing the Visual Studio test runner, because I know we want to focus on things you don't have to pay extra for. Plus it is Visual Studio Toolbox. Well, yeah, good point. Thank you for correcting me. So we got a couple of things in here. I've got some existing code. And I know one thing that we wanted to talk about is how do you get started? How do you be more effective in writing your unit tests and what should you be testing? But also you've got legacy code, right? And legacy code as in code that you're working on that isn't under test. And how do you deal with that? Well, the same way you need an elephant to use a cliche is one byte at a time. I don't wish on anyone the requirement to go, okay, you've been developing for six months, now everybody stop moving the ball forward and let's write tests for six months. That doesn't do any benefit. So we'll talk more about the patterns about that. But I know that one thing you wanted to talk about is scenarios and how to cover different scenarios. An N unit and MB unit and X unit are all custom built to handle this. MS test, it's a little more difficult so we're not gonna cover that today. So let's say that we're gonna use a canonical example of the calculator because it's easy. And so we're gonna do test driven development. We're gonna write the test first to use that to drive out the API in the code. So let's say I have a test that is called should add to numbers. And that's going to be, let's do our, do a little old school and explicitly call it out. When we talk about unit tests and we really have a range act and assert. So a range is going to set up anything we need to run this test. The assertion is we're executing code on the system under test. And then the asserts are making sure that it behave the way we expect it to behave. You can only have one act, that's pretty much canon. You don't wanna have more than one action going on that you're testing. And that's, I think our listeners and viewers will agree that's accepted. There is some discussion in the world as to whether we should have more than one assert. I am of the mind that you can have as many asserts as you need to prove that your action was correct. But we will have some people who will probably chime in on the boards and say you should only have one assert. I'm not saying that's wrong, I'm just saying that's not how I do it. So let's arrange, and usually this is setting up any variables we'll need. We'll get more involved when we start talking about mockings. But let's say int add end one equals five and int add end two equals 10. And then we have to create a new instance of the calculator, and we'll call it system under test or SUT. And that's new calculator, which doesn't exist. So we're gonna use the Visual Studio Refactoring to actually create and type calculator. And we would do it in a separate file, but just for demonstration purposes, we'll keep it in the same. And then we're gonna act. So what we want to do is say var result equals SUT.add and we're gonna pass in add end one and add end two. We don't have an act method or an add method, so we're going to create that as well. And it's gonna return an int and it takes two ints. And then we would assert that equal, we have expected, which is 15, and that equals the results. Now if we run this right now, the test will fail because we're gonna get an exception. And if we're doing strict TDD, we would run the test. This is really the obvious implementation. We don't have to run the test at this point. But what we want to do is write the bare minimum amount of code to make the test pass. So we would return 15. Okay. Right? Because that'll make the test pass, correct? Let's... So let's stop here for one second and let's chat about what we're doing. We're, test-driven development says you write the test first and then you fill in the actual functionality. So at this point, you haven't actually written the add method. Correct. Well, I have, but I've hard-coded it. Right, so you've hard-coded it, but that's not anything resembling what the real add method's gonna look like. Correct. And you've got a passing test because you hard-coded it to be equal to what your bogus add method returns. Yes. So I'm gonna play a little bit of devil's advocate here. What have you accomplished? What I've accomplished is I've flushed out an API. I have an add method that takes two numbers. That takes two numbers. Don't you already know what this API is gonna do? I do because it's a very simple or what we would call the obvious implementation. But what if it was something much more complicated and took in a couple interfaces and the logger and had call-outs to other things and we weren't sure what it was gonna do. So that's really, we need to, we consider this a cata. You ever studied martial arts? Wax on, wax off. So you repeat the same thing time and time again until you get good at it and then you start combining things. So in real life, I would never say return 15 because this is so obvious. I would say return add end, add end one plus add end two because it's obvious that's what add should be. But there are many times when you're doing this where it's not obvious. What about the cosine function? Or better yet, I did time and time, felt like it. I worked in insurance for a while and we were working with actuary tables. And that really did come, you're taking 27 parameters into this annuity calculator and I took the first row and I'm passing all these things in and the last cell in that row says this is what it should return. So I literally hard coded it to that just to make sure I had the API right? And I was passing in all the right things. So you can, sir, is it safe to say that the test it's actually part of the specking? It is. Okay. It is helping you define the API. Okay, so there's the speck in terms of the feature and now what you're basically doing is specking the code you're going to write. You have an add method that's going to take two parameters in and then you're specking what it's supposed to return and the various scenarios of what it could return and how it could act. It's really how it could act, not so much what it returns. You're specking how this method's going to act. What happens if you pass in zeros, right? It's a divide. What happens if one of these is a zero? What happens if something doesn't get specified correctly? What happens if they do get specified correctly? Is this thing going to do the thing it needs to do? So you can kind of think of the test you're writing as the speck of the code you're ultimately going to write and you do it kind of in parallel. Was that fair? That is fair, although you jumped the gun a little bit because we don't have any scenarios in here yet. Right. Well, we have one scenario. Right. We have 10 and five. Okay. We need to add more scenarios to make it a complete test. But I'm just trying to answer the question. But absolutely, what you said is correct. Your initial assumption might be, well, you're just writing a bunch of stuff for something you haven't written yet. Why are you doing that? And the answer could be because you're specking, you're helping to speck what you're ultimately going to write. And you're also writing your unit test while you do it. Right. And so a side effect benefit is, now I have tests to cover my code. Right. Right, but we're really driving out. And maybe divide would be a better one to do. Let's switch it. Do a little clipboard inheritance and we'll say should divide two numbers. I don't know my math, but one of them is a divisor. Right. Okay. And then we want to build out that. And this is actually going to return add end divided by divisor. And this should be, my math is right, point five. So let's look here and test explorer and we have all kinds of tests in here already. Well, let's run these, see how far off the mark I am. That's something else I like using unit testing for is sometimes we make assumptions. And yep, look at failed. So the result should be, why would that be zero? Because it's an int, right? So I've already failed in driving out the API. Right. Because I'm returning an int. So I need to change this to return, we'll just say decimal. Okay. Right. Possible loss of fraction. I'm okay with that because I don't care. And then we'll run the test again. If you will run it from here with the visual studio. Right. And so we're finding all kinds of failures because I don't know how to do math. What am I doing wrong? What's it saying? It succeeded to click on this again. Don't make me run, I'm gonna run it this way. All right, so we showed how to not drive out an API. But more importantly, I think this is meaningful in that we're on camera, it's live. I did, well, I went off script with this and so I screwed up a little bit. But as we're trying to figure out how to write a divide function, what we learned is that rapid feedback show this, we're doing it wrong. As opposed to saying, oh, I can write divide, that's simple, right? I just do add in divided by divisor and I go on and later on in the program, they're expecting 27 from an actuary table. We're returning four. Now you have to chase always through the program. Where did I fail? Well, the fundamental problem is, I don't know how to do divide and C sharp. So we'll just, these are not the droids you're looking for and we'll skip over that, but it does show how to drive out the API. I initially said an int in there, but then it came back and said, no, you're wrong. And I learned that early, it's all about the rapid feedback. Let's talk about scenarios. What we can do in N unit, NB unit and X unit, the methods are different, is we can do scenario-driven testing. And in X unit, we do that by writing a theory and then we can do this multiple ways and we'll show this in the next episode to do this, but the simplest way is with an inline data and it takes a list of params. And so we will say add in one. Actually, it takes the actual numbers. So we're gonna replace it with five, 10 and 15. And then what that does to our test is it now parameterizes the test. So add in one, int, add in two, and then int expected. So we don't need these anymore. Our arrange is actually done through the inline data, the first part of the arrange. And now when we run this test, we see that it passes. Okay. So now instead of having to clipboard inherit a whole bunch of tests to add different scenarios into it, some of the ones that you said is what if they didn't put a number in? What if we said that's null? Well, now we have a problem in that we have to make that nullable. Now we won't compile because it's not accepting a nullable int. So now we're driving the API. So we go down here to our add function and we're gonna make that one nullable. And now we have a problem in that adding those two numbers doesn't work. So now we have to fix our code. So we've solved that problem of, hey, if you're not adding, again, from a logical standpoint as a developer, we would never think of doing that. If I'm adding two numbers, that means I have two numbers. But maybe they just type in one number and hit Enter. So you're actually kind of forcing yourself to do this type of design up front, right? How is this going to be used? And perhaps more importantly, how is this going to be misused, right? And then ensuring that you cover that in the code. So rather than write the 90% case first and then having to go back and say, oh, well, what if somebody does this? Oh, what if somebody does that? Oh, what if somebody does this? You're getting all of that taken care of more up front so that it's covered, which then early on, not last minute at deadline, you're now thinking about the edge cases or the 10% of scenarios where all your errors are going to come from and you're handling that up front. So, yeah, it's more time up front, but it's then paying off big time down the road because you've already got it covered. Correct. And built into the API. So we can fix this by using the Elvis operator and doing it on both and we're like, okay, if they can pass in one that's null, they can probably pass in two that's null, right? So let's just add in the Elvis operator and see if that fixes, of course, our expectations have to be correct and run this. And what we will see in the test runner is that actually shows up as two different tests. So we expected 15, the actual was five, we passed in five and 10 and 15 and 10. So what I am showing here is again, my lack of knowledge around C-sharp by doing this wrong. So I had an assumption that adding in question mark, question mark would fix the problem. However, it didn't. So the problem's not in the assert because you're expecting 15? The problem is in the assert because I didn't change the test. That's a joy of life coding. So we've got the one with the null working, but the one with the five and the 10 bringing back 15 didn't work. So then we would go through and debug why it doesn't work. I mean, it doesn't matter whether or not this example works. The point is that as we add these different scenarios, we're flushing out the code to make sure that it returns the right thing. So obviously we want to fix it, but let's talk about another scenario that as developers, we would probably never think of doing. But I was putting question marks there. What happens if we add int dot max value and 10? Right. Right? We don't know what the expectation is, so we're gonna put something in there anyway just to make it fail. And I'm actually going to get rid of this test. Get rid of the divide so that we aren't occluding what we're trying to show. What happens when we run this? We know that's gonna be a failing test. Right. And max value plus 10 does not equal five. It's infinity plus 10. Well, actually I think it's int dot min value plus four. Right, it's not exactly infinity, but. Right, so we ended up with that. Right, so now we can start with very little effort on our own, adding in all these different scenarios to really flush out. And more often than not, you're gonna put in every scenario that you think of, and then users will get ahold of it, and they're gonna do something different, and you might get a bug report saying, hey, the calculator didn't work. Right. Well, how do you re-pro the problem? Well, we just started hitting numbers and it ended up being int dot max value plus int dot max value, and we got something weird. Right. Well, now we can start putting checks in there. Well, maybe we don't allow them to add a number bigger than a million. Right. Or we then find out what the reason is, we update the code to make the test pass. Once the test passes, we put that failing condition in as we have here, and then we flush out to code to make a pass. Yep. Okay. And then it drives the conversation of, do you handle that in the calculator or the UI? Right. Right. You might, as guidance have in the UI, make sure you don't enable this, but if someone does, then the calculator needs to handle it somehow. So you're really, again, you're driving, it's less, you know what code you're going to write, ultimately, but you're really driving much deeper thinking about how the whole system works and heading off a lot of issues at the pass because you've covered them and you've thought to cover them ahead of time. So again, one of the benefits of doing it this way is you're not only testing the code you write, but you're helping yourself design a much more robust system as you're writing the code. Correct. And so let's change the tester on a little bit and it comes back from the business that says, they have to put in two numbers. We can't allow them to do null, but they might put in a null, but we have to handle that exceptionally. So I've just changed the code and I'm doing this a little backwards. I'm doing the test eventual development here. I changed it to throw an exception if either of these are null. So then what we want to do is add a different test to handle those exceptional cases. And let's get rid of those. And so if it's null, we actually want this system to throw an exception. So I have my new calculator. I'm gonna call add, but I want to handle this a little differently and I'm gonna combine my act and my assert. The computer wakes up. And we have a special case where we can say assert.throws and we can specify the type. And that's an argument null exception. We're gonna time? No, we're at 30 minutes. Okay. So we will finish up here quickly and then prep for the next episode should not add nulls. Make that more meaningful. And then we run this test. If the exception gets thrown, then the test passes and it passes. So we had the exception. Which again, drives the decision of when do you want APIs to purposely throw an exception versus just return in some error code of negative one, if it's a connection issue or whatever. You probably never want calculators returning numbers because that's what they're supposed to do. Numbers as an error code. Right. So here you probably do want it to blow up and then the person calling it gets an expected exception because they violated rules. But again, you're driving that discussion. And that becomes a business decision to how you want to handle that. Cool. So here's our little intro. Let's get ready and talk about XUnit in great detail. All right, so that'll be in part two. Thank you for listening and watching. Watching. Hopefully people are watching. They're probably heads down working with a plane in the background. All right, we'll see you next time on Visual Studio Toolbox.