Everything you need to know about Testing In Unity3D (even if you've never written a unit test)

Video Statistics and Information

Video
Captions Word Cloud
Reddit Comments
Captions
hey what's up I'm Jason and for the last year or so I've been writing a lot of unit tests for my game code and today I wanted to share the experience of why I've been doing that what the benefits I'm seeing are and how you can kind of see these in your own projects and get started with unit testing your own code it really is worth it it's really a lot of fun and it will save you a lot of time in the long run if you learn this stuff and get good at it so follow along in well we'll go from zero experience if you've never written a test to showing you how to write nice big tests that use real-world projects and real-world problems as examples so you can actually use this stuff going forward it won't just be all theoretical we're gonna really dive into well first the theory and then all of the practical stuff of how you write these tests how you structure your code and how you'll start to see the benefits from doing all of this so if this is the kind of thing you're interested in get ready it's gonna be a little bit long make sure you subscribe like and most importantly if you can just share the stuff even if you don't want to hit subscribe and like just hit the share button it helps more than anything alright let's get going I want to start by quickly discussing different types of testing that are available and just cover a couple quick terms the first is manual testing this is what everybody does at first so it's when we go in and add a feature to our game or make a change we go hit the play button and then we wait for that scenario to happen maybe as we go shoot an enemy and wait for the explosion see if the particle place that's manual testing it's the thing that we do pretty much innately when we're building games and the first thing that we learned to do it works great because we can see what's happening we can tell what's going on and we kind of work around problems but it's very very slow the first time you do it it might not seem bad but if you imagine that you're going in and retesting every single thing that you've done every time you make a change manual testing obviously really quickly becomes impossible to keep up with well I wouldn't say impossible but as your project grows it gets a whole lot harder and a lot more expensive and a lot more time-consuming in fact this is what we did a lot of at some other big companies I worked at working on MMO stuff we had entire buildings of QA teams just to do manual testing to cover all of the different scenarios and things that we can hit so we didn't really have much automated testing the next step down this little ladder though is our end-to-end automated tests these are the tests like you would imagine almost like a bot running them so the game starts up something forces these tests to run it could be something internal or external it could be like a test runner that's outside the game or it could be a bunch of built-in functions that are in the client may be calling in to some server stuff or something like that they're usually hard to set up they take a lot of time but they'll run through a full process and kind of tell you whether or not the entire game or the entire workflow is working they're good to have but not something that you want to have a lot of because they're extremely hard to build they take a lot to maintain and they break often the next step down that line is the integration tests and integration tests are really where you're testing the I guess integration between two different systems so this could be testing something like your server to your database or maybe your client to server calls in a multiplayer or network online game there's something that again kind of like end-to-end tests are a bit harder to do and they tend to break a little bit more often so you don't end up with as many of them as you do with the next level which is what we're really gonna focus on today and that's unit tests unit tests are supposed to test a unit of code they test a bit of functionality ideally in isolation and tell you whether or not that code does what you expect it to do not necessarily if it's broken but if it does what your test says it should do it'll tell you yes or no if it doesn't do the right thing it doesn't do what your test says you'll know right away you'll get failures and you'll be able to go in and fix them the last thing I want to briefly touch on before we dive deep into unit tests though is TDD that's test-driven development that's the process of writing your tests before you write code generally if you're doing tdd what you'll do is write a test that fails and then you will write the code that makes the test pass and then once that's done if you need more tests you might write those but then you'll write more tests that fail and then more code that passes it and you always kind of go in that that pattern or at least that's that's the ideal that's not what we're really gonna cover today I'm not a huge TDD guy myself and that's probably just from a lack of really using it and getting a lot of good experience with it it seems like it's awesome it's probably worth getting good at and learning more about but that's not what we're gonna talk about we're gonna talk about the most important part I think and that's unit tests we're gonna dive right into all of the awesomeness and get you started get the stuff into your projects right away now let's talk about why you would want to do this why do you want to watch the rest of this video learn about unit testing and start doing this in your actual project I'm gonna start with these two very simple reasons that one you'll verify that your code works and you'll have code that verifies that your code works so not only will you know that your code works the first time that you run it so maybe like you've gone and you've added a feature you hit play you watched it everything was awesome it was good but you're gonna know that that code does exactly what you expect it to do all through the future so even if something it's relying on has changed or maybe the way that you're calling it has changed a little bit some of the data that's going into that data structure has changed or the things that are interacting with the code have changed you'll know that your code still works if it stops working you're going to get a failure you'll have immediate information about it and you'll be able to go in and fix it but that also really ties in to preventing bugs from regressing in fact kind of is the same thing right it's the idea that we don't want bugs to come back and the biggest kind the biggest problem this will solve with bugs is when you're working with multiple developers who are working on semi-related code don't completely understand each other's code or what's going on there but they have some idea and they're making a small change that breaks somebody else's code so this has happened to me I don't know dozens of times and I'm sure it's happened to plenty of other developers they're where you'll make a small change to some code and not know that it broke somebody else's functionality they were expecting it to maybe act in a certain way or return a certain value and you saw oh that's a bug I need to go change it and go back to the other you know maybe I'm I think I'm fixing the bug for my code and it ends up breaking theirs and then a week later they go hey somebody broke this let me go fix that back and they switch it back and now my codes broken and then I go oh no hey look somebody busted this let me fix it again send that bug right back home right and it will just keep going back and forth because nobody knows that they're breaking the other person's code nobody realizes that's happening unless you're really actively communicating and not missing anything it's hard to know that that that's what's going on and why it's happening especially when you're busy throughout the day doing all your normal development stuff right it's like usually a small thing that you're not even paying attention to you and you're just like why does that stupid thing keep coming back if you have unit tests there as soon as one of you changes it you're gonna break the other person's tests you know realize okay there's something going on here let's either I can figure this out or I'll communicate with the other developer who wrote this test and we'll figure out what's going on and figure out the actual solution so we're not accidentally breaking each other's code really important really helpful but again even if you're working solo you'll still kind of get that benefit because the ping-ponging it might happen a lot with other developers it can also happen with you know one month ago you and one month from now you and then keep repeating that process and you'll forget like why did I do this how's this broken why is it like this it's weird which kind of takes us onto the next page of things which are the reasons that I really love the tests and that first one there is just that I can remember what my code was supposed to do so I work on a lot of projects a lot of the time and it bounced around and do youtube videos and a lot of temporary projects all kinds of things or just looking at other people's stuff and helping them out so it gets hard to kind of keep track of everything in my head right if you have one thing and that's the only thing you're thinking about it can be easy but a lot of us are very distracted so remembering exactly what the code was supposed to do and why I wrote it can be hard and if I have a unit test for it I don't have to I just go look at the test the test tells me exactly what the code was supposed to do why it's there and how it should work at least if I've written the test right and we'll talk a lot more about that in the future or I guess like in the next 20 or 30 minutes right so remembering why it's is a very important reason but even I'd say more important is this middle one which is that when I have unit tests over my code and I have something that tells me if things broke I can refactor and clean up my code easily without worrying about it and you might think like why would I want to do that like who cares right what's the benefit of being able to refactor your code who cares the benefit is really just that I can clean stuff up I can see an issue in my code that makes things harder like hey it's a little bit hard to make this change here it'd be nice if my system was a little bit different so that that was easy so that I'm not constantly doing hard things and able to make it easy for myself to extend and build on my systems but that only happens if I can refactor and not worry that I've just broken everything if I don't have tests on these things you know I'll make a big change to the system maybe add in some new layer of functionality and I could be terrified like uh who knows what broke like hopefully nothing broke everything seems fine but I know that there's gonna be some random edge cases and some stuff that's gonna completely break maybe like one character class is not gonna work or you know one weapon type is no longer gonna shoot whatever it is right like do you know something's gonna happen if you don't have tests you'll get very worried about doing these big reef actors now when I have tests on my code and I've got it on a lot of it I'll refactor like crazy I'm just like hey this I don't like the way that this looks this doesn't feel exactly how I wanted it I'm gonna change it refactor refactor refactor all my tests pass oh one or two of them fail I'm gonna go in I'll fix those two tests and I'm good to go and I'm happy and I'll found the bugs and the tests have saved me but less bugs is of course the last reason on this list and that's obviously not because it's not important it's just because the other two things were more exciting but having less bugs is awesome and writing a lot of unit tests leads to less bugs especially for me part of that is also because when you're writing unit tests because it's so easy to add in different scenarios and different kind of minor changes it's easy to test the cases that that might break and it's easy to think of more cases that might break there are a lot of things that like I wouldn't think to do in game but I probably could figure out a way to do them in game and I want to test against those without having to go through that whole process I want to be able to test like if I give this thing bad data if I give it bad input is everything gonna blow up for the players gonna be able to cheat or is the whole game gonna crash and die anything bad can happen and if I have unit tests I can test for all of these things and I can test for him easily then I can put in code that checks for the stuff and just minimize my buds so let's dive into what an actual unit test looks like now so we're gonna start off with an empty project here and I've got twenty 19.2 I think any version newer than this or even any 2019 version should be very similar so to get going we need to open up our test run or window before we actually create a test so I'm gonna go to window and then believe it's under general and test run or now it could be in a different location it moves a lot so just look around for it when you do that you're gonna get this test run or window that will pop up and if you have the assets folder selected down here you should see this create edit mode test assembly folder if you don't if you already have a folder for this it's not going to be here but if this is a new empty project like this should be exactly what you see you might be on the play mode side don't go to that yet we're gonna talk about those in a little bit stay on the edit mode for now so click on edit mode and then just let it create a folder it'll name it tests I'll just hit enter to let it save that folder name and then I can create a test script in the current folder so I'm just gonna that it's actually gonna do it in the tests folder because that folder was selected and I want to call this damage underscore calculator and I left it all lowercase intentionally so if you're following along and want to do it exactly how I'm doing it the lowercase part is all there on purpose now I'm gonna open it up so I've opened the file in writer just hit enter or double click on it it opened up I already had writer cache so it loaded quick and let's give it a couple zoom ticks and make it so you can meet read this a little bit easier in fact we don't need the the project view by the way if you're not using writer and you're curious why I am I did a whole video on it it's really awesome I highly recommend you check it out but it lets you see the entire view and run our tests from from the editor or from the the IDE nathie editor okay so here we go we have a class named damaged underscore calculator with terrible naming right and riders already complaining about it in the tests namespace it probably added some using statements if we got in unit framework unity test tool stuff and then we got two tests down here we have a test attribute right here on line 12 and a unity test one we're gonna talk briefly about what the difference is but these unity tests we're not really going to talk about right now so I'm going to just delete it it's really for play mode type tests or tests where we want to test things over time we're not we're not at that point yet we're gonna start with something simple so I've cut it down to just this one test test behaves as an ordinary method that's the comment I'm gonna to get rid of that and then right here what is this use a certain class to test conditions will delete that too if we're gonna talk all about that so our test is defined here with this test attribute and this is the name of it we want to change this to be the name of the what we actually want this test to test and I'm gonna use my semi weird naming structure for tests feel free to use whatever you like I just feel like this works well for me but what I want is to know that my damage calculator well we don't want to do I want this to be a calculator that calculates damage based on mitigation so I'll say it sets damaged too our half with 50% mitigation so I think it's pretty obvious what I want this to do now if I have 50% mitigation it should set the damage to half whatever this damage calculator is now the damage calculator doesn't exist so this is where we go about if we were actually doing TDD is kind of how we would do it would write up the test and then start creating the class and we're gonna go that we're out at the beginning like I said I don't TDD stuff all the time but we're writing a simple test so we're gonna do it just this way so I've got this code right here and I need a class to test so I'm gonna go back into the project go to the assets folder right click create a new folder and College scripts just very standard unity folder will go into there and I'll right click create a new C sharp script and this one I'll name properly and just call it damage calculator nice Pascal case an uppercase D in an uppercase C just like we should have for regular C sharp stuff except for my unit tests that like I said I named weird so there we go we've got the file loaded in it's all cash double zum-zum zum-zum and i want to get rid of monobehaviour I don't want this to be a mono behavior which means I also don't want all of this chain we don't have to start an update in fact all I really want here is a static calculate damage method so I'm gonna make a public static int calculate damage and it'll take in an original damage like into original damage or let's call it a mount and then we'll give it a mitigation so float mitigation person let's call it what it is that's what I want to be really obvious with the name here and then let's just say we do something like this we return amount times mitigation percent but that's not an inch so we'll have to cast it as an int well we'll just use convert convert to int 32 that'll just convert our float result and the reason we're getting a float is because we're multiplying an INT by a float here we multiply the end here and the float so we get a float and we'll just convert it to an it and return that and then we can go back over to our code and write the rest of our test if you see an issue don't worry we'll talk about it in just a moment let's just go along with it though so we've got damage calculator let's say I've got well I want to find the amount if I pass in 100 damage or let's pass in 10 damage right let's do that so we'll say int result or mitigate Earle it's what we want to cause final damage equals damage calculator dot way up with that we're getting errors already dot calculate damage and then we pass in like a 10 and a 0.5 for the mitigation now we have an error here and it's saying that it can't resolve the symbol damage calculator the reason for that a couple reasons really one is that it's not referenced that fix it let's see let's save B nope C didn't fix it so we go in we hit alt enter and you might think okay it wants to reference that assembly what actually happens is with unit tests we now have assembly definitions and if we go to the test folder and select our tests assembly definition that actually got created with the folder when we created the test folder so when you add a folder it's not just adding the folder it's adding the folder and an assembly definition file in fact that's literally all it's really doing it's creating this assembly definition file and I want to take a quick look at it it has a couple things in here it's got the Auto reference option we're not using that it has override references which allows us to define custom references like our if we uncheck it you'll see that the end unit option goes away if I turn it back on we can turn on and unit and put in other assembly references in fact we'll be doing that for in substitute in just a little while we also have to define assembly definition references so right here it's got the two unity test ones and I believe there's a way to make it work with all of the assembly definitions what I do instead is I go into the scripts folder and create an assembly definition so we just go right here and create a new assembly definition I'm just gonna call the scripts now there is a way to make it so that it can access all of them globally but this is I think generally the cleaner way to do it so the way that I'd recommend going for it have an assembly definition for your scripts files and in fact you may need to have multiple especially if you have like a server and client and game you should definitely split those in it up but if you can splitting into multiple assemblies where the seams really make sense and I'll probably do a video about that later it really is a good idea if you can do that so I've got the assembly definition file in my scripts folder and I go to my test folder I'm gonna select this assembly definition and then right here on the references part I should get rid of this because it's a little distracting so right here on the references for the test I'll hit plus then I hit the little search dot here and you should see that scripts appears and I can just select it I could also just alternatively go over here and take it and drag it right up there onto it so with that all done I should be able to save I'm gonna do a file save project and then go back over to my code here we go it let it apply all the assembly definitions or errors already disappeared and if I go back to my code and go to the damage calculator here see that the error is gone and I don't actually need to do anything different in the code it was really just getting those assembly references set up and you're gonna find that that part is pain if you don't remember to do it might sit there and struggle with it I've done it a couple times and I'm like oh yeah assembly definition it's got to do that the other way you can do it is if all your tests are in the editor folder it's gonna automatically find them and like I said there's there's an option somewhere that allows you to just find them all I just don't remember what it is and I'd go with the assembly definitions anyway so here we are we've got a test now that returns back the final damage from calculate damage and now what do I want to do with this well this is actually part of my test this is my acting part I don't really have any setup for this so this is the part where I'm acting on a method here and I want to just assert that I got the right result so I'm gonna put that here now these comments are completely just so I can spell out and show you what the different sections of the test are not required or anything like that so here what I want to do is assert and this is part of any unit it's there because in unit is there and I would say assert dot are equal and then if we look here it takes an expected value and then an actual and I can even give it a delta to give it like an amount that it could be off by so I want to say that are equal well if 50% 5 should be equal to the final damage because I expect that final damage is 5 so the expected value is 5 and that's that so if I save that and then I go back into unity and reopen that test runner a window general test runner I should have my test here so I have my damage calculator that's damage to half with 50% mitigation let's try that so if I hit run you see my test runs and it passes it is nice and green pretty cool right but let's add another test and start to see where we're gonna get a little bit of value from this so I'll copy this test so I'm just gonna select from 11 to 19 control C enter a couple times control V and then let's say I want to do sets damage to 2 with 80% mitigation so I want to say sets 10 or let's say it calculates let's change this calculates to damage from 10 with 80% mitigation so I wanted to calculate back to from 10 when I have 80% mitigation so my mitigation percent will change to 0.8 which is 80% or anything times point to a the same as giving 80% of it and I expect a 2 there we go so now I can save this off and go run my tests again look I've got two tests now so I'll just hit run off and I've got a failure let's take a look at that so I selected the test you'll see that my expected result is right here this is expected to but was 8 and let's go look at that again let's go take a peek at our damage calculator and think why would that happen first let's get rid of all these extra using statements because they're just cluttering things up and then let's just think about this code for a second so if we have a 10 that comes in and we mitigate 80% right now we're doing 10 times 80% and that's actually not what we want right we want a mitigation should be like it takes off 80% not you take 80% that would be like 20% mitigation where we took off 20% mitigation is the amount that we want to take off when we're like calculating damage and I'm thinking of this like from an RPG or even a shooter damage than where you've got armor that mitigates the damage so how do we do this well we need to actually change this so our test here this test has found a bug in the code that our first test didn't so really important that just because you have one test on your code does not mean that the code is good or that it is covered it just means that you wrote one test for the code you need to test the right scenarios and you need to test multiple different scenarios the different possible inputs for the different possible outputs those combinations and you don't need to test every combination we don't need to go in and test like one two or 100% mitigation we could but I wouldn't write a separate test for each of those we just needed I could test a couple different spots along the way or a couple different inputs so that we have some variety here checking these different things the one reason that our first test passed was that 50% is a nice even cut so it didn't matter it kind of masked and hid the problem but once we put in the 80% one we found the issue so how are we gonna fix it well it's going to calculate damage and what we actually want is to take this mitigation percent and we want to make a float multiplier equals one F minus mitigation percent then we'll multiply by the multiplier so now if we have 80% mitigation we're gonna get point two and we'll get back at two if we have 50 will get 0.5 and still get the right value let's go back into unity and rerun our failed tests so rerun failed would just run those failed tests there we go it passes we now have two passing unit tests and if anybody goes in to calculate damage and changes the way that it works makes it do something else that we're not expecting that our code that's calling this is not expecting we will know about it and they'll know about it right away and hopefully take that into account not just blow everything up so that's one simple test it's time to move on to some more advanced stuff though okay now let's go from testing a simple static method to testing an actual class in some functionality that checks for state and state changes that we could use in a real game so here I've got some equipment slots I've got a left hand right hand a chest and legs so just picture any old game that you can put something in each hand or some on your chest in your legs and this is kind of that we've got the slots defined as an enum or enumeration so we've got 0 through 3 there with the integer values and then we've got items so items have an equip slot and they have an armor amount that's it we're not going to add anything else you're going to keep nice simple items even our weapons that are going in our hands you're just gonna give armor for now it's just 2 shields right we've got a character that has some inventory and health and then we have the meat of our code here which still not much but a basic inventory system so we've got an inventory that right now is a monobehaviour let's actually make it not a monobehaviour because if we look here there's nothing monobehaviour related is there fact we can probably delete that get rid of the monobehaviour part completely don't worry we are going to talk about mono behaviors really quickly though very very soon so here we've got an inventory class and we've got a dictionary with equip slot as the key and item as the value so if you haven't used the dictionary before it's essentially a key value lookup that's very fast so we can give it an equip slot and it will give us the item for that equip slot and we can do that right here on line 15 we use the indexer kind of like an array index or but we give it the key and it gives us the value or allows us to set the value so here we have that dictionary and then we have a list of unequipped items just to hold all the items that were not equipped don't even use it right now but maybe we'll write a test around it then we have a public method for equipping an item that takes in an item it checks to see if we already have that item equipped so we use contains key on a dictionary to say true or false whether or not we already have something in that dictionary with that key entry and if we do we just move it to the unequipped items or we add it to the unequipped and either way whether we had something there or not we set it so that we do now we put this item to be the equipped item that's it and then we have a finally on line 18 a method to get the total armor so what I want to do now is write a test that make sure that we can only put one item in each slot let's do that let's start off with something simple so we can only put a single item in each slot to do that we'll go back over to unity we'll go to our test folder and we're just going to create a new test class so I'll go right click create C sharp script and I'm going to call this inventory lowercase so I'm just testing against the inventory will open it up zoom it way in get rid of the monobehaviour base class because we don't need that and all the start and update stuff that we don't need we could have also just gone in and hit the create test with the test editor button but creating a new script is essentially the same it's just the boilerplate that we're gonna get now I'm gonna start with the test attribute so just a pad at the test and then go to the next line and create my actual method do public void and what do I want this to do it's giving me an error by the way that the missing reference is there so in writer and just hit alt enter and fix it if you're in Visual Studio you can hit a control period or right-click on it and add the reference but it needed the end unit dot framework there so let's give it a name so inventory only allows one item of each slot or let's say only allows one chest to be equipped at a time and I'm just gonna test against chest items for now so to get this test setup I first really quickly want to talk about I kind of glossed over before which is the three parts of a test the arranged act and assert let's just put these into comments arrange act and assert arranged is where we're gonna actually set up our test and that's what we're gonna do first so we need to create an inventory that we can use to test so we'll start with inventory inventory equals new inventory and just create a new inventory we also need some chest items so I'm gonna make an item the item item equals new item and I'm gonna use the initializer here so I move get rid of the semicolon then we're gonna say equip slot equals chest or equip slots chest and just put a semicolon at the end and I'm gonna call this actually I'm going to call this a chest one and then I'm going to copy that and make a chest two because I'm going to use two different chest items and then I want to make sure that only one of them is actually equipped next we want to do the acting so I want to go into my inventory and tell it to equip the items say inventory dot equip item chest one and I can even copy and paste that and just say inventory diet equip item chest - now what do I want to assert here well we'll really want to assert that only one of these two items is equipped they I don't have two chest items equipped and the more I think about it I really want to decide here which one should be equipped not not just there should be one but should it be a chest item one or chest item two if I call equip item am I expecting it to replace the existing item with the new one if that fits the slot or to just say no one stopped me and if we remember our code let's go take a look we really just replace it right so we don't we don't care and we move it to any clipped items if it's an unequipped item so we don't have a good way to test that this is actually happening in our tests we don't not have a way to check to see which item is in the chest slot for example now we could technically give these different armor and then maybe call in and check the get total armor methods total value but then we're not really testing what we say we're testing we're testing that the item is equipping but we're also testing that get total armor returns back the sum of all equipped items and that we have nothing else equipped innately and we're testing a couple other assumptions that aren't really part of our code that or at least aren't part of our tests that we're writing we really want to just make sure that the item in the chest slot is that second one so what we're gonna do is add a method here so add a get item and add a equip slot equip slots and here I'm just going to return back out the dictionary value so we'll just say let's copy this line if it contains the key equipped for equip slot then we'll return equipped items at equip slots all right let's get rid of that s there on the end I don't like that so that should return the item but I have a wrong return type I have a void so let's put item here and then if we don't have it we'll just return null because there's nothing there so save that and then we can go back into our test and just get the item that's equipped so say equipped item or item equipped item equals inventory get item at the chest slot and then we can assert dot are equal now this will tell us that - object references are the same or - non non value or - non reference types are the same - if you have to value types but here we're just doing are equal and we want to do that the expected thing which is chest - is equal to the equipped item so it's going to tell us that we have now got the second chest equipped in our inventory let's try that so we go back over to unity we run the test and we've got this one test that now passes it says it only allows one chest to be equipped at a time cool so now if something goes in and something changes in our equip items so say I say somebody is working on the inventory system and there's a change made that you know we don't want if there's already an equipped item there we're not gonna replace it you have to manually remove the item first and then then you can add them or maybe it's the just the call has gotten changed or maybe it's just a bug right maybe something something broke in here and changed and we don't do this check anymore all right we'll know immediately and maybe just return or maybe we say like if it contains that we just returned it was like oh yeah there's already an item there we'll just want to return we'll go back over when they run it when they compile they commit and automated build tests run off or something or we just come in and run it we should see oh look it failed something's going on there let's go take a look and figure out why why is that test expecting that and what is the rest of the code doing there so this kind of give you a real quick idea on the inventory or on a mono behaviors or I said a mono behavior on a non mono behavior my point here is this is cool but what if you have a mono behavior what if you've got an inventory but your inventory is a model behavior or you want to test something that's related to a mono behavior and somewhat mixed up like how do we do this because we can't go in in our tests and just create a new mono behavior with a new right so let's make this a little bit more complicated and let's dive into a character using inventory to calculate their mitigation and making sure that that whole process works it's even more complicated so to do that I've got a character here a character has an inventory and a health on it right now a character is not a mono behavior we're gonna change that in just a moment first we're gonna write some tests we're gonna write a character mitigating with a chestplate takes less damage so let's do that let's make that our test so to do that I'm going to go over to my inventory file and I'm gonna create a new class the way that I usually do it in writer just go public class character taking damage or character with inventory so do it just like that and then I select the name here and hit alt enter and then I just hit move too and it creates a new file movie sit there and I don't have to do anything basically it just keeps me from bouncing back and forth into into unity so now I'm in here I want to create a test so I make the test attribute hit alt enter and get that a missing reference in there then I'll do public void and let's say with 100 armor or let's say I'm gonna keep the numbers nice and simple so let's go back to like at 90 with 90 armor takes 10% damage so I'm gonna equate armor to mitigation at just a one-for-one obviously you probably want to have some calculation you don't want 100 armor is 100% mitigation but you scale it by your level or something else here we're gonna keep it somewhat simple and just say that 90 is not 90 armor is the same as 90% mitigation we'll just leave out that multiplication step in there so a character with an inventory that has 90 armor takes 10% damage so how are we gonna build this out first we need a character so we say character character equals new character then we'll need an inventory so say inventory inventory equals new inventory and then we'll need an item so say item item equals new item and we're gonna give the item a slot of oh let's make this one legs we did a chest for the last one and we'll give it an armor value of 40 and then I'm gonna copy that and well let's name the first one leg item and then the second one I'll call right hand let's call it shield we're holding the shield and some pants let's name is pants pants and shield and we'll call this the right hand and give this a value of 50 so I've got a shield on and some pants my total armor value going to be 90 now let's equip those items so this is again all in our range so we're still arranging we're gonna say inventory dot equip item pants inventory dot equip item shield so now I've got my item and my shield equipped I've got my character my character doesn't know what it's inventory is though so let's change that to say character dot inventory equals inventory we're just assigning that inventory right now it's just a public property really I probably wouldn't have this as a public gatoring etc but you're gonna see this in just a moment how we'll get around that so we've got all of that we've got a character here it has an inventory and now we need the method that's actually going to calculate damage for a character you know I could do this in a whole separate system in fact really I would I'd put out of a whole system here for calculating out the damage for this but we have a damage calculator and we're doing a demo so let's stick with that we'll put it into there will say damage calculator got calculate damage and we're gonna give it an original amount like let's just go with 100 let's do a thousand a thousand damage and our mitigation percent is actually going to be calculated mm-hmm let's see how do we want to do that well maybe we need some sort of mitigation calculation formula or a mitigation calculator or we could just pass in our character to calculate damage and let it figure out the mitigation for us in fact I think that's what I'm gonna do this time so just pass in the character now obviously that's gonna fail because character is not a float so we'll go ahead and do calculate damage or I just hit alt enter and I doesn't want to let me just Auto create it so I'll just go in here and we will copy it so well actually I'm just gonna copy line 5 paste that down and we'll replace the mitigation percent with a character so now our damage calculator can calculate damage for a character or worth saying that again we haven't actually hooked that up yet so let's figure out the mitigation that we want from the character to do that we'll get the inventory get the total negation so say like it total armor equals character inventory get total Armour so you get our total armor amount well what's the error there o get total armor as a float let's change this this should be an int that was just a bug in the code typo there so we get our total armor and with that we just need to well if we're going with a hundred we'll subtract 100 from our total armor sounds right yeah so we'll do in amount equals 100 minus total armor to give us that difference so if we're at a hundred we're gonna get a zero yep and if we're at zero we're gonna get a hundred in fact let's change this to a float so it's gonna be a float for 100 F minus total armor as a float seems right oh I see the issue though I named it amount so let's say this is gonna be our multiplier multiplier is 100 minus total armor so then say we've got a value of 50 we're gonna get 150 minus 50 we're gonna get a 50 here if we have an armor of 20 we're gonna get an 80 here so this might sound kind of familiar right it's kind of like this this calculate damage then we want to divide that multiplier by 100 so say multiplier divided by e equals 100 F just gonna cut it down so that it's a decimal so if it was 80 it'll be 0.8 if it was 20 it'll be point two and then we can just return calculate damage of our amount and our multiplier it seemed right oh but think about it for a moment and I'm Mike it can get a little confusing right when you're thinking about the math in your head luckily we can just go over here find our tests and run them but we haven't finished this test right like we wrote the test it passes but let's actually look at it let's right click on the test and hit open source code and actually add an assert and figure out the damage mouth so we calculating out of damage here we are expecting to take 10% damage so we'll say int totaled or calculated damage is that and then we should assert let's do our here's our acting actually we should call this out this is the act and then the assert we're gonna say assert dot R equal 100 and the calculated damage so if our math is right we didn't screw it up we should end up with 100 damage with 90 armor and that's the value we'll get back and if we did mess it up well then we're gonna get a nice clean error message that tells us and we'll be able to go in and change it and clean it oh look at that we got a nice clean error message expected 100 but was 900 I didn't have to start my coated enough to run in and go test 2 just ran it and so oh hey look that's a little bit wrong so let's take a peek real quick so our multiplier is 100 minus that if you think about it we're actually getting the inverse of what we want so do is just go 1 F minus that or we could just not do this part right so I can undo that and I could just take the amount times our actual multiplier because this is our real multiplier here we're passing in a mitigation percent and I was using the multiplier as my mitigation percent and then the names should have just said hey don't do this stupid you got a multiplier right there you're obviously flipping it again but sometimes it doesn't happen I mean a lot of time we're just distracted projects are big there's a lot to do so tests will save you again so I'll just take this line of code instead and paste that down there I'm no longer relying on this method to invert something I don't need to invert it and convert something I can do the conversion right here my codes cleaner and my test passes look at that test is good everything's good so I said we would talk about mono behaviors though right so what if this character is not that it's this what if our character is that suddenly we go back into our unit test and we run let's try run all right and then if we select the test that's actually using the character it's kind of weird because the test is passing but it shouldn't be it's actually giving an error here it's saying that you're trying to create a mono behavior using the new keyword it's not allowed alternatively your script can inherit a scriptable object so what it's saying here is that there's something wrong but it's still just not it's not getting to the assert so it's not actually failing properly this is really annoying this really should fail and it's fine we'll figure out a way around it but for now we're gonna actually go in there and figure out how to code this so that it doesn't give this error and that our test actually runs and works so let's open up the code and take a look at this test if we look right here you'll see the problem says hey mano behaviors must be instantiated with game object a tab component of type T instead of new now we can go in and create a play mode test for this or something we could create a game object then News this up runs it in a temporary scene and does all of these things that we want it's not what I want to do though because play mode tests while they're no longer a pain and they're actually pretty cool now there's still a bit slower' they're harder to set up and it's not always gonna work for this kind of scenario so say my character is a network spawned object maybe it's multiplayer game and I the whole process for setting up a character is huge right newing a character and setting all that up in the test or trying to get the network staff working and everything going might just be a little bit too much work so there's actually a much easier way around this I don't necessarily need the character in fact if we look at it let's look at our calculate damage our calculate damage really only cares about the inventory so I have a couple options one I could just change this to just take an inventory but let's imagine that we care about something else on the character too maybe we also care about the characters level in fact let's add that right so let's say we get the armor and then we also add in the character's level times 10 all right whatever their character level is so we're getting we're giving them some base armor based on their based on their character level and we'll add that as a read-only property on the character or we'll even make it not read-only so it's got a level on there and then now we you can't just go in and change it right so we can't go hey well we'll just take inventory and obviously there are a million other cases where this could come in and you can't just go in and restructure and reformat or totally redo the code because of that we need a way to get a character in here without creating an actual character and this is where all of the magic and fun really happens with mocking and substitutions to do this we're gonna create an interface we're gonna create an interface for our character and we're going to allow calculate damage to take that interface so let's do that let's go into calculate damage and let's go to character actually and then if we just hit alt enter you're gonna see an option to extract an interface I think or maybe it's a ctrl shift our extract interface and you'll find this in whatever editor you're using you should see an extract interface option somewhere it hit hit it and it's gonna pop up and show you all of the properties that are available if I just select these three it's actually going to generate the interface for me kind of magically and I'm gonna do that sit next there we go I've got an interface so the interface is well I'd say it's a key part of clean C sharp development if you're not using interfaces you're missing out if they're really there to just make things easier for you it seems a little more complicated at first it seems super stupid and annoying at first but when you get used to them it really saves time and makes things a lot easier so here we are we've got this interface and it has an inventory a health and a level and I'm going to remove the setters here most of the time I don't put setters on my interfaces unless I actually want to set the thing in our case for the character I put setters here because I wanted to be able to set those because I needed it for the tests that was it now I don't need it once I start going into this substitution route so I've got this in face here that says I have an inventory of health and a level and that's added after the monobehaviour with the comma now we can have as many interfaces as we want we'd have like eye logger eye whatever else I subsystem these are all existing ones that are just built in I can add as many as I want we can only have one base class but we can have as many interfaces we need we just have to make sure that we implement them and if I got rid of this line may I comment it out we're gonna get an error I can go up here and say hey it says that the level is not implemented I could even hit alt inter and implement it and it'll just drop it right back in there and delete out my commented version so there we go we've got a interface and a character and they're in the same file I'm going to move this to its own file again nice shortcuts in writer there also available in visual studio though and then we're gonna go back over to our damage calculator and we're just gonna make a little change what's this we're gonna take character and put neither it now takes an eye character so our calculate damage doesn't care that it's natural character only that it implements this character I character interface meaning it has inventory health and a level that's it so we've already simplified or added an interface and complicated things but we've simplified what our damage calculator relies on it no longer relies on the concrete implementation of the character now let's go to the tests so if I go over here I'd be like okay well we're almost there right we'll just change this to AI character and I character and see more red errors that's because you can't create a new instance of an interface it's just like a mono behavior right so what was the point of that kind of stupid right we just wasted a bunch of time no there's actually a shortcut there's a trick and what that is is n substitutes here it is and you'll want to download whatever the latest version is I'm just grabbing four to one you download the zip and open it up it doesn't really matter though because we're actually going to pull out in older file that's in the docs folder it's a little bit weird so I wouldn't worry too much about the version if that changes once this file is open there we go we go into n substitute and we go to docs downloads n substitute to point 0.3 point zero zip open that up going to the lib folder the.net 3-5 folder and take this and substitute dll so this the kind of the full path if you're not sure where to find this I know its opinion hopefully it just somehow gets built into the editor eventually or something or it becomes a package but this is where I get it for now so copy that file and since I'm like super deep clipboard wise what I'm gonna do is just take mine and drag it on to the desktop so that it's there and I can just pull it from the desktop into my project because if you're in a zip especially in a zip in a zip and you try to pull a file into unity it's not gonna work it's gonna say hey know here's the little cross no don't do that just get out of it take it out of your out of the zip into the desktop and then okay so I've got it on my clipboard right here or on my mouse but I need to put it into my project so I'm gonna go to assets right click on the empty folder here hit create folder and make a plugins folder and I'll go into the plugins folder and I'm going to drop that in substitute DLL right in there look at that beautiful so I've got n substitute in I really only need editor I don't need anything else so I'll just check editor and hit apply because I'm only running edit mode tests right now but I need to do something very important I need to go to this test folder select my test DLL let's apply that other one or I select my test and I do the assembly reference file or assembly definition file and I need to go to assembly references and hit plus in here I need to select n substitute and then I need to make sure that I apply that change otherwise I'm not gonna have access to all the n substitute stuff in my unit tests very important part make sure that you get that checked if it's not there it's not gonna work also since I said unsubstituted only work in play mode or edit mode right now it won't work in your play mode test so if you're doing play mode tests make sure that you've changed that to not be set to edit mode only and that was right here this editor on the option okay so let's jump back in and try building and see what happens but hey we still have the same error like what the hell Jason you said this was gonna fix it it was magic and it is but we have to change the syntax just a little bit instead of saying new character we're gonna say we want a substitute and look you can see writers already figured it out dot four and then we give it the type of I character now you may not have seen the magic that happened there but with writer it kind of automatically added this using n substitute up here that's why this is blue and good and not red get rid of that obviously you're gonna have an error and it's gonna say hey add the reference so it added the reference for us but we're now getting a substitute character cool we have an interface for a character we're getting close but we still have an error here we have this inventory equals inventory that's failing that's because since it is an interface it's an interface and the inventory only has a getter we really don't want things changing a character's inventory we have to change the way this works a little instead of setting it we do dot returns look at the magic here and then we give it the thing that we want to return which is our inventory that we've created up here now it's important to note that if I did something like this so if I said like hey it returns a new inventory and try to access this every time I access this I'm getting a new inventory I mean it's not getting one holding a reference to it and keeping it around it's just literally returning back a new inventory every time so when you're doing this you probably most of the time want to cache the thing that you're setting it up to return well I think that's it I think we can now actually call our test and our test is now going to make it so that our inventory or n substitute it's gonna make us our inventory for the character returns back to this inventory and our calculate damage method should work it's at run play look at that beautiful it all worked we got this all kind of running and it didn't matter that our real character is a monobehaviour because we just use the interface we pass that along and everything is kind of magic pretty cool but there's even more cool stuff to cover so we need to talk about a lot of other fun important things we still have to talk about play mode tests and we need to talk about checking to see whether or not something was called sometimes we can't just check state sometimes we want to make sure that a thing actually happened not that a calculation happened but that you know an event fired off or a method was called or something happened other than just a value changed did it give us the right value or did it return back the right value because so far that's what we've done a lot of did this thing return back the right value oh well actually I take that back we did do a little bit of a test here to make sure that a thing got set to the right value in fact that's kind of where we're going to extend so in our inventory system right now we have a get item we have a get total armor and an equip item but let's say I've got an inventory where when something changes I want to oh I don't know send a message to the client and say hey go show this item visually or hey you just you just put on your pants I'd send some message to them right well let's keep it simple and just go with like a text message but you can imagine this could be anything from swapping a UI element to sending a message to the the client or something else may sending a message to everybody around or doing some other random thing so what I want to do is make it so that whenever we equip a new item on our character then that character gets a message that says hey you just put on whatever so let's do that to do that we're gonna well we're gonna have to think about this for a moment how would we set it up I think what we'd want to do is make it so that whenever we equip an item we tell our character hey this new item has been equipped and then you can go do whatever you want with it right so how would we do that well we'll make a method or actually let's do it in equip item let's say our character underscore character dot handle or on item equipped and we'll pass in item now you're looking at this like hey it's red what the hell's character we never made that what's all-night animal if we didn't make that okay that's how I like the code so we've got this here and we've got a an idea that we want our character so we want to have a way to access our character constantly on the inventory so I'm going to refactor this inventory just a little bit and I'm going to add a constructor watch this will do ctor right here in just a tab it's going to generate a constructor constructors calls whenever we create a new inventory this will get called but we're not going to make it a parameter or lessons constructor we're actually going to take in an I character and call that character then here we're gonna say underscore character equals character and I'll go over here hit alt enter and create a field so now I have a private field for our character that it's actually saying I could make read-only so why not hit enter and make it read-only and this is going to keep track of the character for this inventory or the owner of the inventory by the way the read-only property here just means that once it's set by this constructor and only by this constructor it can't be changed again so it's just saying hey nothing can change this the reason you do that is so that you don't accidentally change it if you're in a scenario where the owner of the character for a thing is never gonna change then making it read-only might seem like a little extra ceremony but it's really they're just there to make sure that you don't accidentally do a typo and change that or make it no lore do something weird to it that you didn't intend so there we go we've got our character it's now cached in our inventory let's let's shrink this down our errors aren't really valid anymore if I build they'll go away and then we have this need to call and on item you quit so let's generate that I'll hit alt enter create a method there and it's just void on item equipped that works I want to change this to an action though and hit alt enter to add the using system statement up above so this way I can actually just register for it more like an event so I've got on item equipped actually you know what let's change it back keep this simpler let's go with just a method so I've got an item equipped on a character and then we'll go over to our character and we need to implement the interface see how it's read that's because we don't have the on item equipped if a mouse over I can even see the error message but I can hit alt enter and implement that method and then right here I'd want to do something like debug log you equipped the item and I'm gonna put a dollar sign up here so that my string interpolation works by the way if you haven't used string interpolation it's a great feature in c-sharp you basically put a dollar sign before your quotes and then you can put the little brackets here and a variable inside and it will just to string the variable if it's a string cool if it's a number let's turn to a number here it's just gonna do the game objects name I don't care though I just wanted something here to say that I've equipped the item in fact I'll even do it like this in item dot equip slide so I've equipped the whatever it is in the equip slot is the log message that I expected yet now again in a real game this might be like a popup message on the screen or a network message that's sent across or something else but we really just want to make sure that whenever we equip an item the on item equipped message is called so how would we do that we'll go back over to our inventory really because we want to test this on our inventory and uh first we need to fix this test because our inventory no longer works without a character but we just pass in a no I wonder what's gonna happen oh nothing because we can't pass in oh yeah we can't there we go so we pass and no if we do this though I already know what's gonna happen I said I wonder what's gonna happen but I really just wanted you to wonder for a moment but if we look here at equip item what's gonna happen is character is gonna be no and it's gonna blow up and we're gonna get an error not exactly what we want so we need to give this a character or we need to make our inventory code not care if there's a character in our case though I just wanted to have a character because I can't think of a case where I want to have an inventory that doesn't have a character assigned to it so again in this - I character equals or character equals substitute for I character Oh let's fix the casing if I can get the casing right this is actually very simple and then I just drop that in and I'm done this is the benefit of n substitute right I need the thing but I don't really care about it I just need like a mock version of it something that stubs it out or fits in there I can just make a substitute for it and my test works and I'm still just testing the inventory so what I want to test now is that the inventory tells the character when I've equipped an item successfully so I'm gonna copy that error that test and I'm gonna go down here and just rename this so tells character when an item is equipped successfully so what do I want to do well I want you clip a single item cuz I don't really care about equipping two items and then I don't want to check that the equipped item as the chests because I'm already testing that somewhere else and I don't want to equip that it's test two just to because that's not true at all what I wanted to check is that something on my character was called I want to check that the item equipped was called so what we're gonna do is say character dot received and this is another one of those special calls that's available because of n substitute and it does take a parameter so we can give it any number of calls that we expect here I only want one so we'll just leave it out the default which is blank or one and I want to receive on item equipped and what item do I expect to receive well it's just one that's it so this should tell me whether or not I received a message for equipping chest one now let's go in here and let's break it first before I run it let's just let's imagine that I'd done it the TDD way and wrote my test first and then let's go in and fix whatever error we have here oh the inventory here on our constructor wants the character so I need to pass in the totally missed that try to build one more time control shift B or f6 depending on your editor by the way there we go build succeeded it's looking good we've jumped back in here and let me run our new test let's just hit run all and see if everything's working nope so this one is failing because it expected to receive a call matching on receive on item equipped with that item but actually received no matching calls now if we go back in here and we go to our where was our method in our inventory inventory there we go and we re enable the on item equipped come back in and run our test so I just uncommented or deleted the commented part here we go our test now successfully passes or is successful in tells our character when an item is equipped so I know this is going really long and I'm trying to get into a lot of different examples but I don't think I can stress how important separation with these interfaces in being able to write in easy substitutions is it makes it so that you don't need to do a whole lot of setup in your tests you can kind of minimize that it also makes it so that you can test pieces in isolation right because what I'm really testing here isn't that my character sent a message right that sending messages works or that debug logging work sent we care about that I care that my inventory system is doing what it's supposed to do and I want my tests to just be about that not about everything else so getting the separation with these interfaces is what's gonna really kind of make that happen to make that easier also writing tests will kind of lead you in that direction where you'll start to see that writing these interfaces makes a lot more sense and makes things easier for you to write tests and to have just clean code long term so before we wrap this up I said I was going to talk about playing mode tests and I want to do it because I think it's important and it's um something that a lot of people are interested in something that there's really not much coverage of and I'll probably end up doing a full totally separate video just on play mode because I think I can go on about that for hours but let's go over the basics real quick I've got my test runner here and I want to create a play mode test assembly folder so that we can actually make play mode tests so I'll go to the assets folder and hit create play mode test and I'm gonna call this play tests now I have the option to create a test in the folder but I want to open it first and take a look at it so let's look at the assembly definition that's in here notice the difference it is not only editor that's pretty much it right the other one was editor only and this one is any platform because it's play mode it needs to be able to run an actual test like it needs to be able to run in real play mode the play mode test is gonna actually start up a scene it's gonna create your objects run and let them kind of do their thing and then let you test that stuff actually worked over time the way that you expected so let's create a test so we created a new test and I'm gonna call this a rigidbody test or let's call this player movement and we'll make a simple test that checks to see that if we give input we can move based on that input I think that that's simple enough to explain and go over in a really quick time but show so that we can have a thing moving the first thing I want to do is well after zooming and delete this other test we're not going to use the regular test attribute here we're going to use the unity test attribute and then I'm gonna change this so that it's a not named something that doesn't make sense we're gonna make it player movement with positive vertical input moves forward simple enough right so that if we have positive input on our vertical axis our player will move forward now we need to make a player class so let's do that I'll go over to my inventory script go to the bottom say public class player : monobehaviour because we're making monobehaviour to test and then I'll go select it and move it to its own file yeah nice simple way to do it without going in and right-clicking in unity because I just find that to be a little bit slower now our player is going to have an update because we want it to move and we want it to move based on player input so we'll read the player input now we generally would do something like this right like bar or hosted float vertical equals input get access vertical we read that vertical input and then we could move based on the input let's say that our player has a well let's just add a rigidbody to it so our player is going to expect the rigidbody let's go in here and add an awake and we'll just say rigid body equals getcomponent rigid body so we're just going to assume that we have a rigid body on here and we'll create a field for it in fact we could even have the require component type of rigid body up here just so that we know hey this requires a rigid body so we've got a rigid body here and we want to just add force in the direction of it in fact the other thing I want to do is say that rigid body I use gravity is false because this one I just want to test him it's just moving around I don't want it doing anything weird with yeah with gravity and falling so here we're gonna say a rigid body add force well let's give it a vector let's just do vertical or goes to zero comma zero come vertical so x move speed and we need to give a move speed here just that we have some float whatever 100 F so what we're going to do now is add force in the vertical Direction times 100 very very very simple okay so how do we test this though because we can't write if we want to try to test this the way that's written right now we really can't we can't go in and change the value of this axis so we can't see what this thing is doing if it's actually moving so we need to make a small change and what we're going to do is wrap our input so instead of reading input.getaxis we're going to read player input dot vertical do it like that so say we're gonna read a player input dot vertical but now I hate a player input doesn't exist what's that well let's create it so we'll create a type for it it's going to automatically create a class I don't make it public just because I like to keep it public or private for these no need for internal and extra complication so we've got a public class player input and it needs a public float vertical and that is going to return input.getaxis you might have guessed it vertical now if you've never used an expression body property before this is the same as writing a getter with a bunch of parentheses and the words return input not get access so we're kind of short cutting it so we don't have to write as much code now I want an interface because I need to be able to swap this in and out I need to actually have a reference to it in my player and I need to be able to swap it in and out so we're gonna add in i player input interface back this time I'm gonna go down here and type it public interface I player input and we're gonna add a I almost typed the word public and do that a lot but we're gonna they float vertical with a getter you know you don't need public because interface means the public parts of it so it's always public now I'll copy that interface right there put it up here so that our player input is now implementing I player input and oops I messed that up that's not a comma should be a colon a colon right there so now we've got a player input that wrap or implements I play our input and we've got an interface for that I'm gonna move this to its own file and we'll move i player input to its own file as well just get those out of here now what are we gonna do well we still have this and that's definitely wrong we need to actually create an eye player input here so we'll say private or X let's make a public eye player input player input with a getter and that's it so now our player would generally create a new player input or have a player input assigned to it what we're gonna do instead is now be able to mock this so imagine we're doing our setup of it maybe we have a setter for the player input where we set the player input probably not on this property probably in some sort of a setup method like an initialize player probably do something like this like public void initialize player I player input player input or a sign player to input so that we can customize and swap out their inputs maybe it's a different controller or a different control scheme or a network system instead of a local input something like that hopefully you understand the idea though that we'll be able to swap out the input wherever we are including in this test so if I go back over to our tests our player movement test and we want to create a player now so it'll go well create a new player we'll create a new game object and we'll add this component so we're gonna say game object game or cost player game object equals new game object and I'm call player then we'll say player game object add component of type player and what I want to assign that so say player equal player player equals that now I have an error what's the error let's do a build see what's going on here so it says that it can't find our player why can't it find our player class well let's take a peek so our player is here in our regular scripts folder right let's go take a look so scripts we've got our player right there and we're running tests from our play test folder hopefully you've been along and paying a lot of attention and think oh yeah we need that stupid assembly definition reference so we need to go select the playtests assembly definition and hit the plus then go take our scripts and assign that assembly definition or hit the little pop-up thing to search for it now we apply come back in and let's try a build again everything should be better though so if you run into build errors or reference issues like that and just go double-check those assembly definitions make sure that they're right and that they've actually applied so here we go we've got our player movement script we're now able to add a player to it now I want to assign a input system so I'll say player dot input dot returns that I can't do anything I can't do any returns right but I can just set it to a mock so since we're testing the mock I can set it to some other player in but now I could either create a mock player input or a new player input and try to do something with it or I can do a substitute dot for i player input and add the using statements here so we need the using statement but look same error right and substitute it's not working we didn't get a using statement up here did we oh oh we did let's try building though see if we get in there yep so we get an error that substitute went away and that player input can't be assigned so actually what I'm going to do is make this just assignable for now because we're actually testing the mono behavior and then we'll look at the error and see that we're still getting the problem here yes we should yeah and substitute can't be found now the reason for that is that we have on our end substitute assembly this DLL here only editor selected remember I showed that before and I need to actually select standalone as well and then a hit apply with that I should be able to do my tests in play mode but I need to go back here and select this play tests assembly definition and just make sure that my references are still good so as soon as it finished is doing my selection they're going to apply and do we go select this assembly definition and see it's spinning right there should let's try it one more time Reece like that just I want to make sure that n substitutes actually reference here so there we go we add it and reselect and substitute and hit apply and I'm going to go double check it on my test folder as well just because I had made some changes to that DLL and I just want to make sure that it's updating and that the DLL reference didn't break cool so that looks good and it looks like our error messages are gone so we should be able to jump back into the code and it turned blue if I do another build everything should be good cool so what are we doing all that for why are we creating the substitute here I'm doing it kind of stupid right because this substitute is I being assigned but it's not doing anything what I want to do is say player dot player input dot vertical dot returns one so now I can assign the value of vertical to this mocked in input and test the actual player without needing real input and without needing to write code to test the input right I just say hey vertical returns one I wrote one line of code nothing special at all so if I've got a vertical value of one now I just want to make sure that I think actually moves forward so instead of just gilding returning no let's do you'll return new wait for seconds and let's do something long like 10 seconds because I want to really show what's going on here so this should move us in our forward direction right but first let's do something a little bit more fun too let's add a set of cube to our player just so we can see him so I'm gonna say what do we want to call this let's say bar cubed equals game object create primitive cube primitive type cube and then it's a cube that transformed a set parent to the player game object that transform and we'll just zero out the position vector 3.0 so here we're just creating a cube that's a child of our player that's gonna move along with it just don't see it visually and see what's happening there so let's go to our tests so I don't go to window in general in test runner and now go to the play mode tests and I have my one test here and I'm gonna run it so the test is running I don't have a camera though look at that you can see stuff going on here in the hierarchy but nothing's showing up here because there's no camera so what do we do how do we know what's gonna happen I ran for 10 seconds and it passed it not super helpful right because we haven't actually asserted anything and I don't really know what happened so what I'm gonna do is take my game view and just drop it down here just kind of get it out of the way for a moment so that our scene views here and I'm gonna rerun the test and you might have seen something just go flying by I'm gonna pause I just hit pause and I'll go select the player just hit f2 go to it and let's just step forward one frame and look we can actually see it is moving forward so it's moving along that z-axis it's doing pretty much what I would expect it to do now if we go back to our code we can do some asserting here so let's assert that is true and I'll just assert that the player transform position Z is greater than 1 or greater than 0 this really it should be more than 0 if it's moved in the forward direction it's gonna be more than 0 and I don't need to wait this long I'll just wait like 0.1 seconds so now I'm waiting a tenth of a second and making sure that it's actually moved in the right direction in fact I could even assert that these are equal like say are equal that the Y position is 0 so do like a r equals 0 comma Y position and copy that and paste it again I could do the same for the X that it's only moving in that Z position I could come back here and run that test and see almost immediately if something else is making it move in a different direction which way what it looks like it is so what is it that's in the wrong position let's take a peek and see so it says line 27 if we look over here it's player movement : 27 means line 27 is where the error is so saying that it actually hasn't moved so the Z position is not greater than 0 I might need to just give it a little bit more time to start moving though because I'm adding force here in my in my player I'm doing an add force I'm not just uh physically moving it so it might take another frame to just get going and move so let's do that let's rerun it with a slightly longer delay and we're good to go and I found that in a lot of unity tests when I write the play mode tests they generally need a little bit of time because game objects need some time to change and do their transitions rigid bodies speed up they don't immediately go to a speed unless we set the velocity or something and you'll find that just throughout general game object stuff that you're kind of waiting a little bit longer that's why play mode tests are so much slower but they are easy to write again you can do all of this very very simply you can set up the entire environment have a little setup script part which just use the setup attribute it set up an entire environment and have it run through do some things and then tear it all down it's important to note though that when you do this you do get one test scene so it creates a scene and it runs all of those tests in that scene you can do some management of that if you want but just clean up your things as you're working with them and in general most of your tests should probably be edit mode edit mode tests aren't the easiest to write for existing code but they lead to much better code as you're kind of developing them and getting better at them and writing them anyway I think I've gone on probably long enough if this kind of stuff is interesting just the main thing you can do is share it but I also wanted to say a special thanks to everybody on patreon and all the subscribers and everybody who emails me you guys are awesome I love doing this stuff and I have a lot of fun with it so if you can share it and help me do more of it it's awesome I appreciate it if you have any questions please just drop them below I'll try to answer them as much as possible and I'll try to do some more videos on unit testing as long as everybody is as excited and interested in it as I am all right thanks again bye you
Info
Channel: Jason Weimann
Views: 65,860
Rating: undefined out of 5
Keywords: unity test runner, unity unit testing, unit testing, game development, unity 3d, unit testing unity, unity (game engine), unity3d tdd, nunit, unit testing unity3d, unit testing in unity, red green refactor, unity3d college, gamedev, unit test, unit testing tutorial, unity3d test driven development, game programming patterns, tdd in unity, integration testing, unity, unity3d, game dev, test driven development, tdd, c#, game programming, refactoring, unitytest, unity unit test
Id: qCghhGLUa-Y
Channel Id: undefined
Length: 86min 42sec (5202 seconds)
Published: Mon Sep 02 2019
Related Videos
Note
Please note that this website is currently a work in progress! Lots of interesting data and statistics to come.