1

I am trying to execute a function decorated with Hypothesis' @strategy.composite decorator.

I know I can test functions using the @given decorator, such as -

from hypothesis import given
from hypothesis import strategies as st

@given(st.integers(min_value=1))
def test_bar(x):
    assert x > 0

with pytest using - pytest <filename.py>.

But in the case of a function with the @strategy.composite decorator like -

from hypothesis import strategies as st
from hypothesis import given
import pytest

@st.composite
def test_foo(draw):
    arg1 = draw(st.integers(min_value=1))
    arg2 = draw(st.lists(st.integers(), min_size=arg1, max_size=arg1))
    print(arg1, " ", arg2)
    assert(len(arg2) == arg1)

I am unable to execute the tests in a similar way.
When using pytest I am unable to execute the tests (using python to execute the python file does nothing) -

[reik@reik-msi tests]$ pytest testhypo.py
==================================== test session starts ====================================
platform linux -- Python 3.8.3, pytest-5.4.3, py-1.8.1, pluggy-0.13.1
rootdir: /home/reik/tests
plugins: hypothesis-5.16.0, lazy-fixture-0.6.3
collected 1 item                                                                            

testhypo.py F                                                                         [100%]

========================================= FAILURES ==========================================
_________________________________________ test_foo __________________________________________

item = <Function test_foo>

    @pytest.hookimpl(hookwrapper=True)
    def pytest_runtest_call(item):
        if not hasattr(item, "obj"):
            yield
        elif not is_hypothesis_test(item.obj):
            # If @given was not applied, check whether other hypothesis
            # decorators were applied, and raise an error if they were.
            if getattr(item.obj, "is_hypothesis_strategy_function", False):
>               raise InvalidArgument(
                    "%s is a function that returns a Hypothesis strategy, but pytest "
                    "has collected it as a test function.  This is useless as the "
                    "function body will never be executed.  To define a test "
                    "function, use @given instead of @composite." % (item.nodeid,)
                )
E               hypothesis.errors.InvalidArgument: testhypo.py::test_foo is a function that returns a Hypothesis strategy, but pytest has collected it as a test function.  This is useless as the function body will never be executed.  To define a test function, use @given instead of @composite.

/usr/lib/python3.8/site-packages/hypothesis/extra/pytestplugin.py:132: InvalidArgument
================================== short test summary info ==================================
FAILED testhypo.py::test_foo - hypothesis.errors.InvalidArgument: testhypo.py::test_foo is...
===================================== 1 failed in 0.06s =====================================

I tried adding the function call test_foo() but I got the same error.

Then I tried adding @given above the function and got a different error -

========================================== ERRORS ===========================================
________________________________ ERROR at setup of test_foo _________________________________
file /usr/lib/python3.8/site-packages/hypothesis/core.py, line 903
      def run_test_as_given(test):
E       fixture 'test' not found
>       available fixtures: cache, capfd, capfdbinary, caplog, capsys, capsysbinary, doctest_namespace, monkeypatch, pytestconfig, record_property, record_testsuite_property, record_xml_attribute, recwarn, tmp_path, tmp_path_factory, tmpdir, tmpdir_factory
>       use 'pytest --fixtures [testpath]' for help on them.

/usr/lib/python3.8/site-packages/hypothesis/core.py:903
================================== short test summary info ==================================
ERROR testhypo.py::test_foo

if I do @given() - note the extra braces, instead, I get another error -

========================================= FAILURES ==========================================
_________________________________________ test_foo __________________________________________

arguments = (), kwargs = {}

    def wrapped_test(*arguments, **kwargs):
>       raise InvalidArgument(message)
E       hypothesis.errors.InvalidArgument: given must be called with at least one argument

/usr/lib/python3.8/site-packages/hypothesis/core.py:234: InvalidArgument
================================== short test summary info ==================================
FAILED testhypo.py::test_foo - hypothesis.errors.InvalidArgument: given must be called wit...

I tried wrapping the code inside another function -

from hypothesis import strategies as st
from hypothesis import given
import pytest

def demo():
    @st.composite
    def test_foo(draw):
        arg1 = draw(st.integers(min_value=1))
        arg2 = draw(st.lists(st.integers(), min_size=arg1, max_size=arg1))
        print(arg1, " ", arg2)
        assert(len(arg2) == arg1)

but that did not work either -

[reik@reik-msi tests]$ python testhypo.py
[reik@reik-msi tests]$ pytest testhypo.py
==================================== test session starts ====================================
platform linux -- Python 3.8.3, pytest-5.4.3, py-1.8.1, pluggy-0.13.1
rootdir: /home/reik/tests
plugins: hypothesis-5.16.0, lazy-fixture-0.6.3
collected 0 items                                                                           

=================================== no tests ran in 0.00s ===================================

(I also tried putting the demo function call at the end of the file, but that did not change the testing behaviour in any way)

The Hypothesis quick start guide says that calling the function would work, but it clearly does not. (To be fair, the documentation does not specify how to run tests with @composite)

How do I test the functions that are decorated with @strategy.composite? I do not have to use pytest - I would prefer not having to use it actually, but it seemed the easiest way to test the functions (which were decorated with @given) so I decided to go that route.

Pratyush Das
  • 460
  • 7
  • 24

2 Answers2

2

@st.composite is a helper function for defining custom strategies, not tests.

What you're trying to do can be accomplished by using @given and st.data():

@given(st.data())
def test_foo(data):
    x = data.draw(st.integers())
    ...

https://hillelwayne.com/post/property-testing-complex-inputs/ gives a good overview of how these techniques are used.

Zac Hatfield-Dodds
  • 2,455
  • 6
  • 19
0

I had the same issue. Here is a minimal and complete working example of using @composite.

@dataclass
class Container():
    id: int
    value: str

@st.composite
def generate_containers(draw):
    _id = draw(st.integers())
    _value = draw(st.characters())
    return Container(id=_id, value=_value)

@given(generate_containers())
def test_container(container):
    assert isinstance(container, Container)
    assert isinstance(container.id, int)
    assert isinstance(container.value, str)

anilbey
  • 1,817
  • 4
  • 22
  • 38