Skip to content

pytest and fixtures

pytest and fixtures

Unit tests are important part of the software development. In python those can be approached from different angles and using different libraries. Here I want to show everything I wish I knew before I started working with pytest library, fixtures and parametrization.

Contents

Fixtures

Fixtures are pieces of code that are used to set up a test context. Those can be some data or metadata, system states or helper methods. Same fixture can be used in multiple tests providing the same starting points, encapsulating some functionality and ensuring test repeatability.

In pytest fixtures are defined as functions that serve a particular purpose. We define them using a decorator on a regular function returning desired output.

Let’s say we want to reuse a list object in our tests – such a fixture will have following form:

Fixtures can be also constructed out of other fixtures:

More about defining pytest fixtures can be found in official docs.

Tests with fixtures

Created fixture can be used directly in a test. Let’s define a helper function, which we will test using different scenarios. (Note that I omit error handling, not to clutter the function for the examples).

Now let’s test this method using our fixture, by simply providing it as a parameter to the test.

Of course, alternatively here we could directly insert values into such a simple test. However we’re assuming that our fixture will be used in multiple tests. That is super useful in some more complex scenarios, leveraging the exact same mechanism. 

Parametrization

Let’s look at a more advanced scenario, where we provide more than one nparameter. This can be done via parametrization. This option gives us the opportunity to test more than one scenario using the same test.

By parameterizing number of copies in the test, we tested efficiently 5 different use cases.

We can enhance this test further by also directly providing expected values for each input value.

Again we run 5 tests, this time providing 2 values for each case – number of copies and expected length of output list.

But why limit yourself to just one list.  We can take it even further, by testing several lists instead of one. In a simple manner, we can pass them manually as yet another parameter.

Note the change of the passed list parameter name, now it is defined within the parametrize.

However the above option does not consider our fixture! And we would like to pass different lists as a fixture, instead of just one.

Fixture parametrization

We can define a fixture consisting of several lists:

Note the use of requestargument. The request is actually a special fixture providing information of the requesting test function. By calling paramon the request we can access such parameter value. Fixtures by design do not accept non-fixture arguments, therefore we need to use this special way.

If instead of request we provided x, we would get error: fixture 'x' not found.

Now when we pass this fixture to the test, we get 15 combinations!

Interestingly, if we would like to pass to a test simultaneously two lists instead of one, using the same fixture, we could do it in 2 ways, achieving 2 different scenarios.

Let’s consider a function accepting two lists:

Now for test we can create a copy of the fixture to be used as a second list:

This way we get 3 tests, each with the exact same two lists passed.

What if instead we would like to have a cross product of those lists tested? We can achieve that by creating a helper variable instead of a fixture.

Now we have 9 tests checked.

Generic fixture

Lastly, let’s consider a parametrized fixture using which we could generate a list of specified length for the particular test. 

Again, we need to use the requestfixture:

Now it is tempting to pass the desired length as a parameter:

However that leads to an error:

AttributeError: 'SubRequest' object has no attribute 'param'.

In order to make it work we need to specify within the test that parameter is to be passed into that fixture. This can be achieved by providing it in the parametrizeand specifying that it should be used indirectly. This way first provided parameter goes into the fixture definition.

Without the indirectkeyword, my_gen_list would be treated as a integer variable and not as a fixture.

Leave a Reply

Your email address will not be published. Required fields are marked *