Lumos! Illuminating Failing Tests


Pytest Debugging — A Wizard's Guide

🪄 Lumos! Illuminating Failing Tests

Every wizard knows that darkness is the enemy. When your tests fail and the traceback is a jumbled mess of cryptic runes, you need a spell to light the way. Welcome to Debugging with Pytest — the Lumos of the testing world.

1. Understanding Pytest Output — Choosing Your Wand's Brightness

Just as Lumos can shine at different intensities, pytest lets you control how much detail you see in tracebacks using --tb:

# Short traceback — a dim glow, just the essentials
pytest --tb=short

# Long traceback — full Lumos Maxima, every frame exposed
pytest --tb=long

# One line per failure — a flickering candle
pytest --tb=line

# No traceback at all — Nox! Darkness returns
pytest --tb=no

Use --tb=short when you're scanning many failures quickly (like skimming the Daily Prophet). Switch to --tb=long when you need to perform a full magical autopsy on a single cursed test.

2. Stopping at First Failure — The Impedimenta Jinx

Sometimes you don't want to watch the entire test suite crumble like a poorly cast Protego. Stop the carnage immediately:

# Stop at the FIRST failure — Impedimenta!
pytest -x

# Stop after N failures — controlled jinx
pytest --maxfail=3

This is essential during development: fix one curse at a time, like Madam Pomfrey treating one patient before moving to the next.

3. Running Last Failed — The Marauder's Map of Failures

The Marauder's Map shows you exactly where everyone is. Similarly, pytest remembers which tests failed last time:

# Re-run ONLY the tests that failed last time
pytest --lf   # "Last Failed" — I solemnly swear I am up to no good

# Run failed tests FIRST, then the rest
pytest --ff   # "Failed First" — prioritize the troublemakers

This uses a cache file (.pytest_cache/) to track state between runs. Mischief managed.

4. Verbose Mode — Amplifying the Signal

Like using Sonorus to amplify your voice across the Quidditch pitch:

# Show each test name and result
pytest -v

# Even MORE detail — SONORUS MAXIMA
pytest -vv

With -vv, pytest shows full diffs for assertion failures, making it trivial to spot exactly which potion ingredient went wrong.

5. Dropping into the Debugger — Legilimens on Your Code

Sometimes you need to peer directly into your code's mind. --pdb drops you into Python's debugger at the exact point of failure:

# Cast Legilimens — enter the mind of the failing test
pytest --pdb

# Inside pdb, you can:
# (Pdb) p variable_name    — inspect a variable
# (Pdb) l                  — list surrounding code
# (Pdb) n                  — next line
# (Pdb) c                  — continue execution
# (Pdb) q                  — quit

You're now a Legilimens, reading the program's memories at the moment of death. No Occlumency can hide the bug from you.

6. Finding Slow Tests — The Time-Turner Audit

Some tests are like Gilderoy Lockhart — all show, no substance, and terribly slow. Find them:

# Show the 10 slowest tests
pytest --durations=10

# Show ALL test durations (including setup/teardown)
pytest --durations=0

This reveals which tests are hogging your Time-Turner. Optimize or split them before they slow your entire CI pipeline to a crawl.

7. Flaky Tests — Taming the Niffler

Flaky tests are like Nifflers — unpredictable, occasionally destructive, and maddeningly hard to catch. Use pytest-rerunfailures:

# Install the taming charm
pip install pytest-rerunfailures

# Re-run failed tests up to 3 times
pytest --reruns 3

# Add a delay between reruns (for timing-sensitive tests)
pytest --reruns 3 --reruns-delay 2

# Or mark individual tests as flaky
import pytest

@pytest.mark.flaky(reruns=5, reruns_delay=1)
def test_niffler_behavior():
    """This test sometimes fails due to network gremlins."""
    response = call_external_api()
    assert response.status_code == 200

Remember: flaky tests are a symptom, not a feature. Tame the Niffler, don't just put it on a leash.

8. Using Print and Logging — The Howler Approach

Sometimes the simplest debugging tool is a well-placed Howler (or print statement). But pytest captures stdout by default!

# Allow print output to appear (disable capture)
pytest -s

# Or use Python's logging module for more control
import logging

logger = logging.getLogger(__name__)

def test_with_logging():
    logger.info("Starting the incantation...")
    result = cast_spell("Expelliarmus")
    logger.debug(f"Spell result: {result}")
    assert result.success is True

# Run with log output visible:
pytest --log-cli-level=DEBUG

The -s flag is like removing a Silencing Charm from your tests. Suddenly, all those whispered print() statements ring out loud and clear, like a Howler in the Great Hall.

Summary — Your Debugging Spell Book

SituationSpell (Command)
Need less/more traceback--tb=short/long/line/no
Stop at first failure-x or --maxfail=N
Re-run only failures--lf (last failed)
Failures first, then rest--ff (failed first)
More output detail-v or -vv
Drop into debugger--pdb
Find slow tests--durations=N
Handle flaky tests--reruns N
See print output-s
See log output--log-cli-level=DEBUG

"Happiness can be found, even in the darkest of times, if one only remembers to turn on the light." — Albus Dumbledore (and also anyone who remembers pytest --pdb).