Intro to Unit Testing in C# using XUnit

Video Statistics and Information

Video
Captions Word Cloud
Reddit Comments
Captions
if you want to improve your development skills and you want to reduce number of bugs you've shipped a production and you want to feel more confident angel existing code base you should really learn unit testing today I'm gonna introduce the topic of unit testing and show you how to get started we're gonna take a small demo app and add tests to ensure the methods behave as we expect them to along the way we'll talk about best practices how to rework your code to be more testable and how tests make code updates safer my name is Tim quarry and it's my goal to make learning software development easier if that sounds like something that interests you make sure to subscribe to this channel if you're interested in possible courses or hearing about insider information make sure to click the link in the description to sign up for my mailing list now you for each up in a code I do want to point out that I'm gonna using a third-party testing library today normally I try to teach just what you get out of the box with Visual Studio however when it comes to testing I think the X unit has a lot more to offer than Microsoft's MS test X unit is a long-running open source project that consistently provides a great experience and from I've heard it's only getting better with that being said if you prefer to use MS test or an M unit the techniques you find this video will mostly apply it's just the syntax will be a bit different okay so let's start by reviewing a development environment and also the demo application I set up so this is Visual Studio 2017 Community Edition that's a change from what I normally do normally my development machine is 2017 Enterprise Edition however Enterprise Edition has a lot more features when it comes to unit testing now that isn't to say that unit testing on Community Edition is crippled in fact it's a full-featured testing environment it's just we don't have similar niceties that add on top of unit testing in Community Edition so I want to show off what everybody has access to begin Community Edition is free at Visual Studio comm so make sure you go visuals to come and download the 2017 Community Edition not the Mac Edition not the code Edition and if you want to check out my ten free tools video you'll see more about which ones works for you but get the full Community Edition so that's what I'm on right here and if you're wondering I'm actually running this in Azure I create a virtual machine today it's pulled it up with 2017 Community Edition just kinda showed off so this is actually running in Azure which some of you had a question about whether or not you can have an actual full machine in Asscher or not this is proof I've got the the full desktop I've got I've got everything here that she could want in a machine on Azure and it was spun up and about I believe it took ten minutes for this to spool up me less so and this is a cheap one I think it's $30 a month if I were to pay for it so not that much especially since I won't keep it running all month I'll actually shut it down this is just for this demo okay so my solution has two projects I have a library project where I have a person model which is just a class with a couple of properties and a full property that's a read-only and then I have three classes calculator class they to access class which just does file access so I just ret reading and writing from a text file so I want to do it that way so when you download this code and you can download code by going to the link in the description you'll take you to a blog post on my website you can go ahead and download the source code if you download the source code then you can actually run in your environment because there's no sequel database or anything else complicated it's all self-contained so then finally I have examples with just you know one thing hey I'm I'm loading a example text file and the reason that's there is just because I wanted to have the ability to sometimes throw an exception we were gonna go ahead and catch that and make sure that we are X we get what we expect which is in the case of bad data that we actually get an exception not something else so we're actually going to test for exceptions so that's down the road but those are our classes here again this is just for demo purposes the application technically works kind of but the point is not that works the point is to show us some good examples of how we need to do different things in order to test an application so we have a wind form UI that's our actual user interface and we've got this right here which is our dashboard it does two things we have a calculator over here pretty simple number one number two either add subtract multiply or divide results pop over here and then we have the database section here which is just a list of people and then if you put a first and last thing here and say add person they get add to this list of people that's it that list of people's actually from a text file so we start off with over here the person text there's our star text but what happens is this file gets copied to the bin directory every time compiled and so we kind of start over with a clean slate except for we'll have these four values in a start so that's again just a testing thing I wouldn't do this in a production environment obviously because you would over at my data every time I recompile the application not a good thing but again if you're storing sensitive data or important information in a text file you might want to rethink your application anyways okay so we run this we should see that yes I've got a drop down for people in it if I add Bob done and say add now Bob Don's a list if I come over here and say 25 and 10 I can add them together and get 35 if I change the numbers here to multiply I get that so it all works and this this may be a good example of an application you come into you say I already have an application running but I want to add testing so let's go ahead and do that and a long way we'll talk about what is a unit testing why would I do it those kind of things so let's start by talking through what is unit testing unit testing is the idea that let's pull the calculator this is the really simple one okay how do you know that works the add method well obviously if you look at it XY x + y returns the the correct value so really simple yeah sure works so if it's a little bit more complex of a method though how are you sure it works well if you're writing this method usually what happens and a lot of environments is you write it and then maybe you wire up the button here to just do ad and you see if it works and if it works great you go back and you call that good and you go on but that kind of manual testing first of all slow second of all you might create some test code around it and then you kinda have to delete it make sure you delete all the test code and there's a bug in the calculation then then what you do so it's kind of clunky and that's the way you use the test code and in fact a lot people still do they kind of you know assume it works or they write little test bits and then delete them or they have a different application where they just have test Cody and they copy and paste it in there make sure it works and then put in the real application so unit tests came along and said well here's we can do directly a project that we have a method that's gonna test the add method and it's gonna pass like all values in and where I say okay where I pass a four and A three in four plus three is seven and so we're gonna say you do four plus three you should come back to me with seven then I compare what we think it is versus what it actually is and make sure the two are the same that's the really foundational principle of a unit test it tests one little unit one piece of work and so that's what we're going to do we're going to build up these tasks because then what happens is we can feel comfortable making changes to this method because if the test works afterwards it hasn't changed the functionality so let's go ahead and see us action it'll make a little more sense once we do it so we do is we right-click on our solution and say add new project and we don't go here to the tests folder now see if you have installed under visual C sharp we have a test folder where we have the unit test project that would be what we use if we're using the MS test project but we're not going to do that instead look up here to windows classic desktop and search for the class library just make sure it's the dotnet framework because there is the dotnet standard class library that's not we want so windows classic desktop the.net framework version of the class library and this is one of those naming convention things so we're going to do is we're going to call it demo library dot tests that's my naming convention that's a pretty common naming convention there are other ones out there but since I am testing the demo library with this test project I'm going to call it demo library dot tests all right so the first thing it does it creates class 1 will delete that so this is just a class library right now we're gonna upgrade it to an actual xunit library now the first thing we'll do is add a reference to our demo library because that's what we are testing so we need to see it next we'll right-click and say majnu get packages and we're going to go to browse and say now notice there's n unit that's a different testing framework it's another great one but III think that X unit is just better so we're talking about good versus great here so let's go with great so X unit will install X unit we're also going to install a couple more so it's okay is wait for word to be done installation and now we're also gonna grab the X unit runner dot console will install that and again we'll wait for this to finish installing which it did and then finally a search for X unit dot Runner dot visual studio and we'll install that as well go ahead and check the do not show again now I've seen it all three times okay so now we have three things installed we have X unit we have X unit the runner console and runner dot visual studio notice that X unit actually depends on accessibility core core serve analyzers and all the rest these were all installed because X unit was installed so that's fine don't worry about making sure you know all those just need to know X unit itself X unit runner console and X unit runner dot visual studio I should take care of everything we need now what we can do is we can actually set up our first test right away they'll get our feet wet and kind of see how a test looks we're actually gonna test the calculator because this is a simplest example we can do so let's right-click on the demo library dot tests and say we're going to add a class now again this is a naming convention so I'm gonna say I'm testing the calculator I'll say calculated tests is the name of class I'll make it public I'm gonna do is create my first test now calculator is a static class I don't you instantiate that kind of save some of our time and effort so we have a static method called add so we calculator dot add so let's add a using say that first oops using demo library and we're gonna say public void returns nothing now again here's no a naming convention thing and this this is where people often differ on how you should name your methods some people say wait start you should start with should because you're saying this test should do this is what should happen so should add two numbers together that might be our test name I prefer to start with the method name first so at in this case because I can see this is a calculator class we're testing this is the add method that way when I have multiple methods that do work on doubles I don't get confused as to which is which so I need it first by the method name then by what's going to happen some essay in this one simple values should calculate all right so I'm saying is I'm testing the add method I put an underscore and I say this is what's gonna happen the simple values should calculate and by me my simple values is you know four plus two and five plus three and those kind of things and I'm going to let's turn off with nothing we're going to pass in we'll get to that in just a minute but for now we're just going to have this method as is and I'm gonna create three different sections in here and this is the general layout of a test first we have the arrange this is where your range our values and get things set up to run our tests next we have the act face and the ACT is where we do the actual action that we're testing and finally we have the assert face an assertion is where we say this is what I expect here's the actual value and let's test the two to make sure they're equal and there may be other ways we can do that but it's always an assertion this should be the outcome so that's the assert face so first we have the arrange now this is a very simple one so what I do is I set my range at least I'll have my expected equals let's say our expect that is five so what my expected is is I'm saying this is the value I expect to be returned from the add method alright so there's my expected and then I'll have my actual they'll be in the act face double actual equals calculator dot add and let's pass in a three a two because three plus two is five so now I have two variables expected and actual and I like to always have these two variables named this because I like to see exactly the same thing every time so if I come into a unit test and I see a variable called expected I know it holds the expectation of what should happen and again actual hat it holds what actually happened so this is a naming convention that that's common it's not fully common it's not everyone that uses it sometimes people kind of shortcut and don't use expected or they don't use actual or stuff like that I like to always have expected and actual if I can again it's only if it makes sense so then we have the assertion so an assertion to be as simple as assert which right now assert doesn't have there's no a certain method yet but if we hit control dot we have to add the using statement for X unit so assert dot equal this is a pretty common one so the assert dot equal says I need two values release the simplest version I need two values value one is expected value two is actual now notice it's string decimal double and all the rest so let's pass in first expected and then actual all right so now I'm doing is I'm comparing these two values what the assert does is the assert says let me check to make sure these two are the same if they are I'll have a passing test so we have our first test setup but before we go we have to decorate it and it's right above the method we put the square braces and we've got type fact so a fact is just a test that says yep this is a test and I want you to run it in the test runner well where's a test runner if we go at tests go windows and say task explorer so it says okay build your solution to discover all available tests let's go ahead and build solution and it's still building down here so that's why how much shut up oh I only have two warnings that's fine it looks like maybe actually those errors mean something for us because we're not populating our tests over here which was fine let's look at the error list and find out while it's not panic so this is the primary reference the demo library that DLL could not be resolved lots of problem he can't get the demo library because it was built against dotnet framework 4.6.1 which is higher than the currently targeted framework of 4.5.2 makes sense so what's saying is the demo library is 4.5.2 of dotnet but I'm sorry that demo library tests yes but demo library is a higher framework version so let's go up properties of our demo library got tests and sure enough we're at four point five point two let's change that to 4.6.1 we changes dot two but six one is fine if you for us that'd be a match alright so now let's change that let's build our solution again built sixy it okay so now we have a test showing up in our window here let's kind of expand this window here notice how large it is this demo library test calculator test dot add simple value should calculate it's quite a mouthful all right and we're gonna shorten that in just a minute but first let's go ahead and run it so this is our test to run it we could say run all or we could right-click on it and say run selected tests either way it's going to compile our application and run it now I have a working test it turns green which is great in most cases the only danger here is if my test always passes so for example if I came to my actual and I put five for my actual instead of saying I want you to actually do the calculate and I were to run this again so let's go ahead and build a solution and we're gonna run all again and says yep that passed well yeah it passed because I'm comparing five to five that means nothing so that is the one danger of writing tests after you have working code which in this example we're simulating an application is already in place they were now adding tests to make it even better so that's the one danger and that's why one of the tenants of test-driven development which you may have heard of test-driven development says before you write the code you write the test and the reason for that is because when you write the test the first thing that happens is it fails because there is no code and so then when you write the code and it passes then you know that the test is working as expected hopefully or mostly that's true and you know the code is working as expected so they have what's called red green refactor so you start off with a red light which a red light let's just simulate that let's say for here instead if I were to run this now it goes red that's because for does not equal 5 so this is a failing test and it says expected 5 action for all right we can actually move it up a little bit so they say their idea is you write this test where it fails first then you write the code and make it work and then you go back to the code and refactor and so what refactor does is it allows us to change the code without changing the the input or the output and so it's basically clean up making it more efficient or making it better code internally but since you have a working test that hopefully is turning green meaning it's actually working the way we're expecting it to then you can make any changes you want to the code because if it turns red you know those changes are bad if it stays green you know those changes are good at least that's the theory so that's test-driven development and that's why they do what they do because they are also testing their tests now for us since we're working an existing application adding testing we don't have that luxury and sometimes it's a little aggressive for unit testing here's here's my philosophy let's take a quick timeout just to give Tim's philosophy on unit testing you will find a lot of people out there who are unit testing fanatics okay and I don't mean that an unkind way I'm just saying they are you know everything is precise everything is test first everything is must be tested we're gonna you know if we write a property on auto property it has to be tested which doesn't really make sense since you're testing the dotnet framework essentially but we're gonna do that you know all the way through those people are a little aggressive for my nature they're a little all-or-nothing now there's the other side of the pond which is the people who have never written a unit test in their life his right coat those people could benefit from unit testing but if you listen to the test-driven development people especially or the extreme programming people or you know all those other you know buzzwords that have been thrown around if you listen to some of those people now oh yeah I'm broad brushstroke in here but that can get discouraging be that feels like if I don't write perfect tests and if I have a thousand and one tests I shouldn't do anything wrong I'm not good enough and so it's you know I'm embarrassed to show you my testing library because it has ten tests in it well guess what ten tests are better than zero tests eleven tests are better than ten tests most likely I'm not I'm generalizing so I encourage you to not get caught up in the hype of I have to test everything I have to have a hundred percent test coverage and I have to have every layer of test because we're testing right now unit tests but then there's integration tests and there is end-to-end tests and there's all these other tests you could do and there's tons of frameworks and tools and and stuff surrounding it start off small write a test maybe you have a method that's a little tricky and it doesn't really kind of tricky stuff the right tests for just that method make your life a little easier and keep going get the ball rolling take small steps get something don't feel discouraged because you don't have the the ultimate behemoth unit testing solution get something to work it will benefit your code and will make you better as a developer all right so enough philosophy let's get back to actually testing so remember I said this is a kind of a long name and it is so let's go ahead and shorten that a bit so the way to do that you know come with a smaller he is to go to our solution Explorer in our demo library tests we're going to right click where I say add new item we're a search for JSON JSON and grab a JSON file we're gonna call this JSON file X unit dot Runner dot JSON okay and in here in quotes we're a say method display or essay method so what this JSON file does is it changes our test Explorer view notice it's actually shorter already where we'll be sorry let's build this oh you know what we need to close an open visual studio that's why let's do it it's may take a couple of seconds here but what it's going to do is it's going to allow us to see just the method name as opposed to the whole solution name and the class name and then the method name so that'll hopefully shorten up our string a bit alright so they should appear in just a minute alright know one thing I forgot to do the reason why it's not showing up and changing this if I go to my solution Explorer again right click on this JSON file properties I forgot that needs to copy to the output directory so copy of newer what that does it actually when it builds it'll take the JSON file with us let's build a solution and now we have a shorter just the name of our method so add simple value should calculate so that's a pretty simple one okay so we have this calculator and it got an ADD method so we're going to test to make sure it actually does add but you know there are potentially issues with this for example what if it returned five all the time well then we have a problem because we're testing against five and this is where X unit has a feature I really love so normally we just tag fact on top of any test method and it picks it up and puts it in our test Explorer well we're gonna change that so it's a test we're gonna say theory now a theory what it does is allows us to pass in data and run test multiple times with different data sets so let's start off by saying I want double X double Y and double results a result let's actually call this expected all right so you have X Y and expected let's take the expect it out and we're gonna pass in what's called inline data so inline data allows us to essentially populate these three parameters X Y and expected right here so let's pass in o4 and next one's maybe let's go three and seven so four plus three is seven and that copied us inline data and paste it again and this time gonna say 21 and five point two five please remember this is a double which means that it really should add the two together but what if it's actually doing some kind of rounding let's make sure that we have that covered so it should be twenty six point twenty five you have to do some math here make sure that your math is right but now I'm passing in two different sets of data how does this look when we actually build this build this and now over here in the test Runner I have two tests notice that actually tells me the two different sets of values coming in twenty one five point two five twenty six point two five four three and seven so it's gonna run both of these so let's run all and it's gonna test both of these and it goes you know what nope they both failed why well I wasn't ever I'm not actually using XY so let's use x and y so I'm passing in x and Y for my parameters instead now let's run all again and now they're both green so we have two working tests and in fact we can add more if we want just by adding more in line data now that's probably good for add but you know there might be a case where we have some edge cases so let's think about double Dodds max value well max value is the maximum value could have for devil so what if we're gonna add 5 to that watch me the results well I'm assuming max value let's see if I'm right the other thing here is when I do build it shows up here but I have to come over here then hit run unless I click this button right here underneath test in test Explorer it says run tests after built you can also go to the test menu and go to the test settings and select run tests after build same-same setup either way so now what happens is when I go to build solution it's going to build it see we have new tests and run that new tests or at least it's supposed to evidently it's not cooperating I think this might be to do the fact that's exiting if I'm not sure my Enterprise Edition does do this without fail I always have us turned on they just run afterwards not quite sure why it's not playing for me but we'll see that it kicks in a little bit later but for now we'll go ahead and hit run all alright so now we have this value also worked notice whoops a little too big let's um it's a little smaller so it says okay I'm adding these two values together and my expected let's see if we can't make this bigger I'm gonna do gonna drag it down to the bottom instead they really need to work on this whole interface so it says okay the X is this really long value right here the Y is 5 the expected is again method long value here and it says yep that worked all right so we just tested an edge case I'm gonna put it back over here we just tested an edge case in that normally you wouldn't pass max value through is one of your values to add but what if someone did we'll make sure the application doesn't crash when we're expecting you know something we're hasn't weird come through okay because if you always test you know the simple stuff four plus three is seven you're not gonna necessarily catch all your bugs now here's the other thing that might happen you might come across you might test this and it all test find you get green across the board and then someone gives you some wacky value and it crashes your application or you have some kind of air throws an exception and you say well I didn't expect that you know they file a bug report and go through the bug report you reproduce it and go up yep when a person passes you know negative 5 and positive 5 for whatever reason it doesn't amount to 0 what you can do is you can create a test that reproduces that and if you can recreate a test that reproduces that and you do see that it fails the test then when you go and change your method hopefully the current tests that are green stay green and the one that's red changes to green if that's the case you can deploy your fix and you leave the test in place because now you have a test for an edge case that you hadn't thought of so as there something else to think through now let's go ahead and test our next method so we're going to do pretty much the same thing except we're going to do it for a different method in our calculator class and we can go on with the add method I don't want to get too far in depth just because you know now how to test the add method so just start thinking through you know any other edge cases you might feel I don't feel like I have to cover that in this video just for sake of completeness but there is one method in here that's a little screwy sometimes that's a divide method so let's test that so let's draw off we're going to do a theory reality gate and we'll say that the against square brackets the in line data is going to be let's start off with something simple rest say 8 minus 4 is 4 really simple and our method is public void divided simple values should calculate double X double Y and double expected alright so we have our arrange phase we are doing arrangement we already have our expected set up and we're not instantiating a class or anything like that so now so we have our range you don't have to type this out but I just want to kind of demonstrate you know the different phases for you and highlight them so now we have our act face double actual equals calculator dot divide x and y and now we can do our assert which is a search dot equal there's a lot more options in here but we're going to do equal assert equal and rest say expected actual and that order is important even though it's an equality check because when we look at it in the task Explorer we see the X and the y values and what should have been vs. what was and so if we get those flipped it might we might see it should you know four plus three we're saying it should have been seven but it actually was six we want make sure it says we've suspected seven and got six not we expected six and got seven so just make sure you put them the right order so we have a test now for divide let's run all on this and got an error already all right let's find out why so the error is we expected for we got was two well actually it's because I was thinking subtraction 8 minus 4 is 4 but 8 divided by 4 is 2 our tests was the problem not the actual code just make sure you think that one through again this is where test-driven development people go yep missus this is why we develop our tests first so we make sure that our our test is failing for the right reasons but but yeah so just make sure it the TAS is right but then go and check your code next so we have a working TAS in theory let's go ahead and and build a solution here oh and it says open or unn let's go ahead and run all and I've got a green test now the other thing you have right now is you've got this kind of mishmash bundle we can set up grouping by we can say group by class or by name space duration outcome traits project let's group like class right for right now it's still groups into one group because we're only testing the calculator that kind of play in just a minute when we expand outside the calculator but now let's look at a an edge case now we could either test it right here which probably isn't the right place to test it because I called this simple values should calculate but let's copy this entire thing and it's test simple value should calculate we're going to test divide by zero and we're actually make this a fact because we only pass in eight different things that divide by zero we just do one so what I got pass anything in and instead we're going to do is in our range will actually set for expected and here is the first place you have to ask the question what do you expect to get when you divide by zero well if you expect to get 0 which isn't isn't mathematically correct if you expect that then we'd say 0 I do know that it's gonna return in Fandi because I guess reasons I'm not sure it used to throw a divide by zero error or exception so the question of what you expect depends on your logic for this particular case I'm gonna say I expect zero back now again not the greatest math logic I just wanna cover exceptions later so let's just say that I expect it to return zero that's my personal business logic so let's just say 15 and divide by zero all right so that's my act I'm doing a division to get the actual value let's see if a two are equal to each other 15 divided by zero I expect zero back let's go ahead and build this we get a new test will you just say run all or we can right-click and run that one test and we get an a failing test we expected zero we got infinity well I'm not sure how I would define infinity as a value to be tested expected against and I'm pretty sure it caused some problems when I try and use infinity then to add 5 to it so this is one of those cases where okay we've got a bug in our code because our business logic says we should expect to get zero back and is it almost cases where if you have business logic that says when this happens this should be the outcome that's a great place to put right no tests so calculator we have us divide method well if we're dividing by zero we need to return zero instead of this action so let's do this where we factor surround with if if Y is not equal to whoops not equal to zero I almost got in my sequel call there a sequel is a greater than less than but with c-sharp it's not equal the the bang or the exclamation point and then the equal so if Y is not equal to zero return X divided by Y else it's gonna make sure I note that because the next program coming along bill is that's just stupid so I say yeah but the boss told me to so return zero so if Y is not equal to zero do division but if Y is zero return zero now let's build a solution and run all of our tests and now we have a working test so I put that business logic in when I have a failing test and I made a change and now all my tests pass not just making sure it divides by zero properly but also these simple values should calculate so now I feel comfortable making that change because I didn't disrupt the normal stuff and I add the logic for the edge case so that's why you know writing good tests makes you feel more comfortable more confident making changes to code because if an intern comes in and says oh I can fix this and they go ahead and fix this method if you don't have any tests how do you know that it worked well you actually run the application so that can very easily miss these edge cases now the intern makes a change and you just go ahead and say well push the run all button if you get all green lights you're good to go if you don't you screwed something up so go back to the drawing board so that's one of the benefits one of the big benefits of testing so now I have some inline data with theories and I have a fact test as well now let's go and add another test this time let's Tesla a little more complicated let's start with my examples don't see us first because this this one is the exception so this one method against a static example static class anaesthetics method so what this does is it simulates loading a text file so you give a file name yet the file name is less the length of it is less than 10 it throws a file not found exception otherwise it returns the string the file was loaded correct or click correctly loaded so let's test this example alright so we're gonna go to our solution Explorer we're going to right click on demo library tests we're gonna say add new class I recall this class examples tests make it public and now we'll create our tests so let's let's do this let's create a fact test first and I'll I'll show you why we're gonna keep as a fact test but let's start a fact I think it me have to add our using X unit to the top and we also have to add a using statement for dental library so our name of our example method is example load text file so just like here where we start oops not here just like here where we say the name of the method and then what does we're going to do the same thing for our example tests so public void we don't return anything with tests I'll start another name example load txt file let's just pass in valid name should work okay so the lolly pass in a valid name it should work so we'll do is we'll start off by saying here's our assert so arm start expected expected equals and then let's just say you know what let's not do it expected I'm gonna show you why in just a minute so let's see our string actual equals example Tet example oops this is in the examples there we go examples example load txt file okay we're not passing a file name this is a valid file name doesn't matter what it is just a string longer than 10 characters we're gonna say assert dot and we're gonna say there's got a lot of different options here we have matches equals not equals at the same no vs. we could say not null there's a whole bunch of different options here so we can say true let's go with true this is a fun B that just says test this condition is true so actual dot length because a string don't forget is greater than 0 meaning you actually turn some past ring it doesn't matter we could go ahead and copy this string over and make sure that it matches exactly and do it an expected and actual but that seemed kind of fragile me instead of get that show off another one of our options here which is the assert true so you can look through this entire library has tons of stuff in there for other assert types statements including you know null and comparing to values and doing some other options but assert true works in this case we're just gonna say hey make sure the length is greater than 0 for the return value that's a really simple test let's build our solution notice it now pops in down here because under this class because we did that sorting or filtering by not filtering but grouping by class instead of viably was outcome so we can right click on this and say run selected tests we get a green light meaning we are good to go with our basic example now one more thing to point out real quick is what if you wanted to debug why this wasn't working right well you can throw a breakpoint right in there if you were to run this test though it is ran and we hadn't hit that breakpoint and the reason why is because run skips over breakpoints but if you right-click and say nope I wanna debug the test then we hit this breakpoint it pops right out to it wait for it wait for it there we go it pops right to it and we can actually inspect the value so in this case the file is correctly loaded is Lee returned information and the length is 30 which is definitely greater than 0 will you continue and it it runs on its merry way so we can use breakpoints in that way just like you would in the actual application to make sure that things are working the way we expected making sure that we know why a test is failing so let's copy this and create another fact but this one where si instead of valid name regress say invalid name should fail alright so an invalid name should fail remember that it checks for a length of at least 10 so anything less than 10 it's gotta throw an exception so how do you check for an exception well we're gonna do is right cut this code down to just our actual check I'm going to do a control X to cut this and we're gonna say just traffic let's search a search dot throw and we can actually specify throws I'm sorry if you actually specify the type of exception so the exception that we're looking for is the system do dot file not found exception so it's actually a day using stood up for system to i/o using system Daioh that is shortens our call so this is a file not found exception and that's inside of these angle brackets alright and so then we do is we have to actually call the the the method but since it's going to throw an exception we can't just call it and put the value into say a string because that would cause an exception we don't want an exception where it's gonna blow things up we want to capture the exception so this assert dot throws is actually has a try catch in there and it's going to check to make sure the exception is this exact type and then do a comparison and all the rest so we have to actually run method inside of our parens here which means we have to get a little tricky and to do it open close paren again and then equals greater than and our call so what is this doing well this is actually saying I want you to pass an action in okay so if we mouse over here it says action test code notice right there so this is an action this right here what it's doing is saying here is the parameters you pass in and then here is the actual call okay so it's the actual call it's a little complicated in our stand but basically just know that open closed paren your lambda expression and then the actual call you want to make the method call you and make so that's it and what's gonna happen now is it's going to run this if it throws an exception it's gonna check to make sure it's a file not found exception let's see if this actually works go ahead and hit build we have as new test here let's do run all to make sure that all of our tests are run and now yes it did work so we actually had an exception if I were exchanged this exception let's just call it that's had a generic exception instead and if I were to run these all again it'll actually fail because it says expected system died exception but it actually got system do dot file not found exception that's a different exception so that's why it failed so now it's checking to make sure that it's the correct exception all right now is one other thing we can do here and that's the very beginning before we let's there we go we can actually pass in a parameter name whether the parameter name does is it allows us to say here is the parameter that caused the problem say you have five different things passed in and you're checking each one to make sure that it's valid information and if one is not you thrown exception but which one was it was it you know say it's the first name last name email address phone number and age well which one of those properties was a problem if any one of them can throw an exception which one was the ultimate problem well I could passing the exception name right here a parameter T so we could say well the parameter name is file I'll just put file here now here's the problem this right here the file not found exception that exception will actually work with the parameter name but an argument exception should yep so that would mean you'd have to make sure that your item is returning argument exception but let's just say let's go in here refactor this maybe get the file length is less than 10 we should instead of throwing new file not found exception we throw a new argument exception we can actually pass in which argument it was okay which parameter it was so that's that's number four out of five so you can say the message was the file name was too short and the parameter name is file again that's a string so you can actually type the the name out exactly but if we did it this way then we can actually test to make sure the correct parameter is the one that caused the exception so for testing this we might actually test it five times if we had that object with the five different values we test it once the first name it's invalid and then say make sure the arguing exception throws it on the first name now the last name not the email address the first name and all the rest so just make sure as just to show this does work if I had filed one instead and I run these tests I've got a failing test B that says I've got a problem here and says ok assert dot throws failure expected type of system to argument exception actual no exception was thrown well that's interesting let's find out why well we didn't say throw when I came down here actually accidentally blank that out so throw new argument exception let's try it again this is where tests really help you that little typo caused me a problem so a given test failed this time it says I expected that the failure would be on file one parameter but instead it's on parameter file well you go oops I missed typed that it really should be on the the file parameter we run this again now we've got a working test so we're actually testing that something throws an exception which is good because exceptions do tell us information you know they do say you can't do this and that's that's a good thing they wanted to stop the application but we can also test to make sure it really does stop the application so there you saw that we can throw an exception and catch it we can actually if we have certain exceptions not all exceptions but certain exceptions like the argument exception we can actually check which parameter was the problem and you saw how what I went in here to make a modification to my code I did something that I shouldn't have done I didn't throw the exception and that little typo would have allowed my application to work as I thought I expected unless I actually tested this file dot length is less than ten fortunately I had written a unit test for that and the test failed and it says nope I what I expected to have happen which is an exception didn't happen I fixed my code and now it works as expected so really easy to see how unit tasks can make your life easier and again I could have a hundred tests for this little project I have seven but those seven have helped me already so a game Oh getting back to I talked about earlier just because you don't have all hundred doesn't mean that you're not getting benefit on unit tests or that you're somehow not measuring up to what you can make a little better every time maybe if you write new code write tests for it first or write tests for at some point but don't just ignore it at least make it a little bit better every time okay so let's look at the big one now and that is the data access class because this is where people kind of you know stop or you know unit tests kind of break down a little bit because they get into the real world the real world is complicated and messy and it's hard to test and I say it's hard to test because look at this let's just take the the add new person method what does it do well not a whole lot yet something happens we get all the people from our text file that's the text file right here we add a new person to that list and then we grab each of those lines we we create a line for each of people and they write all those lines back to the text file so essentially we add the person to the list and write everything back to the text file over ready everything do you see a problem with running tests on this method I mean if I run a test a hundred times you know every time build it runs again every time I do something runs again well if I do that we're gonna be adding that person a hundred times the text file well if we had something in place where we're saying make sure that person's not already in the text file well then my tests have only worked the first time and then fail every time after that so that's a problem I mean there's there's so many problems here with testing this method and part of the reason why is because this right here is not one unit of work and that's where over the calculator the add method does one thing even the divide method that has a little more logic to it does one thing it divides one number by another so we're talk about data access look at what it does it gets all the people then it adds the people person to the people and then it takes all the people and it converts them to a list of strings and then it writes everything to the text file that's about three or four different things that is doing in that one method well first of all it kind of violates our single responsibility principle the single responsibility principle says that a method should do one thing that's his job one thing so we're really breaking this method apart and that's where unit tests really help us be the unit tests are all about those single things the single responsibility methods a unit tasks really shouldn't span a large area of code now there is some debate on what a unit of work is in my personal opinion a unit of work is any set of work that doesn't access anything external external meaning a database a text file the web those type of things that's all external stuff so a unit test would be an anything that does not access external stuff an integration test would be on accessing that external stuff and then our end-to-end test would be for you know if you you know start in the UI and they actually even capture the UI clicks and then watch what happens all the way through the process you know so creating and saving user that'd be an end-to-end test but today we're doing unit tests so how do we make sure that we can test this add new person method we're gonna do is we're actually going to change how this new person method is laid out so let's start and kind of break us out a little bit let's create a couple private methods actually I'm gonna come public and this is one of those things where it can get a little bit debate on how you do this because the next questions gonna come up is I want to make these private methods but I want to test them how do I test private methods there will be ways to test private methods but my personal opinion is you shouldn't because private methods are not exposed anywhere what you should be testing in my opinion is those those public access points the you know whatever happens behind the scenes all they care about is input and output and that's the public methods so in this case I'm actually gonna make methods public that I might not otherwise make public but they do a job they do one job and so it's okay now I could also create a separate class that supports this class and that class could have all my public methods that do those one bit work that way keep this class clean for public use and yet I have all the rest of my methods somewhere else that might be a good solution if you're concerned about having all these methods be exposed but I would make these public because they are like I said doing work one bit of work so let's make a public static void add person to people list all right we're gonna pass in our list of person model we'll call it people and we're also going to pass in our person model person all right so what'd I just do well all it does is it says people dot add person which seems kind of ridiculous okay but and that's what we do right here so let's get rid of that line but the reason why I'm gonna do this add person to to the people list is because I want to do some checks to make sure that you know maybe the first and last name of this person object is valid so before I add a person to the people list I'll make sure that this thing is valid first all right so now I'm going to do is say add person to people list I'll pass in my list that we have and the person has got so all this method does now is right now it just adds the person the list but eventually we'll have some kind of X in place to make sure the person is a valid person so we've now taken one of the responsibilities off of this method right here and we creates something we can test now let's look at this right here because I think we can take care of converting our people list over to a CSV list instead so let's do public static void that's actually you know what let's return a list of stray instead it's that void Liz the string converge models to CSV we'll pass in our list of person model again call it people so we're gonna pass in our people or I get back a list of string okay that's we're capturing up here in fact here's I'll do I will cut this out I'll paste it in here you want to change them like is I'll call it output you know I like to call my the value going back out I should call output so it's very clear and now I'll grab this code right here and cut it out and I will paste it in down here so now I'll take this people lists for each one I'll create my CSV and I get out change at the output instead and I'll return output so now I will say convert models to CSV people and I'm going to use that for actually writing to my text file now I could just put it right in here but I'm actually going to capture that as a list of string right here because if I want to debug something I have it available vs. passed in line right here so now I've kind of broken out this into a couple different pieces so I've got the you know first we're getting all people well we're going to test that later that's gonna be something else we test but we don't need to actually do anything more without because it's actually its own method then we have we add a person to people lists or Adam code there but the next step is to convert that to CSV the next step is to write that so this method right here add new person is actually composed of four different method calls well we don't need to actually test the actual caller necessarily I would probably test this when I do integration testing because I'm actually integrating with the the text file so that's what I probably look at doing doing that testing now some people might say well you can actually test this what you do is you'd have some kind of mocking framework or you know fake or a shim or something like that and what that does is it will replace the actual let's find it here let's let's look at get all well is right here file dot write all lines this is a Microsoft bit of code well we could write a ship that says okay replace this method right here with our own that pretends to be this and we could do that and there's like I said you can go way down the rabbit hole of of mocking and shimming and writing fakes and all the rest but it makes me a little nervous and here's why because when you do that you get very careful about what you're actually testing but you can write lots of great tests that have you know the green checkmark but that's just because you're writing this this shim that does what you expect it to do and that's all it does it'll actually simulate the real world of what happens in this write all lines so just be careful of that I've seen some people that have you know felt like they had great tests and they had always you know fancy things for for simulating databases and in the web and the dotnet calls and all the rest and they have all this complicated passing things around creating all these objects and all the rest and they're not actually testing real code they're testing fake simulated code so what I prefer to do is I prefer to say well I'm going to test this in integration testing where I'll actually use an actual text file and we'll try it there instead and so I'll skip this for unit testing instead I will tast this method I'll test this method I'll test this method and since I can't I don't have any to do here I'm not gonna test this method but this is a Microsoft method I'm going to trust it so as long as I have passed I've verified all the information going into it and all the methods up to it I think I should be okay so that's my personal opinion that's how I do it for most cases is I try to work around not having to do the the mocking or creating the fake objects so again personal opinion it's up to you just don't get overwhelmed and not do anything okay so let's write a couple of tests for what we have so far so we have this add person to people list okay so let's test that all right so let's actually copy this method names we'll use that in a minute and we're going to right click on demo library tests and add a new class and we'll call this the data access tests make it public and our first test will start off just assume fact unless you have a reason that assume otherwise and we're also going to add our using statement using dental library I say public void it's the add person to people list and these these method names can get really long and it's okay if they get really long because they're descriptive now if you feel like yeah I wanted something a little different that's fine like I said some people don't even put them at the name they just put what it should do so under example test you might see should fail on invalid name or should work with valid name so that can work but like I said that's for example load text file well if we have another one for example write a text file well then we've got a problem because that also has should fail with invalid name for that as well so you have two tests with a similar or same name so that's why I like to have is method names here it does make them long I'm okay with that so add person people list should work alright and so we'll do is we'll start off with our setup which that's our our expected so our expected is going to be a person model all right add the models so actually I can expect that it's going to be one of our new value a new person equals new person model let's say our first name is Tim and last name equals Cory to create a new person model and now we have to create a list of person model and we'll call this people equals new list of person model okay ready now is we're gonna say data access dots and we're testing the add person to people list so add person to people list and this returns a void that's why I'm not putting into actual this is where it gets a little tricky and I'll show you what we're on dude I'm just admit it makes a little more sense this is where this is a little more of an advanced case not super advanced but a little more advanced I want to show you that because this one ran too it's not gonna be the neat and clean calculator example that's it's a pretty common way of seeing tutorials the problem is is that the real world you know bites you as soon as you step out the door of trying to use unit tests like this okay so we're gonna kind of work through how we set this up maybe even even refactor this test but let's set it up first so we pass in a people list which we're passing in the one we just created now it's gonna add this person to this list but since a list is an instantiated item it's a reference type and our reference type means is that when over here and data access I add a person to the people list it's gonna change the list over here in my class so it's the same list in both places we don't copy it we make it into a parameter that's one of those C sharp object oriented things you can either understand me you pass around objects you're not copying them you're passing around essentially they're reference and so it's it's kind of like you're passing on the address to the same house okay so each instance is its own house but when you pass that instance around you're just passing around the address and so everybody points the same address so alright so when I say new person that's going to add this person to this list so what I can say is assert true that people dot count equals one because the list start off with nothing but you in theory added this person to the list therefore the county one here's another thing you can do with a unit test you can do another assert assert contains so let's grab the a person model and say the person model is new person and it's in people so this contains does it says okay I wanna make sure that you have a person model in you that is this exact object okay so it's gonna essentially look through this list of people and see do you have a new person which is this one right here would you know Tim Cory if you do then it's gonna pass and if not it's gonna fail but check this out I have to assert statements that's fine now some people some purists again will say nope when I search statement / / call but the reality is I want check two different things going on with this particular test because yeah maybe you added a person to list and so the people count is now one but what if you just add a blank one well if you didn't actually take my new person add them you just create your own and add it it would still he won so I want checks make sure the count is one because that's a good valid check but also make sure that the person that was added is the one I sent you so sometimes your unit tests will actually have more than one assert and that's okay and it will only pass if both of these asserts are true or they work if not then it will say which one fail and why so let's go ahead and build with solution I can we should have one more test all right so now we have expanded oh there we go there's our data access test it's not been run yet so let's go ahead and run the selected test and we get the green checkmark yes it worked now let's test what if we send bad data in fact let's send us to a theory all right so rest send data in and we're due in line dear now just to point out I'm doing in line dia there is the opportunity to pass in data from other data sources as an inline data you can pass in member de you can pass in data actually from a you know sequel or from Excel or CSV or txt file there's a lot different ways you do it some of them baked right into X units some of them you have to actually create your own loader but there's examples in the web for doing that and even on X units website which is X unit dot github to IO I believe or something like that just sir Google X unit X unit4 example will be there their page they have great documentation on getting started I wouldn't say that there's a full-featured documentation necessarily but they at least get you started in a lot of different areas in a lot of different scenarios but for this video we're already long enough I just want to do in line dear so with that being said I'm gonna pass in the first name so let's pass in Tim the last name was passing nothing and I'm gonna say that the bad parameter should be the last name parameter ready that parameter check thing okay and now we're s a string fer name string last name and string the parameter that that is not working okay so now we have our inline data so and we if called should work or essay instead that's why it's give me the error should fail ok I'd check to make sure it fails I'm going to figure out why it fails okay so we've got this up instead of passing in Tim and Cory I pass in my first name parameter and my last name that's we're getting off a screen here so let's string us up for now and my last name but we're gonna pass in bad data here in fact let's pass it another inline data just to make sure we check both cases will pass in nothing for the first name and where I pass in Cory and the bad parameter is the first name so what we're expecting here is that if you pass in nothing an empty string for one of the parameters it's gonna throw an exception so and so again we there's no asserts because we're expecting exception and if we don't get an exception then that's a whole nother issue and that'll actually fail a test still let's cut this out or essay assert dot throws and this should be a argument exception and the parameter that's going to fail is called param and the code to test let's again open closed paren equal greater than and then our call so this is supposed to fail it's supposed to throw an argument exception and we're gonna make sure that the argument in question that's having the issue is whatever we said was the the argument so let's first make sure that this doesn't run okay it should fail so I said run all and yet we've got two failing tests because it says I expected an argument exception I actually got no exception thrown and the same thing is true for the other one so it ran both inline tests and said nope they both fail because they didn't throw an exception they did not throw an exception even though the expected one which now I can go ahead and refactor my code I'm testing it to make sure it works the way it's supposed to work when you pass in good data and now I have a test for bad data but that test is failing so I can come over here to my data access this right here I can say well you know what I should check that first name if person da first name actually you know what straight I always do this backwards string dot is null or whitespace person dot first name so what that does is it checks to make sure it says is this null or is an empty string or maybe it has even spaces in it but just spaces it's just spaces it is still invalid so therefore it's gonna say if it is true that's null or has just whitespace where I hit this code right here this code is going to say throw new arguments exception and the argument exception is going to say grass say you passed in an invalid parameter and the parameter you passed in is first name so even though it's actually inside a person object not we're not saying the person parameter we're actually saying the first name inside the person object that's okay we can do that now I haven't done anything for last name let's run this right now and see if we get a passing test and we do when you have two passing tests in one failing test the failing test is for last name so let's do this exact same thing now I'm usually opposed to copy and paste because watch this Oh let's check the last name so oops ctrl J go get the last name great I'm done right and so I hit the run off and it's gonna run and it's gonna go nope all right and watch okay we still have an error now the error changed expected last name but I got first name why I copy and paste it and when I did I changed the one parameter to last name but I forgot about this one over here that's why I hate copy and paste because it causes those weird issues like that where you miss you know especially we have 8 different places to change it and you change it in seven that's a problem okay so with unit testing I was able to catch that error I wouldn't rely on you know testing for that but it is a nice feature that it says open nope you're close but you still don't have it right now I've made my change again my method and I run it again and now I have all green tests which means that my work here is done or at least mostly done I could refactor I could say well let's see if we can change this up so that it's a little more efficient or something like that now that I have three green tests if make any changes of this I can just run a test again make sure they all pass if they do I'm good to go if they don't I have to rethink my refactoring all right so what I'm gonna do right now is I'm actually going to stop here now there's a lot more work to do yet in saying this application up to have full testing but here's what I do the way you get good at something is not to watch the videos the way you get good something is by doing it now watching vids is important I watched videos all the time to upgrade my skills in certain areas but the way I really lock in those skills and the way I make sure that I understand those skills is to actually do the work and so what I'm gonna do right now is I'm gonna leave this as it is now I have done some work for you I've got for example this method right here convert models of CSV it needs to be tested because you make sure that when you pass in certain people that you get the correct list of string back so he passing just you know Tim Corrie and Sue Storm you should get back to entries in the return value the first entry should be Tim comma Corey the second entry should be sue comma storm you can check all those data points you can check to make sure it's returning two people or two entries your list of string all that kind of stuff can be tested I'd probably do a fact-based test but it's up to you so that easy tested then we have down here the get all people which does a number of things it reads all the lies in the text file which again this is a Microsoft thing we're not gonna shim that out so therefore you we're not gonna do anything here but this could probably be brought out into a difference method taking the the file contents which is a list of string and I'm sure as a string array say a string array and splitting it up and creating a list of person model well that right there could probably test it if you put it in a different method so create a method for that and then test that method then go in here to the calculator test the rest of these try out things like the you know double max value and Dilla min value and what happens when you add a max value in a min value or what happens when you multiply them in value tie or max value times a max value and all that kind of stuff create unit tests for it and then if there's any other methods that you go you know what I'm not quite sure how I test that I wanna try testing that you can put in the examples class so create a little method you know put it in examples class and then see if you can test it okay you don't actually have to wire up to any UI just test it in a test class so create a new a new test in the the test examples test okay so try all that out kind of get your feet wet with unit testing expand your knowledge a little bit take an existing application and refactor it and add test to an existing application and then we even add new code and test it as well maybe if you got add new code try out the test-driven development style of create the test first have fail add the code make it pass and then refactor it and see how you like it and see you know if you can start doing it if it it feels right to you alright so once you try that out so do that you're gonna have to go down into the description and click on the link that has the the take you to my blog and it'll have the download there that you can go ahead and download the the source now I'll leave a source as it is now so will will have a source as is we existing tests working you can make sure all the tasks work for you and then start adding your own tests ok so that's your homework I want you to try it out get your feet wet let me know how it goes let me know if you have any problems or any questions leave those down in the comments down below and good luck so I hope you've enjoyed us I hope you've learned a little bit about unit testing I hope you take my advice that if you're not unit testing now get started but don't get discouraged you're not at the top of the mountain just take one or two steps every day so just kind of move yourself towards the top of the mountain but don't get overwhelmed with you know the crazy complicated end of unit testing start the very simple end and help yourself out and grow the more unit tests you create the the better you will be not only at creating unit tests but also the better you'll be at writing code that's testable and while I am I'm never a fan of doing something just because you know to fit into a mold testable code is one of those exceptions because testable code is actually focused on a single responsibility principle the idea that a method should do one thing so but you don't test every method because some methods are talking to you know dotnet framework stuff but for the most part you're testing you know the methods they're doing the one thing is the actual logic of your application so I really like that because it does force you to think about how do I only do one thing in this method not a ton so is there definitely a useful skill to have it's pretty common in the industry to have unit tests in place some company will even say things like you can't check in code until it's fully covered or mostly covered by unit tests so the better you are at this skill the more marketable you will be as a.net developer so I hope you enjoy it definitely practice practice practice and as always I am Tim Corinne
Info
Channel: IAmTimCorey
Views: 235,002
Rating: 4.9549422 out of 5
Keywords: .net, C#, Visual Studio, code, programming, tutorial, course, training, how to, tim corey, C# tutorial, xunit, nunit, mstest, unit test, unit testing, unit testing c#, best practices, test runner, code coverage, integration test, unit testing tutorial, unit testing tutorial for beginners, beginner, intro, fact, theory, exceptions, test exceptions, inline data
Id: ub3P8c87cwk
Channel Id: undefined
Length: 102min 8sec (6128 seconds)
Published: Mon Mar 05 2018
Related Videos
Note
Please note that this website is currently a work in progress! Lots of interesting data and statistics to come.