Curriculum / Real-World Quantum Python / Testing Quantum Programs

Lesson 14 of 20ReadingPro+60 XP

Testing Quantum Programs

Learn the patterns for reliably testing quantum code, from unit tests to probabilistic assertion strategies.

Testing Quantum Programs

Quantum programs are probabilistic, they produce distributions, not deterministic outputs. This makes testing fundamentally different from classical software. Yet rigorous testing is essential; quantum bugs are subtle and expensive to debug on real hardware.

The Testing Challenge

A classical test: assert output == expected_value. A quantum test: assert P(outcome) is within ε of expected_probability.

Key difficulties:

  1. 1.Inherent randomness: Even correct programs produce variable outputs
  2. 2.Exponential state space: Cannot directly inspect the state vector in real hardware
  3. 3.Measurement destroys state: Can't observe without disturbing
  4. 4.Long feedback loops: Minutes to hours for quantum cloud job + result

Unit Testing Strategies

1. State Vector Comparison (Simulator Only)

On a simulator, compare state vectors directly:

def test_bell_state():
    sim = StateVectorSimulator(2)
    sim.h(0)
    sim.cx(0, 1)
    sv = sim.statevector()
    expected = [1/sqrt(2), 0, 0, 1/sqrt(2)]
    for a, e in zip(sv, expected):
        assert abs(a - e) < 1e-10, f"Amplitude mismatch: {a} != {e}"

This is exact but only works on simulators. On real hardware, use probability-based tests.

2. Probability-Based Testing (Applicable to Hardware)

Use enough shots to make statistical tests reliable:

def test_h_gate_statistics(shots=10000, alpha=0.01):
    """Test H gate produces 50/50 distribution using chi-squared test."""
    counts = run_circuit_h_gate(shots=shots)
    p0 = counts.get('0', 0) / shots
    p1 = counts.get('1', 0) / shots
    # Chi-squared test: chi2 = N * (p0 - 0.5)^2 / 0.5 + N * (p1 - 0.5)^2 / 0.5
    # For large N, chi2 ~ chi2(1). Accept if chi2 < 6.63 (p=0.01)
    chi2 = shots * (p0 - 0.5)**2 / 0.5 + shots * (p1 - 0.5)**2 / 0.5
    assert chi2 < 6.63, f"H gate statistics failed: chi2={chi2:.2f}"

Statistical note: With 10,000 shots, the standard deviation of p̂ is √(0.25/10000) = 0.005. Tests with tolerance < 0.01 are too tight and will fail spuriously.

3. Symmetry Tests

Correct circuits often have symmetry properties that are easier to test than exact values:

# Bell state symmetry: P(00) = P(11), P(01) = P(10) = 0
def test_bell_symmetry(shots=10000):
    counts = run_bell_circuit(shots=shots)
    assert counts.get('01', 0) < 50  # should be ~0
    assert counts.get('10', 0) < 50  # should be ~0
    assert abs(counts.get('00', 0) - counts.get('11', 0)) < 200  # should be ~equal

4. Inverse Circuit Tests

Any unitary circuit U can be tested by verifying U†U returns the initial state:

def test_circuit_is_unitary(circuit_fn, n_qubits, shots=10000):
    """Apply circuit, then its inverse, check we return to |0>."""
    forward = circuit_fn(n_qubits)
    inverse = forward.inverse()  # if your framework supports it
    combined = forward.compose(inverse)
    counts = run(combined, shots=shots)
    # Should get |0...0> with high probability
    all_zeros = '0' * n_qubits
    assert counts.get(all_zeros, 0) / shots > 0.99

5. Property-Based Testing

Use property-based testing for parameterized circuits:

from hypothesis import given, strategies as st

@given(theta=st.floats(0, 2*pi))
def test_ry_norm(theta):
    """RY rotation preserves normalization."""
    sim = StateVectorSimulator(1)
    sim.ry(theta, 0)
    sv = sim.statevector()
    norm = sum(abs(a)**2 for a in sv)
    assert abs(norm - 1.0) < 1e-10

@given(theta=st.floats(0, 2*pi))
def test_ry_periodicity(theta):
    """RY(theta + 4pi) == RY(theta)."""
    sim1 = StateVectorSimulator(1)
    sim1.ry(theta, 0)
    sim2 = StateVectorSimulator(1)
    sim2.ry(theta + 4*pi, 0)
    assert max(abs(a-b) for a, b in zip(sim1.statevector(), sim2.statevector())) < 1e-9

Integration Testing

Test that full circuits behave correctly end-to-end:

def test_grover_3qubit():
    """Grover should find marked element with >90% probability in optimal iterations."""
    for target in range(8):
        sim = GroverSimulator(3)
        probs = sim.run(target)
        target_bs = format(target, '03b')
        assert probs[target_bs] > 0.90, f"P(target)={probs[target_bs]:.2f} < 0.90"

Testing Noise Resilience

Test that your circuit degrades gracefully under noise:

def test_noise_resilience():
    """Circuit should still classify correctly at 1% noise."""
    noisy_result = run_classifier(noise_p=0.01)
    ideal_result = run_classifier(noise_p=0.0)
    # Allow ≤10% accuracy degradation
    assert ideal_result.accuracy - noisy_result.accuracy < 0.10

CI/CD for Quantum Code

Recommended CI pipeline (GitHub Actions):

steps:
  - name: Unit tests (fast simulator)
    run: pytest tests/unit/ --shots=1000  # quick, less accurate
  - name: Integration tests (noiseless simulator)
    run: pytest tests/integration/ --shots=10000  # thorough
  - name: Noise tests (noisy simulator)
    run: pytest tests/noise/ --noise-level=0.01  # resilience check
  # Hardware tests run only on schedule, not on every PR
  - name: Hardware smoke test
    run: pytest tests/hardware/ -k smoke
    if: github.event_name == 'schedule'

Key practices:

  • Keep simulator tests fast (<60 seconds total for CI)
  • Use fixed random seeds for reproducibility: random.seed(42)
  • Write tests before running on hardware, catch bugs cheaply
  • Log all hardware results with circuit + parameters for debugging
Unit Tests Cannot Replace Hardware Testing

A unit test on a statevector simulator is deterministic and exact. The same circuit on real hardware returns probabilistic results affected by gate errors, crosstalk, T1/T2 decoherence, and measurement errors. A test that passes on a simulator may fail on hardware not because of a bug but because the circuit depth exceeds the device's coherence window. Always profile circuit depth vs hardware T2 times before submitting long circuits.

How this lesson works

A guided reading lesson with interactive knowledge checks. Concepts are explained step by step with circuit diagrams and runnable examples, and you confirm understanding before moving on.

Part of: Real-World Quantum Python

Write production-quality quantum Python, circuit optimization, hybrid algorithms, cloud backends, noise modeling, and software engineering patterns.

This lesson is part of Pro

Unlock Real-World Quantum Python and all 10 tracks with Pro: $12.99/month, $79/year, or $97 lifetime. Start with the free track first if you are new.