avatarAndy Barnes

Summary

The article discusses the importance of setting the correct fixture scope in pytest to optimize test suite performance, particularly when dealing with large or time-consuming fixtures.

Abstract

Pytest fixtures are essential for providing a consistent and reliable environment for tests. The article emphasizes the concept of fixture scope, which determines the lifecycle of a fixture during the testing process. By default, fixtures have a function scope, meaning they are created and destroyed for each test function. However, for fixtures that are resource-intensive, such as those involving network connections or I/O operations, adjusting the fixture scope to session, package, module, or class can lead to significant time savings. The author provides a personal example where optimizing fixture scopes in a test suite of 450 tests resulted in a 20% reduction in execution time, demonstrating the practical benefits of this approach.

Opinions

  • The author suggests that using the default fixture scope (function) can be the most time-intensive and may not be efficient for large test suites.
  • It is implied that developers should actively manage fixture scope to improve test performance, especially when fixtures are reused across multiple tests.
  • The author's personal experience highlights the tangible benefits of setting appropriate fixture scopes, indicating that this practice can lead to substantial improvements in test suite efficiency.
  • There is an acknowledgment that while the concept is straightforward, its impact on test duration can be significant, making it a valuable optimization technique.
  • The author's casual mention of warnings in their test suite suggests a pragmatic approach to testing, focusing on the most impactful improvements first while intending to address other issues over time.

Pytest — Fixture Scope

This post will cover what fixture scopes are within pytest and why you should use them. When developing with a large pytest test suite or using large, time-consuming fixtures, setting the correct fixture scope can shave considerable time off your test duration.

What is fixture scope?

Fixture scope allows you to configure the ‘scope’, or lifetime, of a fixture. It’s set by passing the scope parameter on the creation of a fixture.

@pytest.fixture(scope="module")
def my_fixture():
    pass

The available scopes are:

  • session: The fixture is destroyed at the end of the test session.
  • package: The fixture is destroyed during teardown of the last test in a package.
  • module: The fixture is destroyed during teardown of the last test in a module.
  • class: The fixture is destroyed during teardown of the last test in a class.
  • function: The fixture is destroyed at the end of each test.

The default scope, if not specified, is function. The most time-intensive.

Leveraging Fixture Scope

If you have a heavy-duty fixture, for example, establishing a network connection, generating large test data or doing I/O operations, setting the incorrect scope can significantly slow down your test suite.

If you know a fixture will be re-used throughout your tests, then set it to have the session scope or a different appropriate scope depending on its use. If it’s used throughout a class, then set the class scope etc. It will be created once and reused for each test it’s requested for. Rather than the default, which will be to create and destroy it for every test it’s used in.

Personal Example

I have a pytest suite for a personal project with around 450 tests utilising around 60 different fixtures. If I run this, using no parallelisation, and using the default fixture scope of function for all the fixtures, the tests take a total of 3 minutes 42 seconds.

452 passed, 4 skipped, 27 warnings, 25 subtests passed in 222.85s (0:03:42)

Ignore the warnings, I’ll get around to them eventually…

However, if I configure a correct and sensible scope for all my fixtures, the time drops to just under 3 minutes.

452 passed, 4 skipped, 27 warnings, 25 subtests passed in 173.67s (0:02:53)

That’s over a 20% reduction in time. Not bad for adding one parameter.

I had fixtures that were reading large amounts of OHLC data from an on-disk HDF5 file. Minimising the amount of reads necessary, by reusing fixtures, resulted in a significant time saving.

It’s a pretty simple one, but an example of this can be found in my GitHub repo here.

Python
Software Development
Testing
Pytest
Recommended from ReadMedium