Modern Python Projects Transcripts
Chapter: Let's build a CLI
Lecture: Adding tests

Login or purchase this course to watch this video and the rest of the course contents.
0:00 Now that our uptimer application is ready, it's time to add some tests. If we go to the test uptimer file,
0:08 you can see that poetry has generated a scaffolding for our test.
0:12 And right now it's simply testing that this version from the Underscore version init file is equal to 010(__version__ = 0.1.0). It's not a great test,
0:21 but at least that's something. I have prepared some tests that I'm going to copy and paste here, and we will go one by one and discuss what they do
0:29 and they look complicated. But we are actually testing an application that it's supposed to perform a request to external services,
0:39 so they have to be a bit complicated in the basic test. But don't worry, I will try to explain them as best as I can.
0:47 So, first of all, we have to install one more package called pytest-mock and this will make mocking with pytest is much,
0:55 much easier than using the default stuff. Let's go here and add pytest-mock. Okay, now that we have it,
1:05 let's go back to our tests on Let's discuss them one by one. Let me hide the sidebar so we can see the whole code.
1:13 Ignore this mock response object function. We'll talk about it in a moment. So first we have a test for the check URL method.
1:22 You can see that. Check URL method. Takes the URL performs head request and returns the status code. So here we have the first problem.
1:32 Our code is performing an external request. So what happens if we don't have the Internet connection? Or maybe that website is down.
1:41 We don't want our test to rely on sending a real http request. So instead we have to mock it.
1:48 So in the first line, we patch the head method from the request library and we tell it to return whatever this function returns.
1:58 And now we go back to this function, and as you can see, it takes a parameter of code.
2:03 Then it creates a response object and assigns this code the status code that this response will have, and at the end,
2:12 it returns this response object. So all this code does is that when we perform a head request, we get back a response,
2:20 object with the status code that we specify here, but no real head request is sent over the network.
2:28 So, we test that when we get back a response object with a status code 200 we can actually correctly get the status out of the response.
2:39 So here we perform. Check_url with a dummy_url. Because it really doesn't matter what you well you use. We are not going to make the http request,
2:47 and then we check that if the head request returns a response with 200 we correctly get this 200 back, and then we do the same thing with 404
2:56 and finally, we check that if we call check_url with no arguments is going to raise a type error.
3:05 So, let's comment out the other tests and run this one. And as you can see, it's passing. We have two tests. One is this dummy version,
3:20 and second one is this one. Next, we have a test that will check that if we call colorized status, it's going to call click.secho command.
3:36 So let's go back to the code and see what the color I status does. Basically, based on the URL and status. It's supposed to call click.secho command.
3:47 So here we first mock the click.secho command. And when we do that, we can actually make assertions if this command was called
3:57 or in the second example, we can make an a session if it was called with specific parameters. So this is a simple version of a test that only checks
4:06 that if we call colorized status this function intern calls click.secho. But we can comment it out and use this more advanced version
4:20 Here, We actually check that if we call colorized status, it's going to actually call click.secho with parameters of message and the
4:30 fg=green. So let's run it. And it's not working. It's not working because I commented out the mock. So if we don't mock this function,
4:46 we cannot call this method, and now everything is fine. Next, we have an example of a parameterised test.
5:02 So basically, this one test will take five different configurations of parameters and create five
5:08 different tests. So, what this parameterise do is it takes all the occurrences of code and replaces it with the first parameter from this tupple.
5:18 So this and this and this will be replaced with 200 and then it takes the second parameter, so color, and it replaces it with a string green.
5:30 So to make it easier to follow, Let's actually remove this parameterization. And let's use value 200 green.
5:38 So let's look for code. We have code here and here. Look for value color. This is not the value.
5:46 This is a parameter, so we only change this one and we can actually remove this. We can also remove this, and that way it's no longer an f-string.
5:59 So let's try to run it to see if it works. It does. So let's see what actually happens here. First. Again, we mock the request.head,
6:10 so we get a response object with 200. Instead of actually sending the http request, then we create a CLI runner, CLI runner,
6:19 It's a way of testing click application because click applications are supposed to run in your
6:25 terminal. There is no easy way to actually test what's happening in your terminal when
6:30 you invoke them. So Click Runner is a little helper that you can use And then you can call the invoke command to,
6:37 pretend that you are calling a command from the terminal and providing some arguments,
6:42 and then you can check the output from the results to see that whatever was printed to the terminal is equal to what you expected.
6:49 So here we invoked the check command. We passed the dummy URL and we tell it to use the colors in the terminal because we want to see
6:58 if we get the correct color in the terminal. Then we prepared the message that we expect to have. So basically, we call the click.style,
7:08 which is like click.secho just without the echo. So, it will take whatever parameter you provide. It will take whatever color you want to use,
7:19 and it will return a string containing the ASCII code for the colors that you could
7:24 use in the terminal. Then we use this expected message and compare it with the actual output. And we also add design for the new line.
7:33 Because whatever we pray into the terminal, it contains the new line character at the end. Now let's go back to the parameterize version.
7:49 So, now instead of having one test, we have five tests. The first one will check that if the mock response object
7:56 Return Status 200. This status will be printed with the green color. Next one checks that if we have 304 is going to be printed with
8:06 yellow and so on and so on until we get a weird status of one and this should be printed with the magenta color.
8:14 Actually, this one test help me find a bug in my code. As you remember, before we had -1 here and we had -1 =
8:25 magenta here. But then I realized that this is actually not going to work and we should put magenta color here.
8:33 So as you can see, tests are a perfect way to find bugs in your code. Who would expect that? So, let's go to the terminal and let's run them.
8:43 You can see here we had four tests and now we have eight. So pytest parameterize is a great way to simplify some tests that are testing the
8:53 same steps, but with different parameters. With just a few lines of code, we're testing five different scenarios and then Finally,
9:03 we have one last test that will check multiple URLs Another new thing here is the side effect in all the previous cases,
9:14 when we're calling request.head, we wanted it to return the same response objects.
9:20 But this time we wanted to return 200, when we call it the first time. And we wanted to return 500, when we call it the second time.
9:29 So, side effect is a way to assign different return values. to an object that you're mocking, when you assign an
9:37 iterable to decide effect parameter of the mocker.patch. Each time you call the head method, it's going to return the next item from this iterable.
9:50 So then, in our test, we want to test to you or else the dummyurl1 and 2 And then we expect to get 200 in a green color for the first one
10:00 and 500 in a red color for the second one. So we prepared two expected messages and then concatenate them together,
10:08 adding this new line character and compare it to the output from the runner. And when we run this test in the terminal.
10:19 It's passing, so I know this was not an easy chapter, but this is how you usually write test for real applications.
10:28 You don't want to interact with external services, so you have to mock some of them. But you just have to be careful to not mock too much.
10:37 For example, if we don't mock the request.head, but we mock the check_url function and make it always return a specific number,
10:46 then there is no way that we can check if the check_url function is actually extracting the correct status code from the response object.
10:55 So make sure that you always mock Only. The functions that comes from third party packages,
11:00 for example, from packages installed with pip don't mock functions in your own code.


Talk Python's Mastodon Michael Kennedy's Mastodon