{"title":"Property-Based Testing in Python: Start with Invariants","excerpt":"A property test asserting isinstance(encode(s), str) can run sixty thousand inputs and establish nothing, because no input in the domain could falsify it. This guide covers the three families of claim that can actually fail, why the default text alphabet is a decision rather than a default, constraining the domain in the strategy instead of with assume(), the correctness health check that catches function-scoped fixtures not resetting per input, and how to pin every shrunk counterexample so it never has to be rediscovered.","canonicalUrl":"https://automationtester.in/blog/automation-tutorials/python-property-based-testing-invariants","category":{"name":"Automation Tutorials","slug":"automation-tutorials"},"tags":["python","property-based-testing","hypothesis","test-design","pytest","test-data"],"author":{"name":"Shashank Rawlani","url":"https://shashank.rawlani.com","linkedin":"https://www.linkedin.com/in/shashankrawlani/"},"datePublished":"2026-09-22T13:30:00.000Z","dateModified":"2026-09-22T18:48:26.686Z","coverImage":{"url":"https://ik.imagekit.io/automationtester/automationtester-in/blog/covers/v3/python-property-based-testing-invariants.webp","alt":"Dark technical illustration. A regular grid of small dots covers the entire frame; each dot outside a central region is dim grey and circled faintly, while the dots inside that region are larger and bright green. A thick green closed outline with a slightly irregular, blob-like shape encloses the green dots, marking them off from the grey field around them. To the right of that outline, out among the grey dots, sits a large orange dot inside a filled orange circle, itself surrounded by a wider dashed orange ring. A dashed orange line runs from the right edge of the green outline toward that orange circle but stops well short of it, ending in a small orange dot in open space. Along the lower part of the green region, four orange circles of steadily decreasing size are joined left to right by short solid orange lines, ending at a small solid orange dot held between two short vertical orange brackets."},"contentHtml":"<div class=\"callout callout-info\"><strong>Quick answer:</strong> Write the invariant and the legal input domain before you pick a strategy, because the strategy silently decides which bugs are reachable. <code class=\"language-text\">st.text()</code> defaults to a UTF-8 alphabet that excludes surrogates, so a round-trip property built on it cannot find the encoding bug it exists to catch. Constrain the domain with strategy arguments rather than <code class=\"language-text\">assume()</code>, and pin every shrunk counterexample back into the test as <code class=\"language-text\">@example</code>. Those run first, never shrink, and stop the test immediately when they fail.</div>\n\n<p>Here is a property test that has run in CI for six months, a hundred inputs per run, and has never once failed:</p>\n\n<pre class=\"language-python\"><code class=\"language-python\">@given(st.text())\ndef test_encode_returns_a_string(value):\n    assert isinstance(url_encode(value), str)</code></pre>\n\n<p>It is not a property test. It is a type annotation with a random number generator attached. There is no input in the domain that could falsify it, so the sixty thousand inputs it has consumed have established exactly one thing: that <code class=\"language-text\">url_encode</code> returns a string.</p>\n\n<p>Meanwhile the real defect, a lone surrogate character that survives encoding and comes back as something else, was never generated, and could not have been, because the default alphabet excludes surrogates on purpose. The test was not weak because Hypothesis is weak. It was weak because nobody wrote down what had to be true and which inputs counted.</p>\n\n<h2 id=\"a-property-is-a-claim-something-could-violate\">A property is a claim something could violate</h2>\n\n<p>The usable test for whether you have a property is short: name an input that would make it false. If you cannot, you have an assertion about types or a restatement of the implementation.</p>\n\n<p>Three families reliably produce claims that can fail, and they need different amounts of work from you.</p>\n\n<p><strong>Round-trip</strong> is the cheapest and the strongest where it applies, because the oracle is the input itself:</p>\n\n<pre class=\"language-python\"><code class=\"language-python\">@given(st.text())\ndef test_url_encoding_round_trips(value):\n    assert url_decode(url_encode(value)) == value</code></pre>\n\n<p><strong>Metamorphic</strong> properties relate two runs of the same function, which is what you reach for when there is no inverse. Each of these can fail independently, and each names a different mistake:</p>\n\n<pre class=\"language-python\"><code class=\"language-python\">@given(st.lists(st.integers()))\ndef test_sorting_is_idempotent(xs):\n    assert sorted(sorted(xs)) == sorted(xs)\n\n@given(st.lists(st.integers()))\ndef test_sorting_preserves_multiset(xs):\n    assert Counter(sorted(xs)) == Counter(xs)          # nothing invented or lost\n\n@given(st.lists(st.integers()), st.integers())\ndef test_insertion_keeps_order(xs, y):\n    result = insert_sorted(sorted(xs), y)\n    assert all(a &lt;= b for a, b in zip(result, result[1:]))</code></pre>\n\n<p><strong>Differential</strong> properties compare your implementation against something you already trust: the previous version during a rewrite, a slow reference implementation, or the library you are replacing. This is the highest-value family for migrations and the only one that needs no invariant of its own, because the oracle supplies it.</p>\n\n<p>What none of these are is a parametrized test with extra steps. A property that reads <code class=\"language-text\">assert encode(\"a b\") == \"a%20b\"</code> under <code class=\"language-text\">@given</code> is an example that ignores its input, and Hypothesis will burn a hundred generations proving it.</p>\n\n<h2 id=\"the-default-alphabet-is-a-decision\">The default alphabet is a decision</h2>\n\n<p>This is the fact that makes the opening test hollow, and it is stated plainly in the strategies reference. The signature of <code class=\"language-text\">text()</code> is <code class=\"language-text\">text(alphabet=characters(codec='utf-8'), *, min_size=0, max_size=None)</code>, and the documentation explains what that default excludes: \"the default alphabet strategy can generate the full unicode range but excludes surrogate characters because they are invalid in the UTF-8 encoding.\"</p>\n\n<p>It then tells you what to do about it: \"you can use <code class=\"language-text\">characters()</code> without arguments to find surrogate-related bugs such as bpo-34454.\"</p>\n\n<pre class=\"language-python\"><code class=\"language-python\"># Cannot find surrogate bugs. Not because it is wrong — because the\n# default alphabet is scoped to what UTF-8 can encode.\n@given(st.text())\ndef test_round_trips_utf8_safe_text(value):\n    assert url_decode(url_encode(value)) == value\n\n\n# Can. st.characters() with no arguments includes surrogates, so this\n# reaches the class of input the property was written for.\n@given(st.text(st.characters()))\ndef test_round_trips_any_unicode_scalar(value):\n    assert url_decode(url_encode(value)) == value</code></pre>\n\n<p>Now the important part, which is not a Hypothesis question at all: <em>which of those two tests do you want?</em> The second will find inputs your system genuinely cannot store, and it will fail. Whether that failure is a bug or an out-of-contract input is a decision about your API, and the strategy is where you record the answer. Writing the domain down first is what turns that from an accident into a specification:</p>\n\n<pre class=\"language-python\"><code class=\"language-python\"># The contract, stated as a strategy: any Unicode scalar the transport can\n# carry, up to the field's real limit, excluding the control characters the\n# API documents as rejected.\nidentifiers = st.text(\n    alphabet=st.characters(\n        codec=\"utf-8\",\n        exclude_categories=(\"Cc\", \"Cs\"),\n    ),\n    min_size=1,\n    max_size=320,\n)\n\n@given(identifiers)\ndef test_identifier_round_trips(value):\n    assert url_decode(url_encode(value)) == value</code></pre>\n\n<p>Anyone reading that knows what the function promises. Anyone reading <code class=\"language-text\">st.text()</code> knows only that someone had a string-typed parameter.</p>\n\n<h2 id=\"constrain-in-the-strategy-not-in-the-body\">Constrain in the strategy, not in the body</h2>\n\n<p>The reflex when generated data is wrong for a test is to reject it inside the test. Hypothesis supports that: <code class=\"language-text\">assume(condition)</code> is documented as being \"like an <code class=\"language-text\">assert</code> that marks the test case as bad, rather than failing the test,\" and it lets Hypothesis \"try to avoid similar test cases in future.\"</p>\n\n<p>It is still the wrong tool for anything a strategy can express, because every rejected input is generation work thrown away:</p>\n\n<pre class=\"language-python\"><code class=\"language-python\"># Wrong. Roughly half of every generated integer is discarded, and the\n# non-empty constraint discards more. Hypothesis is doing work to build\n# inputs this test will never look at.\n@given(st.integers(), st.lists(st.integers()))\ndef test_scaling_preserves_length(factor, xs):\n    assume(factor &gt; 0)\n    assume(len(xs) &gt; 0)\n    assert len(scale(xs, factor)) == len(xs)\n\n\n# Right. The domain is in the strategy, so every generated input is used.\n@given(st.integers(min_value=1), st.lists(st.integers(), min_size=1))\ndef test_scaling_preserves_length(factor, xs):\n    assert len(scale(xs, factor)) == len(xs)</code></pre>\n\n<p>Hypothesis will tell you when you have crossed the line, via the <code class=\"language-text\">filter_too_much</code> health check, documented as a \"check for when the test is filtering out too many test cases, either through use of <code class=\"language-text\">assume()</code> or <code class=\"language-text\">.filter()</code>, or occasionally for Hypothesis internal reasons.\" Health check names can be given as strings or as enum members, so <code class=\"language-text\">suppress_health_check=[\"filter_too_much\"]</code> and <code class=\"language-text\">suppress_health_check=[HealthCheck.filter_too_much]</code> are equivalent, which is worth knowing so you recognise both in a config file.</p>\n\n<p>Suppressing it is sometimes correct. The docs are relaxed about this, noting that health checks are \"proactive warnings, not correctness errors\" and encouraging suppression \"where you have evaluated they will not pose a problem.\" The trap is suppressing it without looking, because the reason it fires is that your test is exploring a fraction of the space you think it is.</p>\n\n<p>The measurement is one flag. <code class=\"language-text\">--hypothesis-show-statistics</code> reports where the generation budget went, and the filter cost appears as named events:</p>\n\n<pre class=\"language-text\"><code class=\"language-text\">test_even_integers:\n\n  - during generate phase (0.09 seconds):\n      - Typical runtimes: &lt; 1ms, ~ 59% in data generation\n      - 100 passing, 0 failing, and 32 invalid test cases\n      - Events:\n        * 54.55%, Retried draw from integers().filter(lambda x: x % 2 == 0) to satisfy filter\n        * 24.24%, Aborted test because unable to satisfy integers().filter(lambda x: x % 2 == 0)\n  - Stopped because settings.max_examples=100</code></pre>\n\n<p>Read the invalid-case count against the passing count. Thirty-two invalid cases out of a hundred passing is a third of your budget spent generating data that was thrown away, and \"aborted because unable to satisfy\" means some attempts produced nothing at all.</p>\n\n<p>You can add your own labels to that report with <code class=\"language-text\">event()</code>, which records a value whose frequency is summarised at the end. It is the fastest way to answer \"is this strategy actually producing the interesting shape?\" Tag the branch you care about and read the percentage.</p>\n\n<h2 id=\"fixtures-reset-once-per-test-not-once-per-input\">Fixtures reset once per test, not once per input</h2>\n\n<p>Most Hypothesis health checks are about speed. Two are about correctness, and one of those catches an assumption almost everyone makes.</p>\n\n<p>From the reference: <code class=\"language-text\">HealthCheck.function_scoped_fixture</code> \"indicates that a function-scoped pytest fixture is used by an <code class=\"language-text\">@given</code> test. Many Hypothesis users expect function-scoped fixtures to reset once per input, but they actually reset once per test. We proactively raise <code class=\"language-text\">HealthCheck.function_scoped_fixture</code> to ensure you have considered this case.\" The docs are explicit that this and <code class=\"language-text\">differing_executors</code> are the exceptions: \"with the exception of <code class=\"language-text\">HealthCheck.function_scoped_fixture</code> and <code class=\"language-text\">HealthCheck.differing_executors</code>, all health checks warn about performance problems, not correctness errors.\"</p>\n\n<p>Hypothesis generates a hundred inputs by default, which the <code class=\"language-text\">too_slow</code> check describes as generating \"100 (by default) inputs per test execution\", and a function-scoped fixture is set up once for all hundred:</p>\n\n<pre class=\"language-python\"><code class=\"language-python\"># Wrong. `db` is created once, then a hundred customers are inserted into\n# it. Input 87 runs against a table holding 86 earlier customers, so a\n# uniqueness bug passes and a pagination bug fails for the wrong reason.\n@given(st.emails())\ndef test_customer_can_be_created(db, email):\n    customer = create_customer(db, email)\n    assert customer.email == email</code></pre>\n\n<pre class=\"language-python\"><code class=\"language-python\"># Right. Generate the data, and let the test own its own state inside the\n# body so each input starts from the same place.\n@given(st.lists(st.emails(), min_size=1, max_size=25, unique=True))\ndef test_customers_are_unique_by_email(db_factory, emails):\n    with db_factory() as db:\n        for email in emails:\n            create_customer(db, email)\n        assert {c.email for c in all_customers(db)} == set(emails)</code></pre>\n\n<p>Notice the second version also changed the property. Once you accept that all hundred inputs share one database, the honest thing to test is a claim about a <em>batch</em>. A list-valued strategy states that directly, with <code class=\"language-text\">unique=True</code> and a <code class=\"language-text\">max_size</code> that keeps each input affordable. Fighting the fixture lifetime usually means the property was about the wrong unit.</p>\n\n<h2 id=\"pin-every-counterexample-you-are-given\">Pin every counterexample you are given</h2>\n\n<p>Shrinking is the part of Hypothesis that earns its cost: it reduces a failing input to a minimal one, so the counterexample you read is the smallest thing that breaks. Throwing that away after fixing the bug is the most common waste in property-based testing, because the next regression will have to be rediscovered by chance.</p>\n\n<p><code class=\"language-text\">@example</code> is the documented way to keep it. Hypothesis \"will always try [explicit inputs] before generating random inputs,\" and three of its properties matter here: explicit examples \"do not count towards <code class=\"language-text\">settings.max_examples</code>\", they \"do not shrink\", and \"if an explicit example fails, Hypothesis will stop and report the failure without generating any random inputs.\" So a pinned counterexample costs nothing from your generation budget and fails fast and identically every time, which is exactly what you want from a regression test.</p>\n\n<p>The docs name this use directly: if Hypothesis reports that <code class=\"language-text\">f(n=[0, math.nan])</code> fails, \"you can add <code class=\"language-text\">@example(n=[0, math.nan])</code> to your test to quickly reproduce that failure.\"</p>\n\n<pre class=\"language-python\"><code class=\"language-python\">@example(\"\\ud800\").via(\"regression test for AUT-512\")\n@example(\"\").via(\"regression test for AUT-377\")\n@given(st.text(st.characters()))\ndef test_round_trips_any_unicode_scalar(value):\n    assert url_decode(url_encode(value)) == value</code></pre>\n\n<p><code class=\"language-text\">.via()</code> is there so the provenance survives: the docs describe it as documenting where an example came from, and note it is also used by tooling that adds or removes <code class=\"language-text\">@example</code> decorators automatically. There is a matching <code class=\"language-text\">.xfail()</code> for a counterexample you have accepted and not yet fixed, which takes a <code class=\"language-text\">reason</code>, a <code class=\"language-text\">raises</code> tuple, and a condition: <code class=\"language-text\">@example(...).xfail(raises=ZeroDivisionError)</code> keeps a known break visible instead of deleted.</p>\n\n<p>Two other reproduction routes exist and neither replaces <code class=\"language-text\">@example</code>. <code class=\"language-text\">@reproduce_failure(version, blob)</code> replays one exact case from a serialised blob; Hypothesis prints it when <code class=\"language-text\">settings.print_blob</code> is true, \"which is the default in CI\". It is deliberately disposable: \"intended to be temporarily added to your test suite in order to reproduce a failure … not intended to be a permanent addition\", and it \"will error if used on a different Hypothesis version than it was created for.\" Paste it into your editor, not into a commit.</p>\n\n<p><code class=\"language-text\">@seed</code> fixes the randomness so a run repeats, but only \"assuming that there are no other sources of nondeterminism, such as timing, hash randomization, or external state,\" and the docs point out that Hypothesis \"will only print the seed which would reproduce a failure if a test fails in an unexpected way, for instance inside Hypothesis internals.\" So the seed is not your normal reproduction path: the example database is, and it is what makes a failing case re-run automatically until it passes. There is a <code class=\"language-text\">--hypothesis-seed</code> flag for the pytest plugin when you do want it, and setting a seed overrides <code class=\"language-text\">settings.derandomize</code>, which exists \"to enable deterministic CI tests rather than reproducing observed failures.\"</p>\n\n<p>Last piece of hygiene: attach context with <code class=\"language-text\">note()</code> rather than <code class=\"language-text\">print()</code>. A noted value \"is reported for the minimal failing test case\", so it appears next to the shrunk input rather than a hundred times in captured output.</p>\n\n<h2 id=\"when-uniform-randomness-is-the-wrong-search\">When uniform randomness is the wrong search</h2>\n\n<p>Some bugs are not reachable by chance because the interesting region of the input space is small. <code class=\"language-text\">target()</code> turns generation into a search: called with an int or float observation, it \"gives it feedback with which to guide our search for inputs that will cause an error, in addition to all the usual heuristics.\" Hypothesis maximises the value, and \"almost any metric will work so long as it makes sense to increase it\": the docs offer <code class=\"language-text\">-abs(error)</code> as the idiom for driving a value toward zero, along with queue length, runtime and compression ratio as example metrics.</p>\n\n<pre class=\"language-python\"><code class=\"language-python\">@given(st.floats(min_value=0, max_value=1e6, allow_nan=False))\ndef test_interest_accrual_stays_within_a_cent(principal):\n    fast = accrue_fast(principal)\n    exact = accrue_decimal(principal)\n    error = abs(fast - exact)\n    target(error, label=\"accrual error\")   # search toward the worst case\n    assert error &lt; Decimal(\"0.01\")</code></pre>\n\n<p>The honest limits come from the same page, and they are worth quoting rather than softening: \"this is not always a good idea — for example calculating the search metric might take time better spent running more uniformly-random test cases, or your target metric might accidentally lead Hypothesis away from bugs.\" A metric that rewards long inputs will find long inputs and stop finding the empty one.</p>\n\n<p>So this is the last thing to add, not the first. Reach for it when there is \"a natural metric like 'floating-point error', 'load factor' or 'queue length'\", as the docs put it, and leave it out when you would have to invent one.</p>\n\n<h2 id=\"apply-this-now\">Apply this now</h2>\n\n<p>Open your existing property tests and grep the assertions for <code class=\"language-text\">isinstance</code>, <code class=\"language-text\">is not None</code>, and comparisons against a literal. Each one is a test whose input is decorative. Replace it with a round-trip, a metamorphic relation, or a comparison against a reference implementation, and if none of the three applies, delete the test rather than leaving something that cannot fail.</p>\n\n<p>Then run the suite once with <code class=\"language-text\">--hypothesis-show-statistics</code> and read the invalid-case counts. Any test discarding a meaningful share of its budget has its domain in the wrong place; move the constraint from <code class=\"language-text\">assume()</code> into strategy arguments.</p>\n\n<p>What you are aiming at is a test file where every <code class=\"language-text\">@given</code> is preceded by the <code class=\"language-text\">@example</code> decorators recording every counterexample the suite has ever found, each with a <code class=\"language-text\">.via()</code> naming the ticket. That block is the accumulated memory of the property, and it is the part that stops the same bug arriving twice.</p>\n\n<h2 id=\"questions-about-writing-properties\">Questions about writing properties</h2>\n\n<h3 id=\"how-many-inputs-is-enough\">How many inputs is enough?</h3>\n\n<p>A hundred is the default, and raising it has sharply diminishing returns compared with widening the domain. If a test has run ten thousand inputs without failing, the likely explanation is that the strategy cannot reach the failing region, not that you need twenty thousand. Spend the effort on the alphabet, the size bounds and the shape of the generated object instead. The statistics output reports how much of the runtime went into generation, which tells you whether more inputs are even affordable.</p>\n\n<h3 id=\"should-property-tests-replace-example-tests\">Should property tests replace example-based tests?</h3>\n\n<p>No, and the two answer different questions. An example test pins a specific documented behaviour that a reader can check against the spec: <code class=\"language-text\">encode(\"a b\") == \"a%20b\"</code> is worth keeping precisely because it is concrete. A property states what must hold across the domain. The useful arrangement is both in one place: <code class=\"language-text\">@example</code> decorators for the documented and historically broken cases, <code class=\"language-text\">@given</code> for the domain, one assertion that covers all of them.</p>\n\n<h3 id=\"what-if-the-property-is-slower-than-the-function\">What if the property is slower than the function under test?</h3>\n\n<p>That is normal for differential testing, where the oracle is a slow reference implementation, and it is usually still worth it. You are buying correctness evidence, not throughput. Keep such tests out of the fast feedback loop rather than weakening them: a smaller <code class=\"language-text\">max_examples</code> locally and a larger one on a nightly profile is the standard split, and Hypothesis supports registering named profiles for exactly this.</p>\n\n<h3 id=\"is-it-cheating-to-shrink-the-domain-until-the-test-passes\">Is it cheating to shrink the domain until the test passes?</h3>\n\n<p>Only if you do it silently. Narrowing the strategy is how you record a contract, and a strategy that excludes control characters because the API rejects them is documentation. What makes it cheating is narrowing it in response to a failure without deciding whether the failure was a bug, at which point the commit that shrinks the domain is the commit that hides the defect. Write the reason in the strategy definition, not in the commit message, where the next reader will not look.</p>\n\n<h3 id=\"can-hypothesis-test-stateful-systems\">Can Hypothesis test stateful systems?</h3>\n\n<p>Yes, through rule-based state machines, which generate sequences of operations rather than single values, and that is the right tool once your invariant is about a system rather than a function, such as \"the balance never goes negative across any sequence of deposits and withdrawals\". The signal that you need it is a property test whose body is building up state in a loop before asserting, which is the shape the fixture section above arrives at. Note that <code class=\"language-text\">@seed</code> applies to a state machine class as well as a test function, so the same reproduction story holds.</p>\n\n<h2 id=\"primary-references\">Primary references</h2>\n\n<ul>\n<li><a href=\"https://hypothesis.readthedocs.io/en/latest/reference/strategies.html#hypothesis.strategies.text\">Hypothesis — Strategies: text()</a>. That the default alphabet is <code class=\"language-text\">characters(codec='utf-8')</code>, that it \"can generate the full unicode range but excludes surrogate characters because they are invalid in the UTF-8 encoding\", and that you \"can use <code class=\"language-text\">characters()</code> without arguments to find surrogate-related bugs such as bpo-34454\".</li>\n<li><a href=\"https://hypothesis.readthedocs.io/en/latest/reference/api.html#explicit-inputs\">Hypothesis — API: Explicit inputs (@example)</a>. That explicit inputs are always tried before random ones; that they \"do not count towards <code class=\"language-text\">settings.max_examples</code>\"; that they \"do not shrink\" and Hypothesis \"will stop and report the failure without generating any random inputs\" when one fails; the documented use of pinning a reported counterexample; and the <code class=\"language-text\">.xfail()</code> and <code class=\"language-text\">.via()</code> modifiers, including that <code class=\"language-text\">.via</code> supports tooling that adds or removes <code class=\"language-text\">@example</code> decorators.</li>\n<li><a href=\"https://hypothesis.readthedocs.io/en/latest/reference/api.html#hypothesis.HealthCheck\">Hypothesis — API: HealthCheck</a>. That health checks are \"a proactive warning, not an error\"; that <code class=\"language-text\">filter_too_much</code> checks \"for when the test is filtering out too many test cases, either through use of <code class=\"language-text\">assume()</code> or <code class=\"language-text\">.filter()</code>\"; that <code class=\"language-text\">too_slow</code> exists because \"Hypothesis generates 100 (by default) inputs per test execution\"; and that names may be given as strings or enum members in <code class=\"language-text\">suppress_health_check</code>.</li>\n<li><a href=\"https://hypothesis.readthedocs.io/en/latest/reference/api.html#correctness-health-checks\">Hypothesis — API: Correctness health checks</a>. That <code class=\"language-text\">function_scoped_fixture</code> and <code class=\"language-text\">differing_executors</code> are the only health checks reporting correctness rather than performance, and the statement that \"many Hypothesis users expect function-scoped fixtures to reset once per input, but they actually reset once per test\".</li>\n<li><a href=\"https://hypothesis.readthedocs.io/en/latest/reference/api.html#hypothesis.assume\">Hypothesis — API: assume()</a>. That <code class=\"language-text\">assume</code> \"is like an <code class=\"language-text\">assert</code> that marks the test case as bad, rather than failing the test\", and that it lets Hypothesis \"try to avoid similar test cases in future\".</li>\n<li><a href=\"https://hypothesis.readthedocs.io/en/latest/reference/api.html#hypothesis.event\">Hypothesis — API: event()</a>. That events are summarised as frequencies at the end of a run, the worked <code class=\"language-text\">--hypothesis-show-statistics</code> output showing passing, failing and invalid test-case counts alongside \"Retried draw … to satisfy filter\" and \"Aborted test because unable to satisfy …\" percentages, and that two events are the same if their string forms match.</li>\n<li><a href=\"https://hypothesis.readthedocs.io/en/latest/reference/api.html#hypothesis.note\">Hypothesis — API: note()</a>. That a noted value \"is reported for the minimal failing test case, and on <code class=\"language-text\">Verbosity.verbose</code> or higher\", which is why it belongs where a <code class=\"language-text\">print()</code> would otherwise go.</li>\n<li><a href=\"https://hypothesis.readthedocs.io/en/latest/reference/api.html#reproducing-inputs\">Hypothesis — API: Reproducing inputs</a>. That <code class=\"language-text\">@reproduce_failure</code> replays exactly one case from a serialised blob, is printed when <code class=\"language-text\">print_blob</code> is true (\"the default in CI\"), is \"not intended to be a permanent addition\" and \"will error if used on a different Hypothesis version\"; and that <code class=\"language-text\">@seed</code> reproduces a run only absent other nondeterminism, overrides <code class=\"language-text\">settings.derandomize</code>, is exposed as <code class=\"language-text\">--hypothesis-seed</code>, and is only printed automatically when a test \"fails in an unexpected way\".</li>\n<li><a href=\"https://hypothesis.readthedocs.io/en/latest/reference/api.html#hypothesis.target\">Hypothesis — API: target()</a>. That <code class=\"language-text\">target</code> takes a finite int or float observation to \"guide our search for inputs that will cause an error, in addition to all the usual heuristics\", that Hypothesis maximises it, that \"almost any metric will work so long as it makes sense to increase it\", and the <code class=\"language-text\">-abs(error)</code> idiom.</li>\n<li><a href=\"https://hypothesis.readthedocs.io/en/latest/reference/api.html#targeted-property-based-testing\">Hypothesis — API: Targeted property-based testing</a>. The stated trade-off: that targeting \"is not always a good idea — for example calculating the search metric might take time better spent running more uniformly-random test cases, or your target metric might accidentally lead Hypothesis away from bugs\", and the recommendation to try it where a natural metric such as floating-point error, load factor or queue length exists.</li>\n<li><a href=\"https://hypothesis.readthedocs.io/en/latest/reference/api.html#hypothesis.settings.max_examples\">Hypothesis — API: settings.max_examples</a>. That once this many satisfying test cases have been considered without a failure, \"Hypothesis will stop looking\", and the note that what the setting name calls \"examples\" are now referred to as test cases throughout the documentation.</li>\n</ul>\n"}