pytest fixtures are designed to be explicit, modular and scalable.
What fixtures are¶
In testing, a fixture provides a defined, reliable and consistent context for the tests. This could include environment (for example a database configured with known parameters) or content (such as a dataset).
Fixtures define the steps and data that constitute the arrange phase of a test (see Anatomy of a test). In pytest, they are functions you define that serve this purpose. They can also be used to define a test’s act phase; this is a powerful technique for designing more complex tests.
The services, state, or other operating environments set up by fixtures are accessed by test functions through arguments. For each fixture used by a test function there is typically a parameter (named after the fixture) in the test function’s definition.
We can tell pytest that a particular function is a fixture by decorating it with
@pytest.fixture. Here’s a simple example of
what a fixture in pytest might look like:
import pytest class Fruit: def __init__(self, name): self.name = name def __eq__(self, other): return self.name == other.name @pytest.fixture def my_fruit(): return Fruit("apple") @pytest.fixture def fruit_basket(my_fruit): return [Fruit("banana"), my_fruit] def test_my_fruit_in_basket(my_fruit, fruit_basket): assert my_fruit in fruit_basket
Tests don’t have to be limited to a single fixture, either. They can depend on as many fixtures as you want, and fixtures can use other fixtures, as well. This is where pytest’s fixture system really shines.
Improvements over xUnit-style setup/teardown functions¶
pytest fixtures offer dramatic improvements over the classic xUnit style of setup/teardown functions:
fixtures have explicit names and are activated by declaring their use from test functions, modules, classes or whole projects.
fixtures are implemented in a modular manner, as each fixture name triggers a fixture function which can itself use other fixtures.
fixture management scales from simple unit to complex functional testing, allowing to parametrize fixtures and tests according to configuration and component options, or to re-use fixtures across function, class, module or whole test session scopes.
teardown logic can be easily, and safely managed, no matter how many fixtures are used, without the need to carefully handle errors by hand or micromanage the order that cleanup steps are added.
In addition, pytest continues to support How to implement xunit-style set-up. You can mix both styles, moving incrementally from classic to new style, as you prefer. You can also start out from existing unittest.TestCase style or nose based projects.
pytest does its best to put all the fixtures for a given test in a linear order so that it can see which fixture happens first, second, third, and so on. If an earlier fixture has a problem, though, and raises an exception, pytest will stop executing fixtures for that test and mark the test as having an error.
When a test is marked as having an error, it doesn’t mean the test failed, though. It just means the test couldn’t even be attempted because one of the things it depends on had a problem.
This is one reason why it’s a good idea to cut out as many unnecessary dependencies as possible for a given test. That way a problem in something unrelated isn’t causing us to have an incomplete picture of what may or may not have issues.
Here’s a quick example to help explain:
import pytest @pytest.fixture def order(): return  @pytest.fixture def append_first(order): order.append(1) @pytest.fixture def append_second(order, append_first): order.extend() @pytest.fixture(autouse=True) def append_third(order, append_second): order +=  def test_order(order): assert order == [1, 2, 3]
If, for whatever reason,
order.append(1) had a bug and it raises an exception,
we wouldn’t be able to know if
order +=  would
also have problems. After
append_first throws an exception, pytest won’t run
any more fixtures for
test_order, and it won’t even try to run
test_order itself. The only things that would’ve run would be