Three Pytest Tips
I happened to use pytest a lot in the last few months, so I decided to write down a couple tips which could be useful to those who are already a bit familiar with the framework. For those who are not, pytest documentation is a good place where to start.
Parametrized dictionary
Use case
Run the same test with different input parameters and expected values
Benefit
- Tidy code
- Human-readable test names
First of all, define a dictionary where the key is the name of your test case and the value is a tuple with inputs parameters and expected values.
Then decorate your test as the following:
The first parameter of the decorator is a string containing the names of the parameters you want to parametrize your test on separated by a comma (in this case line
and expected
). You can then use those names as arguments for your test function.
The second parameter of the decorator is the list of values to be assigned to the parameters; in our case is always going to be the list of values of the dictionary.
Finally, the third parameter is the list of test names that will be used for each generated test case; in our case this corresponds with the keys of the dictionary.
This is the output when you run pytest -v
with the above code:
For more information on pytest parametrization, have a look at the documentation here.
Note: dict.values()
and dict.keys()
should have the same order starting from Python 3.6. Before that it was implementation dependent, but it worked for me since Python2.6. I never tried with PyPy, though.
Context manager/mock yield
Use case
Execute some setup and teardown logic for a fixture (not a test)
Benefit
- More compact code
- Avoiding test pollution
Pytest provides a feature to execute some setup and teardown logic when defining a fixture, like in the example below:
This feature is incredibly useful when combined with context managers, since they are going to automatically execute the teardown code when they get out of scope; e.g. opening of a file:
This feature makes possible to create pytest fixtures where we can mock something out only until the fixture stays in scope, avoiding a pretty common source of test pollution:
If you want to know more about this feature, the documentation has a nice writeup here
Composing fixtures
Use case
Define a fixture based on another fixture and intercat with both of them in the test
Benefit
- Modular code
- Shorter tests
Pytest fixtures can be combined at any level, making very easy to build chains of mocked components. This is easier showed than said, so let’s just have a look at an example:
As you can see, the stream
fixture is used as parameter to create a the tailer
fixture. Then, both of them are used in the test case to make the desired assertions.
This one is probably useful only for integration tests or tests covering lots of moving parts, but it makes reading these complex tests much easier, since it makes much more explicit the relation between all the components involed in the test.
I kept this list short in the hope that people could at least remember one of the tips; but I might write more tips in another post in case I find something particularly useful. Stay tuned!