Python Unit Testing: Project Setup
Unit testing is a critical component of modern software development, enabling developers to catch bugs and ensure that their code behaves as expected. If you're new to unit testing or looking to improve your existing workflow, this guide will help you get started with unit testing in Python. In this post, we'll walk through the steps required to configure a Python project for effective unit testing. We'll cover the tools and libraries needed, how to write and run tests. By the end of this post, you'll have the knowledge and skills you need to start writing and running unit tests in Python.
Table of contents
- Modules Used for Testing
- Tests Discovery & Tests Naming Conventions
- Commands to Run the Tests
pytest
Config File- Setup Example
- Conclusion
Modules Used for Testing
There are several modules (packages/libraries) that can be used to write and run your tests with.
Test frameworks / Test runners
- Python's built-in module
unittest
:- Has built-in assertion and mocking APIs,
- Compared to
pytest
it has a verbose assertion APIs, - No auto-discovery, you need to mark a folder as a module in order to be discovered,
- No plugins support.
- Standalone module
pytest
(recommended):- Has built-in assertion and mocking APIs,
- Simpler assertion APIs compared to
unittest
, - Auto-discovery of test modules,
- Can run
unittest
tests (great to support legacy tests), - Supports plugins.
Both are good, but it depends on what you want to test, one module can be a better choice over the other depending on the what you’re testing.
For the sake of being future proof, I recommend going with pytest
which is better than unittest
when it comes to running tests whilst having a big list of plugins which makes it easier to mock third party modules (in unittest
you need to mock a lot to make a third-party module fully mocked).
Tests Discovery & Tests Naming Conventions
Test files needs to be discovered in order to be tested (ran), and there is a difference between how unittest
and pytest
are discovering tests.
In unittest
:
- Files needs to be in the same directory,
- If it’s in a nested folder, it needs to be a module (by adding a
__init__.py
file). - Files' names needs to start with
test_*.py
(default), - You can override this pattern using
-p
CLI flag. - Test methods needs to start with the same pattern:
test_*(self):
, - Inside each test file, you need to have a main method/function:
if __name__ == '__main__':
unittest.main()
- Tests cases (test grouping) can be done via a class:
class MyFunctionalityTests(unittest.TestCase):
In pytest
(recommended):
- Files doesn’t need to be in the same directory,
- Files' names should be prefixed or suffixed by
test_*.py
or*_test.py
, - Test methods needs to start with a prefix:
test_*():
ortest_*(self):
(if inside a class test case), - No need for a main method/function,
- Tests cases (test grouping) can be done via a class;
class TestMyFunctionality:
Commands to Run the Tests
In unittest
:
python3 -m unittest file_name.py
In pytest
:
pytest
More ways on how to invoke tests in pytest
can be found here.
pytest
Config File
To set some configurations for pytest
runner, you can create a config file with different formats, but for the sake of simplicity use either .pytest.ini
or pytest.ini
.
A sample configuration file:
[pytest]
testpaths = src test
More on configuration options can be found here.
Setup Example
Finally to the setup example, we will start by creating the required files and content of each file:
Create a directory if you're setting a new project:
mkdir my-python-project
Create a main.py
file:
touch main.py
def custom_sum(first_number, second_number):
print(f'Summing {first_number} and {second_number}')
return first_number + second_number
if __name__ == '__main__':
print(custom_sum(1, 1))
Create a main_test.py
file:
touch main_test.py
import builtins
from pytest_mock import MockerFixture
from main import custom_sum
class TestCustomSum:
def test_should_sum_two_positive_numbers_correctly(self, mocker: MockerFixture):
# Arrange
print_spy = mocker.spy(builtins, 'print')
# Act
result = custom_sum(1, 1)
# Assert
print_spy.assert_called_once_with('Summing 1 and 1')
assert result == 2
Create a requirements.txt
file (dependency file like package.json
in JS/TS based projects):
touch requirements.txt
pytest==7.2.2
pytest-mock==3.10.0
Install dependencies:
python3 -m pip install -r requirements.txt
Run tests:
pytest
Conclusion
In this guide, we've covered the essential steps required to configure your Python project for effective unit testing. We started by introducing the modules commonly used for testing in Python and discussed the importance of adhering to naming conventions to make your tests easy to discover and maintain. We then covered the commands you'll need to run your tests and how to create a pytest
configuration file to customize your testing environment. Finally, we provided an example of how to set up your project for testing using the popular pytest
framework.
By following these guidelines, you can ensure that your Python code is thoroughly tested, helping to catch bugs early and ensure that your software behaves as expected. Remember, unit testing is a critical component of modern software development, and investing the time to learn how to do it effectively can pay dividends in the long run.
You can find the code of this post in this repository.
As always, I hope you learned something.
Found this useful? feel free to share it with your friends.
Join the newsletter from here to notify you of new posts and updates.
Like the post? consider buying us a coffee ❤️.