Some pytest best practices
Recently I had to use the R test framework called testthat. I noticed that this framework is very human oriented. Its name comes from the way to write tests that aims to make a readable sentence.
For example “Test that str_length
is number of characters” becomes.
|
In the chapter Testing of the book R packages some guidelines are given on the functional way to write tests. For example:
Tests are organised hierarchically: expectations are grouped into tests which are organised in files:
- An expectation is the atom of testing. It describes the expected result of a computation: Does it have the right value and right class? Does it produce error messages when it should? An expectation automates visual checking of results in the console. Expectations are functions that start with
expect_
.- A test groups together multiple expectations to test the output from a simple function, a range of possibilities for a single parameter from a more complicated function, or tightly related functionality from across multiple functions. This is why they are sometimes called unit as they test one unit of functionality. A test is created with
test_that()
.- A file groups together multiple related tests. Files are given a human readable name with
context()
.
So I have thought of a way to use the same kind of guidelines / conventions in Python with pytest
.
Pytest best practices
It’s not exhaustive at all nor a full–boring and long–list of guidelines. Just a couple of things I try to apply each time I have to write some tests in Python.
I’m starting by the example and I will summarize the main points below.
|
- Put inputs and output in the function parameters: It permits to identify them easily–since they are clearly visible at the beginning of the method–and it will ease parametrization(running the same test with different inputs and outputs).
- Log a message instead of commenting the function: I’m always try to log a clear message at the beginning of the test. It replaces the method comment and will be written in the test output. So it’s easier to figure out what the test is actually trying to check.
- Write always assertion in the same way: In assertions put always actual at the left side of the assertion and the expected result at the right side.
- Write assertion human readable error message: Take the time to write clearly what is the error it will save debug time later.
Running this test produces the following output.
|
Parametrization
Once tests are written in this way it’s easy to reuse them through parametrization. Parametrization permits to reuse the same test (code) with different conditions. Here is the same example with parametrization.
|
In the example above the same test has been reused only by extracting the parameters from the function to the pytest.mark.parametrize
decorator. Nothing more to do. Note also the usage of the special pytest.mark.xfail
to indicate that we expect this test to fail, see parametrize
for the full list of features.
Running these tests produces the following output.
|
Pytest output tuning
To produce the outputs in this way I have used a customization of pytest.ini
.
|
Note: I’ve put both files in a gist.