Skip to main content

pytest-quantum

A cross-framework pytest plugin for quantum program testing.

PyPI Python License: MIT CI Docs

Test quantum programs the same way you test classical code, using pytest. Works with Qiskit, Cirq, Amazon Braket, PennyLane, Graphix, Pytket, Stim, QuTiP, Tequila, and Mitiq.

Full documentation


Who should use pytest-quantum?

  • Quantum software engineers writing gate-model circuits in Qiskit, Cirq, PennyLane, or Pytket who need robust, reproducible unit tests.
  • Researchers running VQE or QAOA who need statistically sound assertions on variational algorithm convergence and expressibility.
  • Teams CI-testing quantum algorithms across multiple frameworks and backends, including noisy simulation and hardware targets.
  • Hardware validation engineers characterising real devices with quantum volume, randomised benchmarking, T1/T2/T2* coherence, and XEB fidelity.

What's new in v1.0.0

  • Benchmarking assertions: assert_quantum_volume, assert_randomized_benchmarking, assert_t1_above, assert_t2_above, assert_t2star_above, assert_interleaved_rb, assert_gate_fidelity_above
  • Quantum ML assertions: assert_xeb_fidelity_above, assert_expressibility_above, assert_entanglement_capability_above, assert_no_barren_plateau
  • Cross-platform equivalence: assert_cross_platform_equivalent, assert_qiskit_cirq_equivalent, assert_qiskit_pytket_equivalent
  • Noise channel assertions: assert_depolarizing_channel, assert_amplitude_damping_channel, assert_dephasing_channel, assert_no_leakage, assert_channel_preserves_trace, assert_channel_diamond_norm_below
  • Hardware assertions: assert_backend_calibration, assert_backend_executes, assert_circuit_fits_backend, assert_mirror_fidelity, assert_real_counts_close
  • Mitiq error mitigation: assert_zne_expectation_close, assert_zne_reduces_error, assert_cdr_reduces_error, assert_mitigation_improves_fidelity, assert_pec_reduces_error, assert_pec_expectation_close, assert_error_mitigation_benchmark
  • Sweep / variational: assert_circuit_sweep, assert_circuit_sweep_states, assert_parametrized_unitary_continuous
  • Hardware fixtures: ibm_backend, ionq_backend, quantinuum_backend, quantum_hardware_info
  • New fixtures: multi_backend_runner (parallel multi-backend comparison), benchmark_suite (assertion timing)
  • QASM 2.0 round-trip: assert_qasm2_roundtrip
  • Mid-circuit measurement detection: assert_no_mid_circuit_measurement
  • qiskit-ibm-runtime is now optional (pip install pytest-quantum[ibm])

Why pytest-quantum?

Quantum programs fail in ways classical tests don't handle:

Problem Without pytest-quantum With pytest-quantum
Shot noise flakiness assert counts["00"] == 512 fails ~5% of runs assert_measurement_distribution uses chi-square; fails only when distribution is genuinely wrong
Global phase np.allclose(U1, U2) fails for physically identical states assert_unitary handles global phase automatically
Framework boilerplate Copy-paste AerSimulator() setup in every project aer_simulator fixture injected automatically
Shot count guessing Pick 1024 shots and hope min_shots(epsilon=0.02) gives the statistically correct answer
No structure testing No standard way to assert depth or gate counts assert_circuit_depth, assert_circuit_width, assert_gate_count
No mixed-state testing No standard way to test noisy density matrices assert_density_matrix_close, assert_purity_above, assert_trace_distance_below
No hardware benchmarking No built-in quantum volume or RB assert_quantum_volume, assert_randomized_benchmarking, assert_t1_above
No cross-platform validation Can't compare Qiskit vs Cirq circuit results assert_cross_platform_equivalent, assert_qiskit_cirq_equivalent

Installation

