Calmcode - pytest tricks: fixtures

Fixtures in Pytest

1 2 3 4 5 6 7 8 9 10

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[0]} cols: {d[1]}")
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