Unit Testing with Pytest
The Problem
Usually, when you write a function for the first time, you test it manually, it works, you move on. But what if you change something in the future, in a separate place? Unit tests will catch that you broke something and you’ll know it immediately!
Unit tests are not (primarily) for finding bugs in the code you write now. They are for finding regressions, bugs that you introduced when you were fixing something else.
The Solution
Unit tests check individual components of the codebase, ensuring they work as expected in isolation. It can be a function or a class. Unit tests are the first line of defense. They ensure that the smallest parts of the codebase work as expected.
Pytest is a modern, yet very stable testing framework that makes writing tests a breeze, with almost no boilerplate code. It provides functionalities like fixtures, parametrization, and plugins that make testing more efficient and comprehensive. With plugins you get additional features like mocking, test coverage, parallel testing, and many more.
Your First Tests
By convention, test files are named test_*.py
or *_test.py
and are located under tests/
directory. In tapyr
we have for example tests/unit/test_utils.py
with the following content:
from tapyr_template.logic.utils import divide
def test_divide():
# Given
= 2
x = 2
y = 1.0
expected # When
= divide(x, y)
result # Then
assert result == expected
As you can see, there’s no boilerplate code, just the test function. Then to run the tests, you just need to execute pytest
in the root directory of the project. You can also configure tests in VSCode.
What if the divide
function should fail? Then we can write test:
import pytest
from tapyr_template.logic.utils import divide
from tests.helpers.logging_helpers import log_contain_message
def test_divide_by_zero(loguru_sink):
# Given
= 2
x = 0
y # When
with pytest.raises(ZeroDivisionError):
divide(x, y)# Then
assert log_contain_message(loguru_sink, "ZeroDivisionError: division by zero")
In this case we divide by 0 and two things should happen:
- We should get a
ZeroDivisionError
exception. - The exception should be logged in our logs.
Tapyr already sets up logging for you, so you can use log_contain_message
helper function and loguru_sink
fixture to check if the message was logged.
More on Pytest
Pytest has a lot of features, like fixtures, parametrization, and plugins. We strongly recommend you to read the official documentation to get the most out of it.