pip install pytest-quantum                  # core (no quantum SDK required)
pip install "pytest-quantum[qiskit]"        # + Qiskit + Aer
pip install "pytest-quantum[cirq]"          # + Cirq
pip install "pytest-quantum[braket]"        # + Amazon Braket
pip install "pytest-quantum[pennylane]"     # + PennyLane
pip install "pytest-quantum[graphix]"       # + Graphix (MBQC)
pip install "pytest-quantum[ibm]"           # + qiskit-ibm-runtime (real IBM hardware)
pip install "pytest-quantum[mitiq]"         # + Mitiq error mitigation
pip install "pytest-quantum[cvxpy]"         # + CVXPY (diamond norm / SDP noise assertions)
pip install "pytest-quantum[all]"           # everything
pip install stim                            # + Stim (QEC)
pip install pytket                          # + Pytket

Quick start (Qiskit)

# test_bell.py — no conftest.py needed, fixtures are injected automatically
from pytest_quantum import assert_measurement_distribution, assert_unitary


def test_bell_distribution(aer_simulator):
    from qiskit import QuantumCircuit, transpile

    qc = QuantumCircuit(2)
    qc.h(0); qc.cx(0, 1); qc.measure_all()
    counts = aer_simulator.run(transpile(qc, aer_simulator), shots=2000).result().get_counts()

    # Chi-square test: won't flake on statistical noise
    assert_measurement_distribution(counts, expected_probs={"00": 0.5, "11": 0.5})


def test_hadamard_unitary():
    import numpy as np
    from qiskit import QuantumCircuit

    qc = QuantumCircuit(1)
    qc.h(0)
    H = np.array([[1, 1], [1, -1]]) / np.sqrt(2)

    # Global-phase-safe — e^(i*theta)*H passes too
    assert_unitary(qc, H)

Quick start (PennyLane)

# test_pennylane.py
import numpy as np
from pytest_quantum import assert_state_fidelity_above


def test_rx_gate(pennylane_device):
    import pennylane as qml

    dev = pennylane_device(wires=1)

    @qml.qnode(dev)
    def rx_circuit(theta):
        qml.RX(theta, wires=0)
        return qml.state()

    state = np.array(rx_circuit(np.pi))  # RX(π) = -iX|0⟩ = -i|1⟩
    assert_state_fidelity_above(state, np.array([0, -1j]), threshold=0.99)

Quick start (Cirq)

# test_cirq.py
import math
import numpy as np
from pytest_quantum import assert_unitary


def test_hadamard_cirq():
    import cirq

    q = cirq.LineQubit.range(1)
    circuit = cirq.Circuit(cirq.H(q[0]))
    H = np.array([[1, 1], [1, -1]]) / math.sqrt(2)
    assert_unitary(circuit, H)
pytest                        # normal suite
pytest --quantum-slow         # include shot-heavy tests
pytest --quantum-shots=4000   # override shot count globally
pytest --quantum-real         # enable real hardware tests (requires credentials)

Decision guide: which assertion to use?

I want to test... Best assertion Alternative
A gate implements a specific unitary assert_unitary assert_circuits_equivalent
Two circuits are equivalent assert_circuits_equivalent assert_unitary
A noisy circuit's output state assert_state_fidelity_above assert_trace_distance_below
Measurement distribution matches expected assert_measurement_distribution assert_counts_close
Two measurement distributions are close assert_counts_close assert_hellinger_close
A density matrix from noisy simulation assert_density_matrix_close assert_trace_distance_below
How mixed/noisy a state is assert_purity_above assert_trace_distance_below
Entanglement in a pure state assert_entanglement_entropy_below assert_schmidt_rank_at_most
Single-qubit state on Bloch sphere assert_bloch_sphere_close assert_states_close
Quantum channel is valid assert_channel_is_cptp assert_process_fidelity_above
VQE / QAOA energy result assert_ground_state_energy_close assert_expectation_value_close
Circuit doesn't change after refactor assert_unitary_snapshot assert_distribution_snapshot
Circuit uses only Clifford gates assert_circuit_is_clifford
QASM export/import preserves semantics assert_qasm_roundtrip (v3) or assert_qasm2_roundtrip (v2)
Logical error rate of QEC code assert_stim_logical_error_rate_below
Backend meets quantum volume spec assert_quantum_volume
Gate fidelity from RB experiment assert_randomized_benchmarking assert_interleaved_rb
T1 / T2 / T2* coherence times assert_t1_above / assert_t2_above / assert_t2star_above
Cross-entropy benchmarking (XEB) assert_xeb_fidelity_above
VQA circuit is expressive assert_expressibility_above assert_entanglement_capability_above
No barren plateau in VQA training assert_no_barren_plateau
Circuit compatible with hardware assert_circuit_fits_backend assert_no_mid_circuit_measurement
Error mitigation improves results assert_zne_reduces_error assert_pec_reduces_error

All 80+ assertions

Unitary / circuit equivalence

assert_unitary(circuit, expected_matrix)
assert_circuits_equivalent(circuit_a, circuit_b)
assert_transpilation_preserves_semantics(orig, compiled)
assert_cross_platform_equivalent(circuit_a, circuit_b)  # v1.0.0
assert_qiskit_cirq_equivalent(qiskit_qc, cirq_circuit)  # v1.0.0
assert_qiskit_pytket_equivalent(qiskit_qc, pytket_circ)  # v1.0.0

State assertions

assert_normalized(statevector)
assert_state_fidelity_above(actual, target, threshold=0.99)
assert_states_close(actual, target, atol=1e-6)

Measurement distributions

assert_measurement_distribution(counts, expected_probs)
assert_counts_close(counts_a, counts_b, max_tvd=0.05)

Density matrix assertions

assert_density_matrix_close(rho, sigma, atol=1e-6)
assert_trace_distance_below(rho, sigma, max_distance=0.01)
assert_purity_above(rho, min_purity=0.95)
assert_partial_trace_close(rho, keep_qubits, expected)

Quantum channel assertions

assert_hermitian(matrix)
assert_positive_semidefinite(matrix)
assert_commutes_with(op_a, op_b)
assert_channel_is_cptp(kraus_ops)
assert_process_fidelity_above(channel_a, channel_b, threshold=0.99)
assert_noise_fidelity_above(noisy_dm, ideal_state, threshold=0.99)

Noise channel assertions (v1.0.0)

assert_depolarizing_channel(kraus_ops, error_rate, atol=1e-6)
assert_amplitude_damping_channel(kraus_ops, gamma, atol=1e-6)
assert_dephasing_channel(kraus_ops, p_dephase, atol=1e-6)
assert_no_leakage(kraus_ops, computational_dim=2)
assert_channel_preserves_trace(kraus_ops)
assert_channel_diamond_norm_below(kraus_ops_a, kraus_ops_b, max_norm=0.01)

Entanglement assertions

assert_entanglement_entropy_below(sv, partition, max_entropy)
assert_bloch_sphere_close(sv, theta, phi, atol=0.1)
assert_schmidt_rank_at_most(sv, partition, max_rank)

Information theory

assert_hellinger_close(counts_a, counts_b, max_distance=0.1)
assert_kl_divergence_below(counts, expected_probs, max_kl=0.1)
assert_cross_entropy_below(counts, expected_probs, max_ce=1.0)

Observable / expectation value

assert_expectation_value_close(actual, expected, atol=0.01)
assert_ground_state_energy_close(actual_energy, expected_energy, atol=0.01)
assert_vqe_converges(vqe_fn, hamiltonian, target_energy, atol=0.1)
assert_cost_decreases(cost_fn, initial_params)

Qiskit Primitives

assert_sampler_distribution(sampler_result, expected_probs)
assert_estimator_close(estimator_result, expected, atol=0.01)

Circuit structure

assert_circuit_depth(circuit, max_depth=10)
assert_circuit_width(circuit, expected_qubits=3)
assert_gate_count(circuit, "cx", expected=2)
assert_gates_in_basis_set(circuit, basis_gates={"cx", "u3"})
assert_circuit_is_clifford(circuit)
assert_has_diagram(circuit, expected_diagram)
assert_no_mid_circuit_measurement(circuit)  # v1.0.0

