Providing Fixtures
00:00 In the previous lesson, I showed you how to write a pytest function. In this lesson, I’ll introduce you to fixtures and how to use them to provide test data.
00:08 Test tests, McTesty, test a lot. How’s your count coming? Blown through that estimate yet?
00:15 There are situations where you might want to have some data that you use over and over. You could of course write the same data into each test function, but that’d be repetitive and error prone.
00:26
The standard library’s unittest
way of doing things would be to use a .setUp()
function. pytest uses fixtures instead. You declare a fixture using a decorated function then pass that fixture function into your test function as an argument.
00:43
Let’s go see an example. To contrast, I’m starting with an example without a fixture. Consider testing the standard library functions for sum()
and max()
. In both cases, I want to pass in a list of numbers, but of course the result is going to be different.
00:59
If I was doing a lot of this, writing that list containing 1, 2, 3
over and over again is just begging for mistake. So instead, let’s use a fixture.
01:12
The sequence()
function on line 5 is a fixture function. It is made that way by being decorated as a fixture on line 4. A fixture function returns some data that can be reused.
01:25
The test_sum()
and test_max()
functions can use the fixture function by including it as an argument to the function.
01:33 This example, of course, is a little contrived, but if you’ve got a lot of data that needs to be reused, this can be a handy feature. Honestly, I both like and dislike this feature of pytest. Fixtures are a great idea and can help you reduce the chance of making a mistake, but the use of named arguments magically mapping to a fixture somewhere else feels less than Pythonic. The names of arguments to functions shouldn’t be reliant on a declaration somewhere else.
02:01 It can get even worse, as there are ways of putting your fixtures in a different file for organization purposes, which means you have to go hunting for those magic words. But like it or not, this is how the pytest folks have decided to do it.
02:17
Out of the box, pytest comes with some fixtures you can use. This is actually where it gets kind of crazy. As a fixture is just an object returned by a function, It can be way more than “data.” First example of that: the cache
fixture.
02:33 It’s a generic object that provides a place you can put some state that gets passed between all the tests that use the same fixture. For the most part, tests are meant to be independent of each other, but once in a while you need this. Don’t overuse it, though.
02:48
If you’re not careful, the order of your test execution might become important, and it shouldn’t be. The capsys
fixture indicates that STDOUT
(standard out) and STDERR
(standard error) will be captured for you, and you can get at their contents through the fixture. That could be useful if your code does things like print. The next two do the same thing, but two different ways.
03:11
tempdir
creates a temporary directory for you, unique to each test function and gives you an os.path
object to access it. This is helpful if you need to generate an output file as part of your test and then have it get cleaned up afterwards. tmp_path
is the same thing, but uses the more recent pathlib
library instead of os.path
.
03:31
I recommend this one. pathlib
is the way to go.
03:35
These are just a few of the built-in fixtures. You can run pytest
with the --fixtures
argument to see them all.
03:44
pytest automatically looks for and runs a file named conftest.py
. This is in case you want to do some configuration available across all of your tests. I hinted at one of these earlier.
03:57 You can put all your fixtures in here so that they can be reused across different test files. One common use for this is writing monkey patches. Monkey patches are dynamic code over-writing existing code.
04:11 These are usually used to stub out things you don’t want run during your tests. For example, accessing a database or the network or the bane of all testing—anything to do with dates and times.
04:24
You may want datetime.now()
to return a known value for your tests. Otherwise it can make testing difficult. Hey hey, we’re the Monkees. Wow, that’s going to be like one person who’s old enough to get that reference.
04:39 Google Micky Dolenz if you’re curious about some Beatles wannabes.
04:44 Anyhow, let’s go write a code patch the simian way.
04:49
Remember a monkey patch is just a fixture, so it’s declared the same way as other fixtures using a decorator. Adding the autouse=True
argument to the decorator gets this fixture used everywhere, not just in those test functions that explicitly use it as an argument.
05:07
There are two key parts to this. The first is at the bottom, on line 12: using the monkeypatch
argument to do the actual patching. In this case, I’ve patched the requests
library’s get()
method.
05:20
The first argument to .setattr()
is the requests
library. The second is the name of the function being patched—"get"
in this case.
05:29 And the last is a reference to a function to call instead.
05:33
Lines 9 and 10 declare the alternate function. This patch uses *args
and **kwargs
so that the underlying caller can pass anything in.
05:45
You could use the same argument list as the function you’re replacing—in this case, the requests.get()
method—but if you’re not interested in those arguments, like in this case, *args
and **kwargs
allows anything to be passed in, and then in this case, just ignore them. The body of the patch on line 10 raises an exception saying you’re not supposed to use this function.
06:08
A more realistic case would be to mock out some data to use instead. And that’s pretty much it. Because this is autouse=True
and declared in conftest.py
, you don’t have to do anything else for this to impact all your tests in the same directory or subdirectory. pytest takes care of it all for you and with less code than your average mock that does the same thing.
06:33
pytest also supports a command-line argument --durations
. When used, it will report the slowest x
tests in your test suite. The example here shows the three slowest tests.
06:45 This could be a good place to start if you’re trying to change the mark for certain tests so they’re no longer in your smoke test or to figure out how to speed up some of those laggards. All right, that’s fixtures, monkey patches, and a very dated musical reference.
07:02 Next you’ll learn that the pytest people don’t know how to spell parameterize.
Become a Member to join the conversation.