
Improve your Python testing even more.
In my last two articles, I introduced pytest, a library for testing Python code (see "Testing Your Code with Python's pytest" Part I and Part II). pytest has become quite popular, in no small part because it's so easy to write tests and integrate those tests into your software development process. I've become a big fan, mostly because after years of saying I should get better about testing my software, pytest finally has made it possible.
So in this article, I review two features of pytest that I haven't had a chance to cover yet: fixtures and code coverage, which will (I hope) convince you that pytest is worth exploring and incorporating into your work.
Fixtures
When you're writing tests, you're rarely going to write just one or two. Rather, you're going to write an entire "test suite", with each test aiming to check a different path through your code. In many cases, this means you'll have a few tests with similar characteristics, something that pytest handles with "parametrized tests".
But in other cases, things are a bit more complex. You'll want to have some objects available to all of your tests. Those objects might contain data you want to share across tests, or they might involve the network or filesystem. These are often known as "fixtures" in the testing world, and they take a variety of different forms.
In pytest, you define fixtures using a combination of the pytest.fixture
decorator, along with a function definition. For example, say
you have a file that returns a list of lines from a file, in which each
line is reversed:
def reverse_lines(f):
return [one_line.rstrip()[::-1] + '\n'
for one_line in f]
Note that in order to avoid the newline character from being placed at
the start of the line, you remove it from the string before reversing and
then add a '\n'
in each returned string. Also note that although it
probably would be a good idea to use a generator expression rather than a list
comprehension, I'm trying to keep things relatively simple here.
If you're going to test this function, you'll need to pass it a
file-like object. In my last article, I showed how you could use a StringIO
object
for such a thing, and that remains the case. But rather than defining
global variables in your test file, you can create a fixture that'll provide
your test with the appropriate object at the right time.
Here's how that looks in pytest: