Writing assertions

qtest ships four assertions, each matched to a different question you might ask of a quantum program. This guide explains which assertion to reach for, how to choose a tolerance, and what the failure mode looks like when something is wrong.

Picking the right assertion

You want to check…

Use

Compare against

A measurement distribution from a real run.

assert_distribution_close()

Expected probabilities (or counts).

The state vector produced by a (non-measured) circuit.

assert_state_close()

Expected Statevector.

Whether an operator is unitary at all.

assert_unitary()

\(U^\dagger U \approx I\).

Whether two circuits compute the same unitary.

assert_circuit_equivalent()

Each other (up to global phase).

Statistical vs. state-vector assertions

The most common mistake new users make is comparing measurement counts with strict equality, or comparing state vectors with statistical tolerances. They are not interchangeable.

Statistical assertionsassert_distribution_close() — operate on shot counts produced by a real (or simulated) run. They take shot noise into account: a distribution that is statistically indistinguishable from the expected one passes, even if the raw counts differ.

from qtest import assert_distribution_close

# ``assert_distribution_close`` runs the circuit internally on
# qtest's default backend — you pass the circuit, not pre-computed
# counts.
assert_distribution_close(
    qc, expected={"0": 0.5, "1": 0.5}, shots=4096, tolerance=0.03
)

State-vector assertionsassert_state_close() — work on exact Statevector objects. They support named reference states ("plus", "minus", "bell", …) and are phase-insensitive by default. Use them for tight, unambiguous gate-level tests where you don’t want shot-noise variance.

from qtest import assert_state_close

# Pass the circuit directly — qtest computes the state vector for
# you. Named reference states make tight, readable gate tests.
assert_state_close(qc, expected_state="plus", tolerance=1e-10)

Choosing a tolerance

The right tolerance depends on which assertion you’re using:

  • Distribution tolerance is the maximum total-variation distance you will accept. With \(N\) shots, the natural floor is \(\mathcal{O}(1/\sqrt{N})\) — at 4096 shots, tolerance=0.03 is a reasonable, lightly-conservative default. The helper qtest.metrics.auto_tolerance() will compute one for you given a shot count and a target false-positive rate.

  • State / unitary tolerance is a hard numerical bound. 1e-10 is appropriate when comparing against an analytically known state on a noiseless simulator; 1e-6 is reasonable when noise or finite precision is in the loop.

  • When in doubt, start strict and loosen on failure rather than the reverse. A test that passes with tolerance=0.5 is barely testing anything.

Failure messages

qtest assertions raise AssertionError with a structured message that reports the metric value, the tolerance, and (where useful) the top-contributing offending outcomes. For example:

AssertionError: distribution mismatch
  metric:    total_variation_distance
  observed:  0.0871
  tolerance: 0.030
  worst offenders:
    "00": observed=0.612, expected=0.500
    "11": observed=0.388, expected=0.500

That is by design: when a test fails in CI, the message alone should be enough to diagnose whether the failure is a real bug, a tolerance that is too tight, or a shot count that is too low.

Global-phase insensitivity

Two state vectors \(\lvert\psi\rangle\) and \(e^{i\theta}\lvert\psi\rangle\) are physically identical — every measurement on them returns the same statistics. qtest’s state and unitary assertions are global-phase-insensitive by default, computing \(1 - |\langle\psi_1|\psi_2\rangle|^2\) (infidelity) rather than the raw L2 distance. Pass global_phase=False to assert_state_close() if you really do care about phase.

Where to go next

  • Property-based testing — turn the rules above into Hypothesis properties that hold over many random circuits.

  • pytest integration — wire these assertions into a real pytest configuration with shared fixtures, markers, and CLI flags.

  • Assertions — the full autogenerated API reference for every assertion.