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