Using Fixtures in Pytest
Introduction
In this tutorial, we will explore the concept of fixtures in Pytest and their application in unit testing. Properly structured tests are crucial for ensuring the reliability and maintainability of your codebase. We will begin by setting up a sample project, creating test functions, and then delve into how fixtures can streamline the process of writing tests.
Project Setup
To start, we need to organize our project structure. It's a best practice to have a dedicated tests
folder where you can place all your test files. For this tutorial, we'll create a sample module named sample_module.py
and a corresponding test file named test_sample_module.py
.
Sample Module
First, create a file named sample_module.py
with the following content:
def sort_numbers(numbers: list[int]) -> list[int]:
"""Sorts a list of integers."""
numbers.sort()
return numbers
def reverse_numbers(numbers: list[int]) -> list[int]:
"""Reverses a list of integers."""
numbers.reverse()
return numbers
This module contains two functions: sort_numbers
and reverse_numbers
. These will be the functions we aim to test.
Test Module Without Fixtures
Next, create a file named test_sample_module.py
in a tests
directory:
import pytest
import sample_module
def test_sort_numbers():
numbers = [9, 5, -1, -10, 10]
actual = sample_module.sort_numbers(numbers)
expected = [-10, -1, 5, 9, 10]
assert actual == expected
def test_reverse_numbers():
numbers = [9, 5, -1, -10, 10]
actual = sample_module.reverse_numbers(numbers)
expected = [10, -10, -1, 5, 9]
assert actual == expected
These tests are straightforward but repetitive. Both tests use the same initial data setup. In a larger codebase, repeating such setups can lead to redundancy and maintenance difficulties.
Introduction to Fixtures
Fixtures in Pytest help you to set up some initial conditions for your tests. They can be used to create reusable setups that multiple tests can share. This reduces redundancy and ensures consistency across tests.
Creating Fixtures
Let's refactor the above tests to use fixtures. Modify test_sample_module.py
as follows:
import pytest
import sample_module
@pytest.fixture
def sample_numbers_unsorted():
"""Fixture to provide unsorted list of numbers."""
return [9, 5, -1, -10, 10]
def test_sort_numbers(sample_numbers_unsorted):
actual = sample_module.sort_numbers(sample_numbers_unsorted.copy())
expected = [-10, -1, 5, 9, 10]
assert actual == expected
def test_reverse_numbers(sample_numbers_unsorted):
actual = sample_module.reverse_numbers(sample_numbers_unsorted.copy())
expected = [10, -10, -1, 5, 9]
assert actual == expected
Explanation
-
Fixture Definition:
@pytest.fixture
: This decorator defines a fixture. The functionsample_numbers_unsorted
will return the list[9, 5, -1, -10, 10]
.- This fixture can be used in multiple test functions, ensuring they all use the same initial data setup.
-
Using Fixtures in Tests:
- The fixture is included as a parameter in the test functions (
test_sort_numbers
andtest_reverse_numbers
). - The
.copy()
method ensures that each test gets its own copy of the list, avoiding side effects between tests.
- The fixture is included as a parameter in the test functions (
Running the Tests
To run the tests, navigate to your project directory in the terminal and execute the following command:
pytest
Pytest will automatically discover all files matching the pattern test_*.py
and execute the tests within them. You should see output indicating that both tests passed.
Conclusion
Using fixtures in Pytest significantly enhances the organization and maintainability of your tests. They allow you to define reusable setups, ensuring consistency and reducing redundancy. In this tutorial, we have covered the basics of creating and using fixtures, and applied them to simple test cases. As your projects grow in complexity, fixtures will become an invaluable tool in your testing toolkit.