 Previously on Visual Studio Toolbox, we had a number of episodes on unit testing. Today, we're going to tie it all together. Hi, welcome to Visual Studio Toolbox. I'm your host, Robert Green, and joining me for the last in our series on unit testing is Filger Pixie. Hey, Robert. Well, it's been awesome so far. Yeah. Now, let's say last for now, because next time I'm out here in Redmond, we'll probably do some more. Yeah, but just because we do, what is this? Five? This is the fourth. This is the fourth? Fifth. I can't count. We did an introduction. We did X unit, mock you. And then this is the fourth. So this is the fourth. So just because we say it's a four-part series doesn't mean we can't do a part five later on. Right. We're doing these all together, so that's why I can't count. Yeah. But this is episode four, and what we're going to do in this one. A new hope. Is kind of, is put it all together and also address the issue of what do you do if you already have existing code? And I know it's called legacy code, but it's basically existing code. If you're starting from scratch, yes, you can do test-driven development and build up your unit tests before you write any code at all. But in all likelihood, viewers have existing systems. So they're going to hope that we're not recommending that you do this for every single method that you've already written in these systems that are six months, two years, five years old. We're not, you're not recommending that, I hope. No, they got to stop production and just work on unit tests for the next six years. Yeah, exactly. No, I'm kidding. I jest. No, that would be brutal. And the business would never go for it. Right. You know, it's hard enough to sell. I'm not paying you to write twice as much code type things. What we have to do is we have to be a little more selective. Technical debt, unlike mortgage debt, doesn't have to be paid back. So you could have a large system in place. Explain technical debt. All right, so technical debt is basically code that isn't as good as it could be, right? So maybe I've got some magic strings in there. Maybe I've hard coded some things. You know, maybe I didn't follow solid well enough and I've got some methods that are really long. You should be shorter. OK. Right. And it's what we call technical debt because it could be written better and it's going to cause problems potentially with performance. It might cause problems with maintenance, make it harder to maintain, and upkeeping, things like that. OK. But my mic, sorry. But the biggest difference, again, between technical debt and, for example, mortgage debt is you don't have to pay technical debt back, right? You've got an app. It's production. It's working. It's been tested by the most commonly used testing framework in the world, which, of course, is users, right? And nobody's complained, right? Should you go back in and start refactoring? I'd say absolutely not, right? Because anytime you change code, you're just breaking it, right? So do you need to write unit tests around that stuff? No. The bigger issue is you've got a 300-line method, probably called do-it, with 47 parameters, and either a feature change or a bug request comes in. And you realize that right in the middle of this humongous method is the line of code you have to change. And now you go, oh, I should have gone for coffee, right? And somebody else would have taken this card. And there's no unit tests in place. So we're not trying to drive out the design in a TDD manner. We're trying to make sure we don't introduce side effect bugs. Because there's all this code in there. We're going to change this one line of code. But Butterfly flaps its wings in Portland, and the price of milk goes up in Ohio type things, right? It seems unrelated, but it's that chain reaction cause and effect. So we really want to be very careful and considerate about how we go about this. I don't have a big pile of legacy code to show you, but we're going to pretty much use the same samples we've used before to kind of demonstrate how we would go through this process, right? So what I have is a very simple fake controller. It's not a real ace pen controller. And it takes a couple of objects in through constructor injection. And I've got this get customer action method here. And let's say that we need to change to get customer. It's not working quite right. And it's really long. We're going to do some pretend here, right? The first thing that we need to do is make sure that we have this method that we're about to change under test. We want to make sure that we've tested using X unit and the inline data and mock you to make sure that we have a whole suite of tests that demonstrate the behavior as it works now, right? So let's start with a new class here. And we'll say add new class. And we just do class. Change the name later. Make it public class. And should let's call it legacy test controller tests. I know, terrible name. But the three hardest things in software development are what are they? Naming conventions and off by one errors, right? Right. So we're going to do a theory. Wait, what was that third one? Walk right into it. Actually, you can tell the audience in the normally when I have like today I had an 8 AM session at VS Live. And the jokes don't land as well because people are still pretty groggy. And once they get the coffee in them, it starts going a little better. You just keep telling yourself that that's why your jokes fail. That is exactly why I'm telling myself. I'm a legend in my own mind. No, just kidding. Should test, get customer method. And I'm really just, you know, the names don't really matter. We're going to have some inline data, which we don't know what it is yet. Now, we need to arrange the mock. Let's look at what the method is doing. And sometimes, just so I don't have to bounce back and forth as much, I might do this to just give me a reference. So I know I need two mocks. So let's just. That's a neat little trick I didn't know you could do. What did you just do with that? I copied the method, put it in the comment. That's the forward slash star. So this is one of the C-sharp comment things, right? This has probably been in there all along, right? So I just pasted it right in the middle between those two. So I have a reference to it, and then I can delete it when I'm done writing my test. Nice, nice tip. All right, so. Introduced in Visual Studio, what, 2005 or something? I think it's been around since 2001. All right, it's an old C comment. So anyway, let's go old school a little bit and go back to basics and say arrange, act, assert. Well, we also have, in order to just create a controller, we've got some things we have to set up. Now, we talked about in the MockU show that we did, which I think was released just prior to this one, of being able to mock concrete classes with limitations. A lot of times you'll see legacy code where it's not an iRepo, but it's a fooRepo. It's not an iLogger, but it's a serilogger, right? We've got these concrete implementations, and that makes it a lot harder to create the same. We still have to do the same thing. We have to write tests around the entire system and then return from that with the ability to then start creating seams and pulling out chunks of code. So we need to create two mocks. So we'll have a mockRepo, which equals new mock of iRepo. And then the second mock would be the mockLogger equals new mock of iLogger. And then with that, we can create a new instance of the controller passing in mockRepo. And we need the object that it represents, not the proxy itself. So that's why we have to do mockRepo.object and mockLogger.object. Now, our action is going to be getCustomer. And we're going to pass in some random ID, which I will put that up here in a variable. So I don't make the same mistake like I did in the opening show of hard coding something and then forgetting that I hard coded it. We'll see the ID is 12. And it's going to return a customer. So we can create a new instance of a customer. But what we really want to do is we want to change this method to actually add some logging when there's an exception. We've already demonstrated in the shows how we can call getCustomer and return a customer with the mock and all that kind of stuff. So let's say we actually need to refactor this. If we look at our existing code that we have, not here, of course, let me go into here. This is an example of another test I would make. So let me just copy that in so I don't do all the typing. This would be a happyPath test right here where here's my customer, my mock repo, I call getCustomer and I return the customer. So that's, again, we're building a test suite of everything that it should do on the happyPath so we know we don't break it when we start refactoring. Now if we want to do this, OK, so we have all of this code, all of these tests in place. And the question is, can I use TDD to work with legacy code? And the answer is, of course you can. You use TED, test eventual development, to build up your suite of tests who support your existing legacy code base. So the controller method that I have, pretend it's 50 lines long. I've got all these tests in place. I just have one to show. But what we want to do when we look at the code, what we're wanting to do is add logging here when there's an exception. So I can actually check shouldLogExceptions when getting customer. So we have our logger and what we want to do is we want to arrange that the logger.setup calls error with just say any string because we don't care that much about it and that doesn't return anything. So now we want to set it up, but let's say that it's verifiable. Remember when we talk about Mox, if it's verifiable then we can then verify that it actually got called. Right. So we have our new controller. We're calling find, but let's set up the repo and when it call find, we actually want to throw an exception and let's look at our example down here. And here's a perfect example right here that we can clipboard inherit to save watching me do all the typing. And this is saying that on the Mox repo itself, it's going to throw an argument exception when find is called with a particular ID. So this ID is 12. Okay. So we're going to do our action where we say controller.getCustomerName and we want to assert, remember the only change that we want to do is we want to add logging. So we want to verify that the error method on the logger has been called. So the first thing we do is we want to yank out the expression. And the reason we do that is it just saves us some time when we say mockLogger.VerifyExpression. I can type. And we want to make sure that it's called once. So this is test-driven development. I have written a test that will fail because the code doesn't support it. But once I modify the code to support it, the test will pass. So we do, when we're working with legacy code, we do this combination of test eventual development. Again, we build a security blanket of all this testing stuff around it. And it's, it would be hard-pressed to call them unit tests a lot of times, right? They're integration tests because you're testing into the database because you haven't separated out the dependencies and you might not have interfaces. So they're ugly. They're really ugly. But you have to have some way of verifying that when you pull this thread, you don't end up with a pile of yarn, what used to be a sweater, right? So you have all these tests in place. Now we're going to make this test pass. Let's run this to make sure that I haven't completely goofed, which sometimes happens when we do live coding. Yep, so we got the exception, right? Argument exception right here, which is what we expected. And let's look at, this is what I wanted to click on, right? It throws an argument exception and we're not handling it, right? So first of all, our test is wrong. Okay. Because we're throwing an exception, the method throws an exception, but our action here says we're getting a customer back. So we then need to assert.throws argument exception because it's just doing a throw here. By the way, this is not a good practice to catch an exception to throw it. If you're catching an exception, you should do something with it. In this case, we're going to log it. So our test was wrong. So we fixed the test. Let's run it again. And what we should get is a mock exception, which we do right here. Expect an invocation in the mock once, but with zero times. So then we need to go back and fix the test. No, sorry, fix the system under test to add the logging. So we can go into the test controller, they get customer method, and we say here, logger.errorex.message. Let's make sure I don't have anything else wrong in here. Okay. Let's run that. And there's the pirate exception, argument exception. And honestly, this is not, you might be thinking, oh my gosh, she doesn't know what he's doing. This is how it works in real life, right? As you work through these things, right? If it was all polished and rehearsed, it wouldn't be all that actual of how these things happen as we go through them. Well, let me just clear out some of the noise and go back into our legacy controller test right here. Let's run that. Make sure I didn't completely mess it up and the test was not run. So we get to debug it together. Right. This is what happens when we go off script. So, you know the term gold plating, right? Adding extra stuff that you don't necessarily need. What broke me right there was I gold plated. I went ahead and made it a theory. I didn't have any inline data in there. And it said, well, I don't have any parameters. What are you trying to do, right? So that was a full mistake of gold plating because we're on TV. I could say, oh, I meant to do that. To show you what not to do. I'm gonna find a stumble upon it. So it passed. Okay. So now we are calling into that method as we expect to. And we are asserting that the error method is being called. So a couple of things that we showed here. Most importantly is you have to have a good suite of tests around the code you're changing. Right. So I don't mean the entire application. I've got a method called do it. I have to change code in there. Write as many tests as you can around that big pile O mud. Right? Then you're gonna find the seam somewhere where I can use Visual Studio's extract method. And maybe I'm gonna pull out 15 lines of code as the smallest I can get. Okay. But the bad line is in those 15 lines of code. Now I have this other method. Right? Then I'm probably passing things to from the big do it method. Well, can I change any of those into repos without causing problems? Well, probably not because the more things you change the harder it is to figure out what changed broke it. Right? Right? If I'm changing the oil in my car, I'm not gonna take apart the engine at the same time. So we have this smaller method, let's say 15 lines. Guess what I'm gonna do? I'm gonna write a whole bunch of tests around those 15 lines. Right? And I'm gonna keep doing Lather, Rinse, Repeat until you get down to a point of where I now have this small method that is only doing one thing, follow single responsibility. And I'm gonna write my test now in a TDD manner to say, okay, it's gonna fail until I fix the feature or fix that line of code. And then everything should still pass all the way up. I mean, it's turtles all the way down and turtles all the way up. So this passes, these should pass, this should pass, this should pass. As you go into code and start refactoring things and cleaning it up, then some of these ginormous legacy integration test things can probably get deleted, right? Cause now I've got tests around all these different pieces probably get rid of this big thing. I mean, there's no need to get rid of it. Except for the fact that if I make this test pass around my little tiny thing, this test up here should fail, right? Because the tests were written around the old behavior now have the new behavior. So you need to refactor the test for the new behavior or these can just kind of fall away as you get more and more unit tests in place. Right. So you take the code you need to change, refactor it so that it is as small as possible. Well, before you refactor it, you gotta put a bunch of tests around it. Okay. Because you need to change. You wanna make sure you got as tested as possible. Okay. But what you wanna do eventually is isolate the code that needs to be changed to be as small as possible. Yes. Right, tests around that. And that's how you introduce this unit testing paradigm into existing code. And then the code that exists that has been running for quite some time, perfectly well, is tested as it's been tested. Right. And then you are applying these new unit testing techniques and tools that we've been talking about in this series only to new code you write. Or code you have to change. Or code you have to change, okay. Yeah, I mean, if you've got code out there and it's working and nobody understands how it's working, that's a problem in and of itself. Right. But not necessarily a problem to be tackled today. Right. You know, unless somebody comes in, new marketing person comes in and says, we wanna change how this feature works. And then you draw straws and whoever gets the shortest has to work on that. Or it's something where there's a bug or some reason to go in and change that code. Don't go changing the code and refactoring just because you think it's ugly. Some of us want to, right? You know, if there's code out there that I wish would go away that I've written, but it's still in production. Sure. And I kinda wish my name wasn't on it. Right? But that's not a reason to get rid of it. Right. The reason to get rid of it is because the business value outweighs the cost of doing the change. Right. Cool. So any other questions about, I mean, that was again, fairly brief, but we went deep into the X unit practices. We went deep into Mock or MOQ or Mock-Q, depending on how you wanna pronounce it. But I wanted to show here is, here's how I have a unit test, right? I'll go back to the code, yeah. And I'm now using all these tools, individual studio toolbox that I have. I'm creating these mocks. I'm setting up an expectation on a call, setting up an expectation to throw an exception. And this is the value of mocks, right? If I wanted to really have a fake repo that threw an exception, then I'd have to write a whole bunch of code to make it throw an exception. Right. Now I have this proxy. I say, hey, when you get called, throw this exception. And it just does. So now I can test the system under test, and not those dependencies underneath it, right? So then I create my new controller with those dependencies injected. I assert that an exception is thrown from the getCustomer. And maybe this isn't the right behavior anymore. Maybe we want to go into the getCustomer and return null, right? Maybe we've learned that that's the better way to do it. And now we go in and we say, okay, this isn't gonna pass anymore. This test fails. So we actually just say var cust equals controller like getCustomer. And then part of our assertions here is assert null cust. Right. Now I did the obvious implementation. I did test eventual development, right? I fixed the code, and then I fixed the test. In trudity td manner, I would have changed the test, watch it fail, and then fixed the code. Sure. But when the code is that simple, there's no harm, no foul to do it the other way. Just as long as you do both ends of it, right? No need to be dogmatic about it. You have your unit test in place, whether you did tdd or ted, the end result's the same. Right. No one has to know. Yeah. You're a little secret. Right. And we're not actually driving out in the API. As we talked about in the first episode, the real value, one of the real values of test-driven development is driving out the API. We're not changing the API. We're changing behavior inside of it. And now you know that this code that you have modified or added into this legacy system is very well tested. Yes. Perfect. And honestly, if you go from zero test to one test, that's an infinite improvement. That is. Go ask for a raise. Because nobody else will ever be able to match that level of improvement by adding tests. Exactly. Yep. So, any other questions? Nope. Cool. So I think that's a great overview of unit testing. Thanks so much for doing this. Yeah, thanks for having me on. It's always fun. Again, looking forward to your comments. I'm sure there will be many. And thanks for doing this. Yeah. We will see you next time on Visual Studio Toolbox. Thank you for watching.