Transpilation / compilation

assert_transpilation_equivalent(circuit, backend, atol=1e-6)
assert_transpilation_depth_below(circuit, backend, max_depth=20)
assert_gate_count_after_transpilation(circuit, backend, gate, expected)

Sweeps / parametrised circuits

assert_circuit_sweep(circuit, param_values, expected_probs_list)
assert_circuit_sweep_states(circuit, param_values, expected_states)
assert_parametrized_unitary_continuous(circuit, param_range)

Snapshots / golden-file testing

assert_unitary_snapshot(circuit, name)
assert_distribution_snapshot(counts, name, max_tvd=0.05)

QASM round-trips

assert_qasm_roundtrip(circuit)    # OpenQASM 3 (Qiskit) or Cirq JSON
assert_qasm2_roundtrip(circuit)   # OpenQASM 2.0 (Qiskit only) — v1.0.0

QEC / Stim

assert_stim_logical_error_rate_below(circuit, max_error_rate, shots=10000)
assert_stim_detector_error_rate_below(circuit, max_error_rate, shots=10000)
assert_stabilizer_state(statevector, stabilizers)

Benchmarking (v1.0.0)

assert_quantum_volume(backend, target_qv=16, num_trials=100)
assert_randomized_benchmarking(backend, qubit=0, min_fidelity_per_clifford=0.999)
assert_t1_above(backend, qubit=0, target_t1_us=50.0)
assert_t2_above(backend, qubit=0, target_t2_us=30.0)      # Hahn echo
assert_t2star_above(backend, qubit=0, target_t2star_us=20.0)  # Ramsey
assert_interleaved_rb(backend, qubit=0, gate_name="X", gate_circuit=x_circ)
assert_gate_fidelity_above(backend, "cx", [0, 1], target_fidelity=0.99)

Quantum ML (v1.0.0)

assert_xeb_fidelity_above(backend, num_qubits=2, target_fidelity=0.9)
assert_expressibility_above(ansatz_fn, num_qubits=2, num_params=4, target=0.5)
assert_entanglement_capability_above(ansatz_fn, num_qubits=2, num_params=4, target=0.3)
assert_no_barren_plateau(ansatz_fn, num_qubits=4, num_params=16)

Hardware assertions (v1.0.0)

assert_backend_calibration(backend, min_t1_us=30.0, min_cx_fidelity=0.99)
assert_backend_executes(circuit, backend, shots=1024)
assert_circuit_fits_backend(circuit, backend)
assert_mirror_fidelity(backend, qubit, target_fidelity=0.95)
assert_real_counts_close(job, expected_probs, max_tvd=0.1)

Mitiq error mitigation (v0.4.0+)

assert_zne_expectation_close(circuit, observable, expected, atol=0.1)
assert_zne_reduces_error(circuit, observable, noisy_val, ideal_val)
assert_cdr_reduces_error(circuit, observable, noisy_val, ideal_val)
assert_mitigation_improves_fidelity(circuit, noisy_state, ideal_state)
assert_pec_reduces_error(circuit, observable, noisy_val, ideal_val)
assert_pec_expectation_close(circuit, observable, expected, atol=0.1)
assert_error_mitigation_benchmark(circuit, observable, methods=["zne", "cdr"])

Framework support

Framework Version Fixtures Unitary Clifford Gate count
Qiskit + Aer ≥ 1.0 aer_simulator, aer_statevector_simulator, aer_noise_simulator, qiskit_sampler, qiskit_estimator ✓ ✓ ✓
Cirq ≥ 1.0 cirq_simulator, cirq_sampler ✓ ✓ ✓
Amazon Braket ≥ 1.0 braket_simulator, braket_cloud_device ✓ ✓ ✓
PennyLane ≥ 0.36 pennylane_device ✓ ✓ ✓
Graphix ≥ 0.3 graphix_backend ✗ ✗ ✗
Pytket ≥ 1.0 pytket_circuit_factory ✓ ✓ ✓
Stim ≥ 1.13 stim_sampler ✗ ✗ ✗
QuTiP ≥ 4.7 qutip_solver ✗ ✗ ✗
Tequila ≥ 1.9 tequila_backend ✗ ✗ ✗
IBM Quantum runtime ≥ 0.45 ibm_backend ✓ ✗ ✗
IonQ via qiskit-ionq ionq_backend ✗ ✗ ✗
Quantinuum via pytket-quantinuum quantinuum_backend ✗ ✗ ✗

