You're not supposed to combine @hypothesis.given
and @pytest.fixture
like that. The way to implement random matrix draws is by defining your own composite strategy, not a fixture.
Let's say this was your Matrix
class, as in your example,
import typing as tp
import numpy as np
import numpy.typing as npt
class Matrix:
array: npt.NDArray[np.float_]
def __init__(self, nrows: int, ncols: int, seed: int) -> None:
np.random.seed(seed)
self.array = np.random.rand(nrows, ncols)
then your composite hypothesis search strategy would be defined as follows:
from __future__ import annotations
import typing as tp
import hypothesis.strategies as st
import numpy as np
if tp.TYPE_CHECKING:
import numpy.typing as npt
from hypothesis.strategies import SearchStrategy
seed_strategy: tp.Final[SearchStrategy[int]] = st.integers(
# Bounds given are for the acceptable range of `numpy.random.seed`
min_value=0, max_value=2**32 - 1
)
@st.composite
def drawMatrix(
drawFromStrategy: st.DrawFn, /, *, nrows: int = 2, ncols: int = 3
) -> Matrix:
"""
Strategy which draws random `numpy.float_` matrices of dimensions (nrows × ncols)
Parameters
----------
drawFromStrategy
Callable which draws items from other hypothesis search strategies
nrows
Number of rows for the matrix
ncols
Number of columns for the matrix
Returns
-------
Matrix
Matrix for testing
"""
seed: int = drawFromStrategy(seed_strategy)
return Matrix(nrows=nrows, ncols=ncols, seed=seed)
Your pytest tests would then be written as the following:
_2x3_matrix_strategy: tp.Final[SearchStrategy[Matrix]] = drawMatrix()
@hypothesis.given(matrix=_2x3_matrix_strategy)
def test_nrows(matrix: Matrix) -> None:
assert matrix.array.shape[0] == 2
@hypothesis.given(matrix=_2x3_matrix_strategy)
def test_ncols(matrix: Matrix) -> None:
assert matrix.array.shape[1] == 3
@hypothesis.given(matrix=drawMatrix(nrows=3, ncols=4))
def test_bad_dimensions(matrix: Matrix) -> None:
assert matrix.array.shape[0] == 2
assert matrix.array.shape[1] == 3
Demonstration of this in action:
platform linux -- Python 3.10.5, pytest-7.1.2, pluggy-1.0.0
rootdir: ..., configfile: pytest.ini
plugins: hypothesis-6.54.5, anyio-3.6.2
collected 3 items
test_matrix.py ..F [100%]
=============================================================================================================================== FAILURES ================================================================================================================================
__________________________________________________________________________________________________________________________ test_bad_dimensions __________________________________________________________________________________________________________________________
@hypothesis.given(matrix=drawMatrix(nrows=3, ncols=4))
> def test_bad_dimensions(matrix: Matrix) -> None:
test_matrix.py:91:
_ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _
matrix = <test_matrix.Matrix object at 0x7f1dfb263d90>
@hypothesis.given(matrix=drawMatrix(nrows=3, ncols=4))
def test_bad_dimensions(matrix: Matrix) -> None:
> assert matrix.array.shape[0] == 2
E assert 3 == 2
E Falsifying example: test_bad_dimensions(
E matrix=<test_matrix.Matrix at 0x7f1dfb263d90>,
E )
test_matrix.py:92: AssertionError
======================================================================================================================== short test summary info ========================================================================================================================
FAILED test_matrix.py::test_bad_dimensions - assert 3 == 2
====================================================================================================================== 1 failed, 2 passed in 0.46s ======================================================================================================================
Please see the source code for the signature expected of the function decorated by @st.composite
. In short, the first parameter must take a positional argument which indicates the hypothesis drawing function. This parameter is "swallowed" when you decorate this with @st.composite
to make strategy factory callables (kind of like how self
is "swallowed" when calling instance methods).