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.