Fixtures

All fixtures are auto-discovered (no imports needed) and skip automatically if the required SDK is not installed.

Fixture Framework Returns
aer_simulator Qiskit / Aer AerSimulator()
aer_statevector_simulator Qiskit / Aer AerSimulator(method="statevector")
aer_noise_simulator Qiskit / Aer make_simulator(error_rate) factory
qiskit_sampler Qiskit 1.0+ StatevectorSampler()
qiskit_estimator Qiskit 1.0+ StatevectorEstimator()
cirq_simulator Cirq cirq.Simulator()
cirq_sampler Cirq run_fn(circuit, shots) callable
braket_simulator Amazon Braket LocalSimulator()
braket_cloud_device Amazon Braket cloud AwsDevice(arn) (requires --quantum-real)
graphix_backend Graphix backend with .run_pattern(pattern)
pennylane_device PennyLane make_device(wires, shots=None) factory
pytket_circuit_factory Pytket pytket.Circuit class
stim_sampler Stim sample_fn(circuit, shots) callable
qutip_solver QuTiP solve(H, psi0, tlist, c_ops) callable
tequila_backend Tequila tequila module
ibm_backend IBM Quantum IBMBackend (requires --quantum-real)
ionq_backend IonQ IonQ backend (requires --quantum-real)
quantinuum_backend Quantinuum Quantinuum backend (requires --quantum-real)
quantum_hardware_info All Dict of credential availability
quantum_benchmark All benchmark timing wrapper
benchmark_suite All assertion timing suite (v1.0.0)
shot_budget All shot counter
multi_backend_runner All parallel multi-backend runner (v1.0.0)
quantum_shots All int | None from --quantum-shots
quantum_significance All float | None from --quantum-significance

Markers

@pytest.mark.quantum                # tag as a quantum test
@pytest.mark.quantum_slow           # skip unless --quantum-slow is passed
@pytest.mark.quantum_real           # skip unless --quantum-real is passed (hardware tests)
@pytest.mark.shots(n=4000)          # shot count hint for this test
@pytest.mark.significance(p=0.01)   # p-value threshold for this test
@pytest.mark.quantum_backends("qiskit", "cirq", "pennylane")  # parametrize over backends

Shot budget utilities

from pytest_quantum import min_shots, recommended_shots

n = min_shots(epsilon=0.05)                            # 293 shots to detect 5% TVD
n = recommended_shots({"00": 0.499, "01": 0.001, "11": 0.5})  # 5000 (driven by 0.1% outcome)

Statistical primitives

from pytest_quantum import fidelity, tvd, tvd_from_counts, chi_square_test

fidelity(psi, phi)                       # |<psi|phi>|^2, global-phase invariant
tvd(p, q)                                # Total Variation Distance (0=identical, 1=disjoint)
tvd_from_counts(counts_a, counts_b)      # TVD from count dicts
chi_square_test(counts, expected_probs)  # returns (statistic, p_value)

Random generators

from pytest_quantum.random import (
    random_statevector,      # Haar-random pure state
    random_density_matrix,   # random mixed state
    random_unitary,          # Haar-random unitary
    random_kraus_channel,    # random CPTP channel
    depolarizing_kraus,      # depolarizing channel Kraus operators
)

CLI options

Option Description
--quantum-slow Run quantum_slow-marked tests (skipped by default)
--quantum-real Run quantum_real-marked tests against real hardware
--quantum-shots N Override shot count for all tests
--quantum-significance P Override p-value threshold globally
--quantum-update-snapshots Regenerate all snapshot files

