Writing and Running Doctests12:11 with Kenneth Love
Doctests are the simplest tests to write in Python since they're written in plain text in the docstrings you're already writing for your code.
doctest - A test written in a docstring.
doctest library - The built-in Python library for running doctests.
From the command line
python -m doctest your_script.py
From inside a script
Doctests are something that's fairly unique to Python. 0:00 Other languages have them, of course, 0:03 but they're not always as nicely integrated as they are with Python. 0:05 Remember how, when we define a function or class, we can write a docstring, or 0:08 a brief description of what the function or class does? 0:12 Well inside that docstring you can also write a test. 0:15 Let's go to Workspaces and check it out. 0:18 This script probably looks familiar to you. 0:20 It's a slightly modified and 0:22 improved version of our Dungeon game from the Python collection's course. 0:24 There are several functions in here, that should be pretty easy to test, 0:28 through doctests. 0:33 So, let's see about adding some of those. 0:34 This also gives us a really great opportunity to add 0:37 in explanatory docstrings to our functions and to the file itself. 0:40 So, we can skip this first function. 0:45 We don't need to test it, 0:49 because all this function does is use Python to call a system level command. 0:50 We know that the system-level thing is gonna work and 0:54 we know that the Python function is gonna work, or 0:57 rather, testing those two things is not our responsibility. 0:59 We can rest assure that they work because we know there are tests written for 1:04 them on the system and on Python itself. 1:07 So, we don't need to test it, but it doesn't hurt to go ahead and 1:11 add in a docstring. 1:14 So, we'll just say, you know, clear the screen. 1:17 Okay. And then another thing often really good 1:22 to do is to add a, 1:24 effectively a docstring to your entire app or to your entire script. 1:26 So, I'm going to add one up here that just says dungeon game, explore a dungeon 1:32 to find a hidden door and escape. 1:39 But be careful! 1:44 The gru is hiding somewhere inside. 1:47 And then I'm going 1:53 to add a created 2014 updated 2015. 1:57 Oops, not 2016, and author Kenneth Love. 2:03 You can of course put your own name in there, put whatever name you want. 2:09 You probably want to put, if you were doing this for a script yourself, 2:12 you probably want to put in, like, dates that you actually created these things. 2:16 But, I don't want to have to remember exactly when [LAUGH] it was that I wrote 2:19 which thing. 2:23 So, anyway. 2:24 Just a bit of housekeeping, 2:25 this just makes it a little bit nicer for somebody else who comes across this file. 2:26 Let's go add a test, though, to this next function, this build cells function, 2:30 and, see how to write a doctest. 2:35 So, we're actually gonna come down here, and we're gonna put in three quotes. 2:39 Just like we're writing a docstring, 2:43 doctests and docstrings are definitely heavily related. 2:45 And let's say what this function does. 2:48 So it's going to create and return a width 2:49 by height grid of two tuples or sometimes you'll see these called pairs. 2:55 So, now we need to write the test. 3:05 And I like to leave white space around my tests. 3:08 It helps for letting Python find the doctests. 3:11 Sometimes you'll get weird behavior if there's not this 3:15 blank line at the end of the test. 3:18 And this blank line leaves a bit of nice separation, so 3:20 you can see where the description ends and where the tests begin. 3:23 So, the way that we write our tests 3:28 is any statement that we want Python to run we start with three chevrons. 3:31 It looks like the Python shell. 3:36 So we want it to run cells equals build cells, a width of two and a height of two. 3:39 And then I want to do len cells, and a two by two grid should have four items in it. 3:46 So these are what we want to have happen, these two lines, 3:54 and this is what we're expecting back from Python. 3:59 So if we look at our function, we have this cells list. 4:04 And then for each row that's in our height and 4:08 then for each cell that's in our width, 4:12 we're going to append this pair of the two numbers. 4:16 And then in the end we're going to return our list. 4:21 So when we do this two-by-two, we should get back four items. 4:24 Okay. 4:29 Looks an awful lot like the Python shell, and we're actually going to, in a minute, 4:30 see how to build these things in the Python shell, but 4:34 first let's write one more. 4:39 Let's go down here to this get locations. 4:40 And, it's going to get cells. 4:43 So, once again, we're gonna say, 4:45 randomly pick starting locations for 4:49 the monster, the door, and the player. 4:54 Where am I at? 5:00 I'm at 82. 5:01 All right, we'll go back. 5:01 There we go. 5:02 Okay. 5:04 So how would we go about testing this? 5:06 Well, let's look at the function does. 5:10 We've actually seen this one before, but just let's go over it. 5:13 So we get a random place for the monster, a random place for 5:15 the door, random place for the player. 5:17 If the monster is equal to the door, or the monster is equal to the player, or 5:20 the door is equal to the player, then we wanna get brand new locations for them. 5:23 Finally, we send those back. 5:26 You may not have noticed, but 5:29 this is actually a really good example of a recursive function, 5:30 so some people are like, ooh, we need to learn about recursive, or recursion. 5:33 Hey, here it is. 5:38 Okay, so let's write this. 5:39 So, first of all we're gonna do, oops, I need my three chevrons, 5:41 cells equals build cells two by two. 5:44 We can use the other functions that are in the file, so we're call our cells file, 5:49 ou, our build cells function up there, and we're gonna say M, D and 5:53 P is equal to get locations for cells, and 5:57 then we're gonna make sure that M does not equal D, and D does not equal P. 6:03 That should come back as true. 6:10 And then we're going to do D in cells just to make sure it's in there. 6:12 And that should also come back as true. 6:16 Okay? 6:19 So let's save that. 6:19 Nothing too strange or weird in our test there. 6:21 We're just kind of using it like we normally would. 6:25 'Kay, let's look at our next function here, get_moves(player). 6:27 This is a little bit more complicated than these ones above. 6:31 We're doing a lotta checking in through here. 6:36 So, let's actually see about writing this one in the shell. 6:38 So let's add a couple of things in here first. 6:44 Let's describe it. 6:47 We're going to say based on the tuple of the player's position. 6:48 Return the list of acceptable moves. 6:56 [SOUND] Okay, so right here is where we would start to write our stuff. 6:59 But I don't want to write it here, I want to write it down here. 7:06 Okay. 7:10 So let's go into Python. 7:13 Oop, sorry, you know what, let's get out of here. 7:14 Let's go into our dungeon directory. 7:16 And now go into Python. 7:19 Okay, from dd game import Get Moves, cause that's something that we need to have. 7:22 And then if we look at Get Moves. 7:30 We see it's using this game dimensions constant. 7:32 And if we look at the top of the file. 7:37 Where game dimensions is designed, it's this tuple of five-five. 7:39 Let's let's define that our self so that we don't have to use the five-five one. 7:44 So we'll say game dimensions is, let's do two-two, let's stick to our small grid. 7:50 And then let's say get moves and 7:58 we'll pretend that our player is down in the corner. 8:01 And we'll say zero is, is over in the corner and we'll say zero, two. 8:03 So we run that and we get right up and down. 8:07 Okay. So that's pretty simple. 8:11 So what I'm going to actually do. 8:13 I don't need this from dd game, import get moves line. 8:15 That's a given from my test. 8:18 But I do need this GAME_DIMENSIONS = (2, 2), get_moves, and that RIGHT, UP, DOWN. 8:20 So I've got that, I'm gonna copy it, I'm gonna come, 8:27 let okay, let's exit out of here. 8:31 And then let's drop this down. 8:35 There we go. 8:39 Okay. I'm gonna come up here. 8:40 And I'm gonna paste my stuff in and I'm gonna tab it in, and I'm gonna save. 8:43 Hey look, there's my test. 8:48 I wrote the whole test. 8:51 Isn't that amazing? 8:53 I didn't even have to, like, I didn't have to write it. 8:53 I just did it in the, I mean okay, I wrote it in the shell. 8:55 But still, I didn't have to, come up with what the answers were. 8:58 I could let the function just run and see what happened. 9:02 Okay. So we've got a few tests. 9:05 Let's see about running these. 9:07 Let's see what happens. 9:08 There's a way that we could run the test directly from this file so 9:11 that when you run the file, it runs the test. 9:14 But I'm not a really big fan of that approach. 9:16 The main reason is because I don't always want doctest to run. 9:19 In fact, I usually don't want doc test to run. 9:22 I will list how to do that in the teacher's notes though. 9:25 So instead, let's actually just run it explicitly down here in our shell. 9:28 So, what we can do is we do Python dash M doctest, and then we do DD game.py. 9:33 So what is this Dash M. 9:40 So the dash M tells Python to load the doctest module. 9:43 And the doctest module has some special code written in it 9:47 that says you're going to give me a file and when you give me that file, 9:52 I'm going to look through that file for doctests. 9:56 And I'm going to find the doctests and I'm going to run them. 9:59 So, that's what we've done. 10:02 We've said, load the doctest module and then give it the dd_game.py file. 10:03 Nothing comes back. 10:11 Guess what? 10:12 That's a good thing. 10:13 Nothing comes back. 10:14 That means everything passed. 10:15 That's exactly what we want but, let's make on fail real quick. 10:16 Let's go right here to this one. 10:20 We know that right, up, and down are gonna come out. 10:21 So to make it fail let's get rid of up. 10:25 Okay, let's say up is not gonna come up. 10:28 We're just gonna get right and down. 10:30 Okay? 10:32 So we come down here and we run our test again. 10:33 And here's our fail, so our fail is actually really, really good. 10:37 I'm gonna run this one more time so we can see the whole thing. 10:40 Here we go. 10:43 So, this file failed on line 64. 10:44 So, really, we failed on line 65, but, 10:47 line 64 is the one that we ran and it didn't give us what we expected. 10:51 So, the failure, 65 has the wrong content. 10:56 64 is the line that gave us the wrong content, so 11:00 64 is where the failure happened. 11:03 So, we ran this. 11:06 We expected this, right? 11:08 But we got this. 11:12 So, to fix this test, we need to come back in here and add in up. 11:14 If we save and we run it again, 11:20 now, there's no output because all the tests are good. 11:22 I'm not going to write anymore doctests for this function, or for 11:26 these functions in this script. 11:29 If you want more practice, then I suggest you do it. 11:31 Go in and write doctests for all the rest of them. 11:33 You should be able to do it no problem. 11:35 Wow, doctests are really simple to implement, and run. 11:37 We should definitely write these when we're planning our code. 11:41 It's a lot simpler to write a doctest than a full test suite, especially for 11:43 simple sanity checks. 11:46 Or, to write tests before you start coding. 11:48 Doctests do have some serious shortcomings though. 11:50 They're pretty tightly bound to the code they're written on, so 11:53 you can't just reuse them for new functions or classes, and 11:55 all of their checking is done through string comparison, so 11:59 sometimes floats can be tricky or even impossible to test with doctests. 12:01 Since doctest won't solve all of our problems, 12:06 let's look at a more powerful solution, the unittest module. 12:08
You need to sign up for Treehouse in order to download course files.Sign up