We write tests because they help us build confidence in our code. They also help us write clean and maintainable code. Yet, writing tests requires some effort. Fortunately, there are libraries we can leverage. Pytest, for example, comes with a lot of handy features that are often not used. In this article, I will introduce you to 5 of them.
Test logging with caplog fixture
Sometimes, logging is part of your function and you want to make sure the correct message is logged with the expected logging level.
You can leverage one of the built-in fixtures called caplog . This fixture allows you to access and control log capturing.
Let’s see it in action with a basic example. Say you have a function that translates animal names in a language to another language. You want to make sure that if an animal is missing from the translation dictionary, you log that animal to eventually add it to the dictionary.
Here we use the record_tuples
, a list of tuples made up of the logger name, the level, and the message. This is a simple example but you can go much further.
Test exception are raised
Testing allows you to verify the behavior of your code when it faces edge cases. And when it comes to edge cases, you often end up raising exceptions. Pytest helps you verify exceptions are raised as expected.
Let’s see how it works with a real-world example. Let’s say, you want to write the authentication layer of a CLI app. It might be a good idea to verify the email address format.
Your CLI application uses Click, a package that helps you build CLI apps faster. Click comes with lots of handy features like input verification through typing. However, e-mail addresses are not a built-in Click type so we need to do the input validation ourselves. We’ll use validation callbacks.
So, all you have to do is to use the pytest.raises()
context manager, then invoke the function and Pytest takes care of the rest!
Test time-dependent functions
Manipulating dates is always difficult. Writing tests can help us a lot. However, when you call methods like today()
or now()
, you end up with tricky situations where your tests depend on time. To solve this, instead of patching methods from the standard library, you can use the pytest-freezegun plugin. This plugin makes things easy.
Time for another example! This time, you need to implement a function that computes the number of days since a user has subscribed to your service. Luckily, you had read this post and you are aware of the pytest-freezegun
plugin 😉
Test the same function with different combinations of parameters
Most of the time, you want to test your function against different inputs. In that case, the easiest solution is to duplicate your code for all combinations of parameters. But, it is not a good practice and you should avoid doing so. Imagine you want to make a small adjustment to your test, you need to replicate this change as many times as you duplicated your code. Second, in terms of performances, tests run sequentially, which is slow. You could have parallelized the execution.
The solution to avoid those common problems is to use parametrized tests. Let’s take our email validation example back. You may have thought “A lot of cases are not covered”. Well, I agree and it’s time to fix that!
See, you can use pytest.mark.parmaetrize
to describe your inputs and avoid code duplication.
If you run this test, you will see that it fails, and this is great because it proves that our test is useful and has good coverage!
Bonus tip
As you have read almost until the end, I would like to thank you with a bonus tip!
One of the cases of the last example made a test fail. Now you want to correct the code and re-run the test that failed. If you have a large test suite, this is time-consuming to re-run the whole test suite. Instead, you can run:
pytest --last-failed test_parametrized.py
It will only execute the last test that failed, thus allowing you to quickly iterate and fix that bug. Once the test passes, don’t forget to run all tests to avoid any regression.
Conclusion
That’s a wrap! Pytest is very powerful and a blog post is too short to cover all the features. It is a great opportunity for another tutorial! 🤗
References
[1] Pytest Official Documentation
[2] Dane Hillard, Effective Python with Pytest (2020), Real Python