The idea is to use the less complicated form of metaprogramming in python (decorators) to delay the call to hypothesis.given. This is done by applying the function decorator delayed_given, which stores strategies and partial strategy functions passed to it in a new property on the test function.
Then when a test class is created, we apply the class decorator initialize_tests that iterates over the test methods, accesses the property we just defined, evaluates any partial strategy functions with a fixed set of parameters, passes the result to given and finally replaces the test function with its decorated version.
As an example, this would amount to something similar to:
class TestNumpyReduction(ReductionTests, NumpyTestMixin):
...
given_args, given_kwargs = instantiate_given(
test_variable.__hypothesis_given__,
array_strategy_fn=array_strategy_fn,
)
test_variable = given(*given_args, **given_kwargs)(test_variable)
...
Assuming this works properly (I'll still have to write tests), we can define test classes like this:
class ReductionTests(DuckArrayTestMixin):
@pytest.mark.parametrize("op", ["cumsum", "cumprod"])
@delayed_given(partial(xrst.variables))
def test_variable_cumulative(self, op, variable):
...
and they'd be used like this:
@initialize_tests
class TestReductionNumpy(ReductionTests, NumpyTestMixin):
pass
This is obviously a bit more complex than normal code, but the end result does not look too magical to me. What do you think?
cc @TomNicholas, @Zac-HD