Introductory Tutorial on Unit Testing Python Functions with Pytest, Visual Studio Code, Command-line

Video Statistics and Information

Video
Captions Word Cloud
Reddit Comments
Captions
when you're writing a function or some other unit of functionality that has some non-trivial behavior to it and you're needing to test the progress that you're making and be sure that it works in a variety of different use cases and for different inputs it can be tedious to try and do this manually where you write your function you change something about it and then you open up a new repl you import that function you try out some inputs and see if you get the expected return values well in the software industry there is a practice called testing and specifically what we're going to be looking at today is unit testing where we are trying to look at can we easily and repeatedly test some unit of functionality in our code we're going to be doing this with a platform called pi test which is a very popular python testing framework that is freely available online and if you have installed the course's requirements you have pi test installed already so with pi test we're going to be able to write what we call test functions and easily run those functions to confirm whether or not some other function that we're trying to implement like our the subject of our our testing is is properly implemented it turns out this is how the automated grading works on rn2 so this is a very valuable skill to have and it will help you write functions with more confidence in this course and in the time ahead so let's take a look at what pi test is and how we can actually make use of it so the big idea of testing in software is we can write a program or some unit of functionality and then we can write some other testing unit of functionality that makes use of the code that we had implemented and says okay i'm going to try you with some inputs that i expect to result in some return value and confirm that it actually does and so we can write software to test other software and this is how we can automate the testing of our implementations so that you can have confidence in the code that you write doing the things that you expect it to do and as you're changing parts and working on some unit of functionality if you accidentally change something that broke some test that you were previously passing it should be easy for you to know that without having to go back and remember to run those earlier tests from memory right and so what we call that piece of that idea is called regression testing so we we're adding some tests to avoid us regressing to a broken state and from not having bugs that we may have seen earlier by trying to fix something unrelated to some other tests that we've made so the idea of how we're going to go about this is there's a certain strategy we're going to follow okay and so we're going to implement the skeleton of a function which is just the name the parameters with their types and the return type and then the skeleton is also going to include a return value that is sort of nonsensical we call it a dummy value and it doesn't have any real meaning it's obviously not a correct implementation of the function we're trying to write it's just something that will satisfy the return type and say okay this is a valid function that you can actually call it'll give you back something of the correct type but you know that it's a nonsensical value once we have a skeleton function implemented that's when we can go and actually write our tests so we're going to think of and maybe write down on paper some of the use cases which are like what arguments can we give a function and what do we expect it to give us back if this function works as it's supposed to and we'll write those out and think through those and after we have those we can turn those into actual test functions and test cases and those test functions are going to say okay here are your inputs i'm going to go call the function that you've implemented and test whether or not it's returned value is equal to what i expected once we have some failing tests which we will immediately right because we're going to have a skeleton implementation that doesn't do what it's supposed to do then we can go and work on our implementation and re-run our tests to see okay are we actually making progress towards completing this function in a way that satisfies our expected behavior of it we keep doing steps two and three or three and four until we realize that hey there's really no new novel test that we could write that will demonstrate some behavior that isn't already demonstrated as working already and that's when you know that you're kind of done hopefully with a function and so this gives you a framework for helping you develop code that works in a variety of different use cases and allows you to with more confidence change the code that you've written and be able to test it against everything that you've already tested it against before with very little effort compared to doing this manually in the reply all right so we're going to try doing is implementing a total function which is a simple summing or summation and what we'll try and do is just add up a bunch of elements in a float list and try and compute a sum right so our first step is to implement this skeleton function and i've got an example here on the screen but i'm also going to work with you in vs code on setting this up so i've set up this ls24 underscore module because we're talking about modules and testing and in the previous video we looked at modules more specifically because we're going to need to make use of them when it comes to testing and i've gone ahead and imported the type list so we're ready to go and we're going to define our skeleton of total so def total is going to take in as a parameter i'll call it x's which is just a list of floats and x's is kind of a a generic commonly seen parameter name when you're taking in a list of numbers or an array of numbers and this is going to return what well we want this to compute the sum of the list of floats so it's going to return a float right and i'm going to go ahead and set up a comment here total returns the sum of x's and for now this is just a dummy function so i'm going to return negative 1.0 right this is just a skeleton we know this isn't correct but we have a correct function definition here in terms of the types that we're thinking of it says it's going to return a float in our signature and sure enough we're returning a float down here but we know it's not correct right so this is a skeleton function it's not yet doing what we want it to do but it's enough to use this function we can call it and it's still a valid function all right so now we need to think of some example uses well you could imagine that total of 1 2 and 3 should return 6.0 right total of 110 should return 110 and total of the empty list should return zero we could also imagine some decimal numbers like total of 0.5 comma 0.5 in a list should give us 1.0 right and so it's important to try and force yourself to think of what are some of the expected ways that this function might be used and what are some of the unexpected ways this function might be used and here you know i think the most expected way to use this total function would be you're trying to add up a list of numbers where there's more than one number such as the first example well the second example what happens and are we sure this is still correct if we have only one number and then the third is truly an edge case which is does this work even when there are no numbers being given to total as a part of its float list right so let's try turning those into actually something that can be tested using pi test and we're going to look at pi test specifically because it's got a really nice design that allows us to just write some simple functions without having to do a lot of jump through a lot of hoops and set up a lot of scaffolding infrastructure which some other testing modules need or frameworks need so there are many different testing frameworks available to you pi test is one of the leading ones available in industry that a lot of people love for its simplicity and that's why i love it as well so let's jump in and what we're going to do first is set up a testing module so we're currently working in our implementation or our definition module right and that is the file named ls24 underscore module here if you want to make a test module we're going to set up a new file so i'm going to have this selected i'm going to be inside of my lessons directory and set up a new file and what we're going to name this file but can by convention is going to be the same name as the module that you're testing followed by underscore test as a suffix right and so notice the module that we have our definition that we're trying to test so we're going to try and write some tests for this total function and to do that we're going to set up a testing module and the way that you set up a testing module in pi test is you set up a file that ends in underscore tests.pi and this is one of the conventions of the pi test framework and from here i'm just going to go ahead and say this is an example of a test module in pi test as a doc comment and we're going to go ahead and from ls24 underscore i got to give the package name so from the packages lessons dot ls24 underscore module import total and for now since we're only this is a testing we're testing only the functions in some other module it's okay to implement import the function names directly right so now we can actually use the total function inside of this test module but we need to learn how do we write a test and there's another convention that we're going to follow for this so the name of a we're going to define some functions and if we name this function in a specific way they will be considered tests in our testing module and the convention is any function whose name begins with test underscore is going to be considered a test so let's try adding our first test definition so i'm going to say def test and then maybe empty or total empty and all tests are just empty or parameter lists in most cases until you start writing more advanced tests that we're not going to be looking at in this course and it returns none right and what is the test going to do well we're trying to demonstrate whether or not some function call that we're making to our total function actually gives us back the right expected return value so we want to say something like is total of the empty list going to be equal to 0.0 right because we're trying to sum up a list of floats and then empty list should just be 0 0 for the total or a value of 0. well in order to make a boolean condition like this a part of a test we need to use a special keyword in python called assert and what this assert keyword is doing is saying i'm going to try and make an assertion that whatever boolean expression comes after me is true and if it is true it's all good nothing special is going to happen but if for some reason it's false then what's going to happen is it will cause this test to fail and when a test fails we'll get some some diagnostics on that and be able to look at well which test of our test cases did fail and we'll see a little bit more of that in just a second right and uh if we try running this now so i'm going to ignore some of these red squigglies for now and i'm going to oops it's kind of scary looking i'm going to clear my screen there are two ways of running our pi test test and i just want to give you a demonstration of what this looks like first from the terminal and then from a tool built into vs code so in my terminal i'm going to say python m and then pi test so i'm going to run the pi test module that we've already installed via pip and we installed that earlier in the semester as part of the requirements.txt for this course that has a list of all the packages of third-party packages we're using throughout this course pi test was one of them so you should already have it installed and then pi test if we want to run it as a module we can give it what is the actual file name we want to test so i'm going to give it a path to the file so lessons slash ls24 module tests.pi all right so i'm going to try and test the lessons directory and then in that directory the module file name is ls24 underscore underscore module underscore test.pi and we get a lot of output here so let me try and make this a little bit bigger for a second so what we're seeing is let me go up to the top and here we see that tests total empty is failing and why is it failing oops i think that was output from a previous when i was testing something earlier so let me look at this here we go so i scrolled up a little bit too far i was seeing some output from some functions i was playing around with earlier that we'll get to next but the command that i ran was this one so this is the pi test command that we just ran together and notice now there's one failed test that's a single f and the failures show test total empty and here it's telling us this this carrot is telling us that the line that our test failed on was this one and this error is telling us okay we asserted that negative one was equal to zero and we know that negative one is not equal to zero that's false so this caused our test to fail right and so if we were to go look at well why did this fail well in module dot pi we see that oh yeah of course it would fail because we only implemented our skeleton we're returning 1.0 if we did something silly and we said let's return 0.0 instead of 1.0 and fix our problem here right we could try running this again just to demonstrate that we can in fact pass our tests so i run this again and what do you know we've passed our test we've got a working function so i want to go ahead and point out something that's just hopefully obvious just because you pass tests doesn't mean that you've got a function that does exactly what it should do your tests have to actually be testing the whole range of things that a function is expected to do in order to have confidence that it's it's correctly implemented here we know that this isn't going to be correct this is only correct in this one case so let's go add another test case that will prove to ourselves that okay this function isn't actually fully implemented yet so this is our first test case and i'm going to go ahead and add another one and we should be for good practice commenting these and say a total of empty lists should be zero right so it's good news or a good practice to add some doc comments to or doc strings to your test definition so def test total single item and again these test functions are procedures and total of a single item list should be the first values i the first item's value okay and we can say okay assert and what we're going to assert is the total of a list with say 110.0 is going to be equal to 110.0 all right oops had an extra equal symbol there great now what are we going to do how do we know whether or not this test is going to pass well we can go run that exact same command again let me scroll this up and i'm going to try running this command one more time with the pi test from the command line and notice at the bottom we see that one test passed in the other field right and that makes sense that's sort of what we would expect the very first test is of course passing and the second one is failing so if we went and looked at uh well which is the one that failed let me make this take up my whole screen again that is the one with the single element in it right the single item and we asserted that 0 which was the evaluation of total with a list of 110 equal to 110 is false right that's not true so we've got more work to do right so what do we do well we could go ahead and try correctly implementing this function so let's just make a correct a very simple implementation of this and say something like result is going to be a value we accumulate the sum in and it will be a float variable and it will initially be zero and i'm going to go ahead and return result right and so i'm i'm trying to think ahead to ultimately i know i'm going to use this variable to build up some accumulated value that's our sum so i'm going to try and go ahead and remember that at the very end i need to return it and then our work is going to be well how do we actually go through each number in our list named x's which is our parameter and add each float that's in that list to result well we can make a note of that so um for each item in x's or for each uh x that number float x float in x's um add it to result all right so that's our english representation of what we're trying to accomplish and what we can do is say okay well 4 and i might say x in x's right so we're going to use a 4n we know that we want to sum up every number in this x's list every float so we're going to move through each of them using our 4 in loop and what are we going to do well we're going to add it to result by saying result and then we're going to use the relative addition operator to reassign uh result is result plus x right and so this is going to loop through each value in the x's list that's our parameter right that's our parameter that we defined right here and one by one it's going to add each to result and then finally we're going to return result okay so we think we've we've got a better closer implementation of this function let's see if we're if we're passing our tests now so i'm going to run that program one more time for my terminal and sure enough now both tests are passing but we haven't yet added a test where three values are being added together alright so i'm going to add one more that is def test of total with mini items because these test functions are true functions you have to give them unique names you can't use the same name for each of your tests and total of a list with many items should be their sum all right so we might assert that the total of 1.0 2.0 and 3.0 is going to be equal to 6.0 all right 1 plus 2 plus 3 is 6. and if that's true then we must be adding up each of those individual items to compute the result so i'm going to go ahead and try running my test one more time open my terminal go back to the previous command and boom all three tests are now passing so we have some confidence that this test that our function is working not only for the empty list but also for the single item list and for a list with many items in it and i want to convince you that as opposed to making some changes in your function implementation starting up another repl going back and calling all your that function with all the different various inputs you would expect as arguments and testing whether or not in your head remembering whether or not those returned values are correct this is a much more automated way of in a much more repeatable way of being able to test the function that you're working on now i want to point out that vs code if you have the extension that's the python extension as well as our our pylance extension both of those are made by microsoft we can actually have a user interface for running these tests that's pretty neat so in your sidebar if you have the python test or the python extension installed and the way that you can see whether or not you have that installed is you can go to the extensions tool on your toolbar which are these three squares with the fourth one that's being plugged in right so these are our plugins or our extensions and if you search for the python extension the most popular one is made by microsoft and i've got it installed you should be sure that yours is installed and you may need to restart vs code after you install it if you don't have it already once you do you should see in your sidebar the option to open up the test tool which is either going to show up as a a beaker icon or if you are like me and you you run out of icon space over here this the three ellipses you can click on to go and find your test tab and you'll notice that in the test tab oops let me go back to it so i'm opening up my tests i can press this button to discover tests so i press refresh and if everything worked out then i should see lessons as well as the test that we just implemented so notice that's pretty cool we've got this file that has our tests in it and then those three test functions which we just wrote and if i press the play button all three of those functions just went in ran automatically and all i need to do to retest a function based on some tests that i've already written is is use this interface rather than the one that's in the terminal to do so now i should say that one of the caveats to being able to use this tool is you can't have any syntax errors in any of your python files in your workspace right and so one of the ways that you can learn well do i have any syntax error somewhere is actually let me just demonstrate this because this is such a common problem that you'll run into in the early days of programming that i want to show you how to find your way out of it in case it applies to you so i'm going to open up this module.pi file and i'm going to have a syntax error i'm going to forget the colon at the end of this line all right so i went from having a colon after my for loop statement and i'm going to delete that and save this file and now if i go back to tests and i press this rediscover test button we see uh oh test discovery error please check configuration settings and that looks kind of scary all that's going on here is we have a syntax error somewhere in one of our python files and that's the one that we just created right so i could fix this by adding that colon and pressing refresh again but i want to show you because sometimes it's hard to know which file does a syntax error occur in we can also find this out and the way that we encourage you to look for these errors is in your python terminal so from the python terminal if you just run python m pi test what you'll see is there is an error during collection so collection is this idea that it's scanning all the files in our project and trying to see which of these is a test module that has some test definitions in them or test functions and this output looks a little bit scary but you'll notice that in this red text here it's telling me in that file that i just changed module test.pi on line three we're actually on line nine if we look a little bit sorry line 10 if we look a little bit further and it's telling me that there's invalid syntax right here so i've got a syntactical error in my pr in my program so if i go back and i add that colon to fix that error i should now be able to discover tests so i'm going to go back to my tests and press refresh again and there we go and so that's pretty great i can run all tests across all of my projects with this top button but typically when you're working on a given project or a given exercise you're encouraged to just run on the tests that are most specific to it just to convince you that if we were to i'm going to actually have an error in my test again so i'm going to return 0.0 instead of result right if you have an error and you run your tests so i'm going to run all tests in this file notice that when there are tests that fail you can see which ones failed and if you click on one of those tests vs code will open it up and if you hover over it'll tell you okay uh notice that in the the it says fail assert 0.0 is equal to 110.0 and that gives you some sense of where did this test actually go wrong all right so i'm going to fix that one more time and re-run my tests over here and boom it looks like we've we've got a test in working or a function in working order when you have syntax errors like you saw me just demonstrate you've got to fix those first before you can use either the built-in tool or python from the command line so do be careful of that all right so that's an example of working on a total function for practice let's try implementing one more of these types of functions and to get a feel for this workflow that we've just been through and go through it once more again so we just added and in the slides there's some reference for the total function and some tests on it and when you're writing functions that are and you're following what's called a test driven development where we're going to write some tests before we worry about trying to implement our function correctly and then that can help guide the implementation of that function and give us some confidence that what we're doing is working you should try and imagine first and write down what are some of the expected inputs that you think this function would need as arguments and what it would result in giving back to you as a return value and imagine how this function should behave as if it were already correctly implemented right so there's yes you're still going to have a lot of work to figure out how do you correctly implement a given function or algorithm but starting by thinking about how it should work and how it should behave will help set you on the right path and it will help you write tests that will confirm whether or not your function implementation is behaving in the way that you expect it to so the key questions that you should ask when you're trying to think about what are my potential arguments and what is my expected return value or what are some usual arguments you're writing a function because it's supposed to serve some purpose and how should somebody how would you expect somebody who wants to make use of this function would actually make use of it you call this a use case this is how you expect someone to use your function if they were doing so in a in a pretty normal way but when you're engineering you also need to be careful about ways that are unexpected that someone might use your function in an unexpected way we call these the edge cases because they tend to occur around edges or boundaries of our problem we saw an edge case in the total function where we i would call the empty list in edge case you know it's not typical that you would ask a total function to add up all the values in an empty list but that's still that case still needs to work and be valid and so that's what we would call an edge case then think about given these different arguments as inputs and these potential different calls what should the expected return value be in the usual arguments or the use cases this is pretty straightforward most of the time in edge cases it can be a little bit unexpected as to what should you do when you're given sort of bizarre inputs that aren't fitting the expectations of the function but are still validly typed inputs so we're going to take a look at that in the next example and we're going to look at implementing a function named join and think through some of these questions that we just thought through or mentioned on the previous slide so we're going to write a function named join it's going to be given an int list of x named x's and each and it's also going to be given some delimiter and by a delimiter that's a string that we're going to put in between subsequent values of our list so what we're trying to do here is given a list of integers and some delimiter we're going to produce a list or sorry produce a string that behaves as such so let me just go to my pen here and oops uh for some reason pen isn't working so if you look on the slide where my mouse is this uh string 1 2 3 notice that it's separated by dashes and those dashes are the delimiter that this function was called with so join has two parameters a list of integers is the first parameter and a delimiter is the second parameter and it's going to return a string where each of those items in the first list is going to be separated in the string that's output by whatever the delimiter is right so one two three with a delimiter dash is going to be 1-2-3 and notice there's not a delimiter on either end of one or two only between subsequent values in our list so i want you to try pausing the video here and thinking about what are some usual input parameters when considering a function like this how would you expect this to be used and one example here is sort of shown but try and imagine one where you're trying to produce a list that you could drop into say a sentence if you had some sequence of numbers that you might want to add to a sentence what are some unusual input parameters that you might want to just be sure that it works regardless of the input parameters and then what are the expected return values for each of those so think about your use cases think about your edge cases and pause the video here to try writing those down as well as what you would expect the return values to be all right so maybe for your input inputs of a use case you could imagine something like x's so join you know one two three and a delimiter string of comma space so there's a space there that you're not seeing and you'd expect that to return a string where one comma two comma three right and that might be a way that someone would use this in in a valuable sense some unusual edge cases i'm only gonna come up with one hopefully you came up with the empty list as a an input and you would expect the empty string in response right you wouldn't have a delimiter between two items because there aren't two items but a single value list is also a good edge case here so um join one that's just going to return the string one right and there's no delimiter because there aren't two values to put the delimiter in between oops i needed my second argument here so my second argument would be anything so second argument of let's say comma again so i forgot my second argument in writing that example uh let's see one other one well what if we use the empty string as our delimiter so if we had joined one two three oops and as my second argument here just the empty string we would expect a string one two three as the result right no space or no delimiter between these numbers we're just going to concatenate them one after the other so this is an exercise in thinking through okay i've been given some definition or some expected requirements of a function how do i think through how should this function be used what are the obvious like valuable ways it would be used but also what are some of the weird ways that it might be used and how should the function behave in those edge cases as well and so hopefully this gives you a general sense of there are going to be a number of cases this function needs to handle and we should probably set up some tests for these as well so now that we've got our test cases here let's try thinking through how we can programmatically add those to the file we're working on all right so i'm going to jump back to vs code and i'm just going to hide those for now so let me see if i can there we go hide those and the first thing we're going to do is set up our skeleton so def join x's is a list of int values and our delimiter is a string and it's going to return a string right and i produce a string where subsequent items are separated by the limb right i'm just going to shorthand that so that it all fits on my screen here and return skeleton right spooky season uh just about and so this function is going to return a nonsensical value just a string value in this case so this is a valid function definition even though the implementation is clearly wrong this isn't doing what this function is supposed to do but it has a name it has parameters that are correctly typed it has a return type and it actually has a return statement that is satisfying that return type so we could go and we can use this function from any anywhere else right so the first step when writing a new function and trying to begin working on it is implement a skeleton and from there we're going to start trying to add test cases really related to those that we thought of as we were trying to reason through how this function should work so i'm going to go back over to my module test and now let's go ahead and try adding a test case here so let me try unhiding so this first value was one of our use cases so why don't we um why don't we add that one first so let's say uh def and remember a test case begins with test underscore and we're testing here the join function and use case returns none this is a procedure and what we're trying to say is okay let's say assert and join one two three the second argument is the string with a comma in it and a space following that comma and then the resulting string that we expect we expect that to be equal to one comma two comma three all right now i'm seeing some red underlines here and it says that join is not defined so why isn't the join function defined in this file well remember we defined join in ls under ls24 underscore module we need to actually import that function into the module that we're this test module that we're working on so to do so i should go back up to the top and say okay not only import the total function but also import join from ls24 underscore module right and so now that error's gone away and we can try running this dust and let's see if we if we pass it do we expect to pass the test no right because join is going to return the string function or sorry the string skeleton so i'm going to open my tests and one of the first things you should know when you're writing new tests is that vs code isn't going to automatically discover them in order to do that you've got to use this discover tests button and sure enough it gave us this fourth test and so if i wanted to test exactly that one we see that it fails and if we look if i hover over it we see that it failed because assert string skeleton is not equal to one comma two comma three right so before i go and add or go fix this test let's go ahead and add some of our other tests for good measure here all right and i'm not going to add doc strings to these tests for the purposes of expediency in the video but it would be good practice to describe what you were trying to test if it felt necessary so def test edge case of a single item right so that's going to be our next test so none so assert join of this list where we just have a single item in it and comma is going to be equal to just one all right and so that's the second case that we thought through here the third case was what if we used the empty string as our delimiter which is kind of a weird thing to do uh join it's clearly giving you a uh a space for a delimiter as an input argument so it's a little bit weird to use in this way and that's why we're calling it an edge case so test edge empty delimiter and that will once again be a procedure and i'm going to assert join of one two three and the empty string for our delimiter and that's going to be we're expecting that if this test works to be equal to one two three right because that delimiter should go between each item and the string that's produced but in this case we're not doing that all right so notice that uh if i can we've taken these test cases we thought we before we've even tried to correctly implement this we we've put a lot of thought and effort into figuring out what are use cases what are our edge cases and can we encode those in tests that would be easy for us to to run and so now back from my tests user interface i can refresh this and sure enough i've got all of those if i wanted to run every test in this file i could we can see we're still passing all of those tests for the total function that we wrote but join isn't isn't the test for join isn't showing up it's good practice to uh right after the name of or after the test prefix use the name of the function you're testing so i'm actually going to rename these so test join and actually let me just clear out my screen here join edge single item and test join and those edge are just for edge cases again if you rename tests you've got to rediscover them so there we've rediscovered and we see very clearly that we're testing the join function in each of these and we're failing in all of them all right i would encourage you to try pausing the video here and seeing if you can go ahead and implement a correct implementation of join and as you are working on the join function test it with the tests that we've written in and go back and forth between them all right so go ahead and pause the video here and then we'll come back and work through this together to close it out all right so let's bring this one home uh let's go ahead and we know that we're trying to build up a string so maybe um the joined string previously we used the word result as a variable name but we don't that was just a we can use anything we want so i might use uh generated string as my variable name that's the stir that we're building up and initially it's empty and return generated string as my return value so again i like to set up if i'm if i'm writing a function that's accumulating some result or i know that whatever value this function's ultimately going to return as the function's algorithm works that variable is going to be modified in some way i initialize that variable as the very first step of my function and i go ahead and return it as the very last step and and sometimes you'll deviate from that but for these functions that we're writing here it's not a bad strategy and okay well what do we do well we can say for x and x's we can say generated string is and so what is x is type x's type is int so we're going to need to change it to a stir so we're going to extend generated we're going to concatenate on to the end of generated string so i'm going to use the concatenate concatenation reassignment operator here so we're taking the current value of generated string and adding on to the end of it well we could do something simple here to begin with we could just say stir x's right and notice we haven't made use of the delimiter parameter yet so this seems like this probably isn't correct but let's just see what we're working with and whether or not we've made any progress at all right so i'm going to rerun my tests for the module and all of them are still failing right all of them are still failing now that seems surprising because uh in the case where we had our our edge case right where there's the empty string is the delimiter one two three it seems like this this case should have passed so that that causes me pause for a second and notice oh that's interesting i used the list stir uh i tried to take the stir representation of the list and actually if we we looked at the results here so why didn't this pass the assertion error the string list one is equal to one well that that test should have passed as well if we were if i hadn't made a simple typo same with the one below it and here we start to see an even bigger hint so if we look at that failure line so fail assertion error assert and then we see the list the list to the list is equal to one two three oh no we were referring to we were concatenating onto the end of this generated string the entire list in string form when really we wanted just that one item that first x right and this would be even more obvious if we said you know something like four item indexes generate the string representation of that item rather than x's all right so i'm going to modify that and uh it feels to me like two of my tasks if if i'm thinking about this correctly you should pass and sure enough the edge cases are passing but the use case of having that delimiter isn't passing and that makes sense because we're not making use of the delimiter at all so we could try doing something like okay should the delimiter come before or after the next item that we're adding it should probably come before it so we can somewhat naively say okay delimiter concatenate with string item and let's just see i'm not going to think too deeply about this uh i'm going to try running this and uh oh now we've got two failing tests so let's go see what happened with this this edge case so the single item edge case which was previously working is now failing because look uh assert comma space 1 is equal to the string 1 that's false that doesn't work out and so oh no we should only be putting the delimiter um in front of or in front of subsequent items not in front of the very first item so we're going to need to make our logic a little bit more robust to handle that similarly the same we're seeing the same pattern here where assert comma one comma two comma three we shouldn't have that leading comma so we need to do something different so there are a number of ways that we could implement this logic and i'm going to choose just one of the possible ways there's there's there's many ways you could do this right but one of the ways you could say okay if generated string is equal to the empty string then generated string is just going to be we could either we could just reassign it to be stir item all right this is the first uh the first entry else generated the string and i've got to indent that so it's part of the else block is extended by or is concatenated onto the end of it and reassigned the delimiter followed by the next item right and so if i wanted to leave a comment here as to why we use this logic so uh don't uh put delimiter before first item is what i should have written but that would have gone off my screen so each time we loop through an item in this example we're testing is the generated string the empty string and if so we're only going to add that first item to it otherwise we're going to add the delimiter followed by the the next item okay so we've updated our logic we could have done this in many ways let's see if this satisfies our test cases so i'm going to run my test cases for my module and what do you know all green checks it feels like we're well on our way here and so this is a demonstration of the process of writing tests and testing a function as you're implementing it typically what you want to do is implement the skeleton of a function and after you've implemented the skeleton of the function think through what are some of my use cases what are some of my edge cases and encode those as actual test functions in a testing module and then you can go back to working on the implementation and start to make it more real and try and pass all of those cases that you thought of now there's a slide that's very important to come back to which is testing is no substitute for critical thinking you can pass every test in the world but if your tests are all testing the exact same thing or or aren't catching some of the edge cases or even some of the use cases that are expected of some given function then it doesn't really mean that you've actually correctly implemented a function it just means that you are have correctly implemented whatever tests you specified but that's not typically not necessarily enough so you do have to think very deeply about whether some implementation of a function covers all the cases that matter and coming up with the cases that matter is a real challenge and one of the real practice skills in becoming a talented software engineer and the best way i know how to advise doing that is again think through what are the different possible arguments you could give to this function what are some of the normal ones what are some of the weird ones and that will cover your use cases your edge cases and then in each of those cases what do you expect the function to return with that knowledge you can pretty easily translate those examples into actual tests that you can run easily inside of your vs code and this is how software engineering is done in the in the real world as well at leading companies like google and facebook so you need to be sure that your test cases cover a useful range of actual usage and the best rules of thumb that are are not going to cover every example that i could come up with but will cover the vast majority are you really want at least two use cases that are different from one another maybe one is working in the singular and another is working in the plural if you're dealing with lists or collections of values and you want at least one edge case if your function has an if then statement in it that's usually a good sign that for every if branch or else branch you're going to want a test case to cover all of those branches as well because you kind of want to know that like hey you you're reaching each of these these logical pathways through your function definition you should have that covered in a test so that we'll stop here on talking about introducing the idea of testing the functions that you're working on and i would encourage you in the next exercise to follow the same pattern of trying to think through writing your skeleton coming up with your use cases your edge cases and then actually going through and implementing your tests and finally that's when you start working on your implementation the implementation of your function and hopefully this will give you a pathway towards being more confident in the code that you write and in knowing that it's behaving in the ways that you expect without having to do a lot of tedious work retesting retesting for little changes great work
Info
Channel: Kris Jordan
Views: 23,268
Rating: undefined out of 5
Keywords: pytest, unit testing with python, python test, unit testing, unit test, assert, vscode, visual studio code pytest, pytest command line, pytest cli, pytest runner, pytest test discovery error, test discovery
Id: UMgxJvozR5A
Channel Id: undefined
Length: 50min 51sec (3051 seconds)
Published: Tue Sep 22 2020
Related Videos
Note
Please note that this website is currently a work in progress! Lots of interesting data and statistics to come.