I can manually define an
Address builder strategy:
import attrs from hypothesis import given import hypothesis.strategies as st @attrs.frozen(kw_only=True) class Address: street: str city: str AddressStrategy = st.builds( Address, street=st.text(), city=st.text() ) @given(AddressStrategy) def test_proper_address(address): assert len(address.city) < 4
When I run pytest, it indeed catches my bug:
address = Address(street='', city='0000') # <--- counterexample address found - good ! @given(AddressStrategy) def test_proper_address(address): > assert len(address.city) < 4 E AssertionError: assert 4 < 4 E + where 4 = len('0000') E + where '0000' = Address(street='', city='0000').city main.py:23: AssertionError
According to the docs, it seems like it should be possible to use an auto-generated address builder:
builds()will be used automatically for classes with type annotations on init …
But when I try the following options, neither work:
# If you don't specify city and street, st.builds() will infer them from types address_strategy = st.builds(Address) # Or you can use various stronger grades of magic: @given(st.from_type(Address)) # <-- get me an instance of this type def test_proper_address(address): pass @given(address=...) # <-- infer the address strategy from type hints def test_proper_address(address: Address): pass @given(...) # <-- infer *all* strategies from type hints def test_proper_address(name: str, address: Address): pass
The goal is that you can use as much magic as you like, but can also wind it back gradually if you need to customize just a little bit more of each strategy. For example, maybe we need at-most-length-four strings for the city?
address_strategy = st.builds( Address, # street=st.text(), # <-- no need to spell this out, it'll be inferred city=st.text(max_size=4) # <-- but we do want to customize this one ) # After we register this strategy, the more-magic options will infer it correctly: st.register_type_strategy(Address, address_strategy) # <-- e.g. in `conftest.py` @given(...) def test_proper_address(address: Address): assert len(address.city) < 4 # <-- this will pass now!