This is a lightning introduction to the most important features of Hypothesis; enough to get you started writing tests. The :doc:`tutorial <tutorial/index>` introduces these features (and more) in greater detail.
pip install hypothesisCreate a new file called example.py, containing a simple test:
# contents of example.py
from hypothesis import given, strategies as st
@given(st.integers())
def test_integers(n):
print(f"called with {n}")
assert isinstance(n, int)
test_integers()|@given| is the standard entrypoint to Hypothesis. It takes a strategy, which describes the type of inputs you want the decorated function to accept. When we call test_integers, Hypothesis will generate random integers (because we used the |st.integers| strategy) and pass them as n. Let's see that in action now by running python example.py:
called with 0
called with -18588
called with -672780074
called with 32616
...
We just called test_integers(), without passing a value for n, because Hypothesis generates random values of n for us.
Note
By default, Hypothesis generates 100 random inputs. You can control this with the |max_examples| setting.
A Hypothesis test is still a regular python function, which means pytest or unittest will pick it up and run it in all the normal ways.
# contents of example.py
from hypothesis import given, strategies as st
@given(st.integers(0, 200))
def test_integers(n):
assert n < 50This test will clearly fail, which can be confirmed by running pytest example.py:
$ pytest example.py
...
@given(st.integers())
def test_integers(n):
> assert n < 50
E assert 50 < 50
E Failing test case: test_integers(
E n=50,
E )
Arguments to |@given|
You can pass multiple arguments to |@given|:
@given(st.integers(), st.text())
def test_integers(n, s):
assert isinstance(n, int)
assert isinstance(s, str)Or use keyword arguments:
@given(n=st.integers(), s=st.text())
def test_integers(n, s):
assert isinstance(n, int)
assert isinstance(s, str)Sometimes, you need to remove invalid cases from your test. The best way to do this is with |.filter|:
@given(st.integers().filter(lambda n: n % 2 == 0))
def test_integers(n):
assert n % 2 == 0For more complicated conditions, you can use |assume|, which tells Hypothesis to discard any test case with a false-y argument:
@given(st.integers(), st.integers())
def test_integers(n1, n2):
assume(n1 != n2)
# n1 and n2 are guaranteed to be different hereNote
You can learn more about |.filter| and |assume| in the :doc:`/tutorial/adapting-strategies` tutorial page.
You may want an input to depend on the value of another input. For instance, you might want to generate two integers n1 and n2 where n1 <= n2.
You can do this using the |st.composite| strategy. |st.composite| lets you define a new strategy which is itself built by drawing values from other strategies, using the automatically-passed draw function.
@st.composite
def ordered_pairs(draw):
n1 = draw(st.integers())
n2 = draw(st.integers(min_value=n1))
return (n1, n2)
@given(ordered_pairs())
def test_pairs_are_ordered(pair):
n1, n2 = pair
assert n1 <= n2In more complex cases, you might need to interleave generation and test code. In this case, use |st.data|.
@given(st.data(), st.text(min_size=1))
def test_string_characters_are_substrings(data, string):
assert isinstance(string, str)
index = data.draw(st.integers(0, len(string) - 1))
assert string[index] in stringHypothesis works with pytest features, like |pytest.mark.parametrize|:
import pytest
from hypothesis import given, strategies as st
@pytest.mark.parametrize("operation", [reversed, sorted])
@given(st.lists(st.integers()))
def test_list_operation_preserves_length(operation, lst):
assert len(lst) == len(list(operation(lst)))Hypothesis also works with pytest fixtures:
import pytest
@pytest.fixture(scope="session")
def shared_mapping():
return {n: 0 for n in range(101)}
@given(st.integers(0, 100))
def test_shared_mapping_keys(shared_mapping, n):
assert n in shared_mapping