Academic citation

If you use pytest-quantum in research, please cite it:

@software{ghatule2026pytest_quantum,
  title  = {pytest-quantum: A cross-framework pytest plugin for quantum program testing},
  author = {Ghatule, Tejas},
  year   = {2026},
  url    = {https://github.com/qbench/pytest-quantum},
  version = {1.0.0}
}

See CITATION.cff for full metadata.


Contributing

See CONTRIBUTING.md for setup, test commands, code style, and PR checklist.

git clone https://github.com/qbench/pytest-quantum
cd pytest-quantum
uv sync --all-extras --group dev
uv run pytest          # 646+ tests
uv run ruff check src/ tests/
uv run mypy src/

License

MIT. See LICENSE.

Release files for pytest-quantum 1.0.0

For a detailed explanation of source distributions (sdists) and built distributions (wheels), please see the package formats documentation.

Source distribution (sdist)

Source distribution for pytest-quantum 1.0.0
File Size Uploaded
pytest_quantum-1.0.0.tar.gz 387.1 kB Details

Built distribution (wheel)

Table of built distributions (wheels) for pytest-quantum 1.0.0
File Interpreter ABI Platform
pytest_quantum-1.0.0-py3-none-any.whl Python 3 none any Details

Total release size: 494.7 kB

Release files / pytest_quantum-1.0.0.tar.gz

Download URL pytest_quantum-1.0.0.tar.gz
Size 387.1 kB
Tags Source
SHA-256 checksum
How to use checksums
fe042adb6c5ec50039f1ccf70fec7640cafcc878ae5e1a661bc78ed58bd22f93
BLAKE2b-256 checksum
How to use checksums
eb2ff1500ec2fd5102bd4ed0fafa68a7f97d6cd3df6b846599eb5a892eb55818
Upload date
Uploaded using Trusted Publishing?
What is trusted publishing?
Yes
Uploaded via twine/6.1.0 CPython/3.13.7

Provenance

Provenance describes where a file came from. On PyPI, provenance is shared via attestations, which provide a verifiable record of the build or publishing details. View details, limitations and caveats.

PyPI Publish Attestation

PyPI verified that this artifact, at this checksum, originated from the publisher listed below.

Signed by GitHub Actions, verified by PyPI on Mar 22, 2026.

Transparency log

Release files / pytest_quantum-1.0.0-py3-none-any.whl

Download URL pytest_quantum-1.0.0-py3-none-any.whl
Size 107.6 kB
Tags Python 3
SHA-256 checksum
How to use checksums
f7013c3819b5e2aded8298737a85b285eb597c3737c01bc127308d6c5d3ca30a
BLAKE2b-256 checksum
How to use checksums
744399f4a324e14a54d2f432e5699873bf98948cc993945ae46d344a8cc5cfea
Upload date
Uploaded using Trusted Publishing?
What is trusted publishing?
Yes
Uploaded via twine/6.1.0 CPython/3.13.7

Provenance

Provenance describes where a file came from. On PyPI, provenance is shared via attestations, which provide a verifiable record of the build or publishing details. View details, limitations and caveats.

PyPI Publish Attestation

PyPI verified that this artifact, at this checksum, originated from the publisher listed below.

Signed by GitHub Actions, verified by PyPI on Mar 22, 2026.

Transparency log

Release history Release notifications | RSS feed

This release

1.0.0 This release

2 release files

0.5.0

2 release files

0.4.0

2 release files

0.3.0

2 release files

0.1.1

2 release files

0.1.0

2 release files

Anthropic, PBC Visionary sponsor Bloomberg Visionary sponsor Hudson River Trading Visionary sponsor Meta Visionary sponsor NVIDIA Visionary sponsor Microsoft Sustainability sponsor Depot Continuous Integration AWS Cloud computing and Security Sponsor Datadog Monitoring Fastly CDN Google Download Analytics Sentry Error logging StatusPage Status page