 pytest tricks: fixtures

# Fixtures in Pytest

Parametrizing tests is great, but it's not a solution for everything. Suppose now that we'd like to add another test for `normalize`. Does the script below really make sense?

``````import pytest
import numpy as np

def normalize(X):
return (X - X.min())/(X.max() - X.min())

@pytest.mark.parametrize("x", [1, 2, 30], ids=lambda d: f"x={d}")
@pytest.mark.parametrize("y", [1, 2, 30], ids=lambda d: f"y={d}")
def test_shape_same(x, y):
if x == y:
pytest.skip("Not of interest")
X = np.random.normal((x, y))
X_norm = normalize(X)
assert X.shape == X_norm.shape

@pytest.mark.parametrize("x", [1, 2, 30], ids=lambda d: f"x={d}")
@pytest.mark.parametrize("y", [1, 2, 30], ids=lambda d: f"y={d}")
def test_min_max(x, y):
if x == y:
pytest.skip("Not of interest")
X = np.random.normal((x, y))
X_norm = normalize(X)
assert X_norm.min() == 0.0
assert X_norm.min() == 1.0
``````

There's a lot of repitition here and that's not ideal. Instead, we're going to write a fixture.

## Introducing Pytest Fixtures

``````import time
import pytest
import numpy as np

def normalize(X):
return (X - X.min())/(X.max() - X.min())

@pytest.fixture(params=[(1,1), (2,2), (3,3), (4,4)], ids=lambda d: f"rows: {d} cols: {d}")
def random_numpy_array(request):
"""
This is a pytest fixture. It's a function that can be passed to a
test so that we have a single block of code that can generate testing
examples.

We're using `params` in the call to declare that we want multiple versions
to be generated. This is similar to the parametrize decorator, but it's difference
because we can re-use `random_numpy_array` in multiple tests.
"""
return np.random.normal(request.param)

def test_shape_same(random_numpy_array):
X_norm = normalize(random_numpy_array)
assert random_numpy_array.shape == X_norm.shape

def test_min_max(random_numpy_array):
X_norm = normalize(random_numpy_array)
assert X_norm.min() == 0.0
assert X_norm.min() == 1.0
``````

You can confirm that we can still get the `ids` information when we run these tests.

``````# The verbose setting is optional, but gives more information
pytest test_normalise.py --verbose
``````