Skip to content
Free public beta until December 4, 2026: every course is open, no card needed. After that, the first chapter of each course stays free and full access is a one-time $39 ($19 for up to 100 founding members chosen for helpful feedback). Your progress is kept. How the beta works

Recipe

Fix a reported bug, test first

Use when: someone reports a wrong result ("an empty message counts as one word") in code that has, or can have, automated tests

Goal: a fix, plus a regression test that you've seen fail on the old code and pass on the new code

Taught in Test-first bug fixing · extends Debug a crash or a wrong answer

  1. 1

    Before touching the code, write one check per distinct cause in the report. Work out each expected value from what the code is supposed to do, by hand. Never compute it by calling the code under test.

    Example

    The report: an empty message counts as one word, and a double space adds a word. So the test checks count_words("") == 0 and count_words("hi there") == 2. Both 0 and 2 come from the meaning of "word," not from count_words.

    Why: an expected value computed by the code under test compares the code with itself and can never fail.

  2. 2

    Register the test with add_test, build, and run it. It must fail (red), and the failure must be the one in the report.

    Example

    pass: three words
    FAIL: empty text has no words
    FAIL: two spaces between words
    2 failed
    

    With CTest: ctest --test-dir build -R words --output-on-failure shows the test as failed and prints these lines.

    • it fails the way the report says — the test reproduces the bug; go on.
    • it passes — the test doesn't reach the bug, or the report is wrong. Fix the test before touching the code.
    • it fails some other way (won't compile, crashes somewhere unrelated) — fix the test itself first.

    Why: a test that has never failed has never shown it can fail. Seeing it red is the proof it tells buggy code from fixed code.

  3. 3

    Run the failing test program the way you'd debug any wrong answer. If it crashes or misbehaves, run it in the sanitizer build. If it just gives the wrong value, run it in the debugger with a breakpoint in the function under test.

    Example

    Stepping through count_words(""): the loop never runs, spaces stays 0, and the function returns spaces + 1, which is 1. Counting spaces was the wrong idea.

    Why: the test gives you a small, fixed input that reaches the bug, which is exactly what the debugging recipe's first step asks for.

  4. 4

    Change the code under test as little as possible. Run the new test until it passes (green), then run every test.

    Example

    Count a word each time a non-space character follows a space or the start of the text. The new checks pass, and so does the old "one two three" == 3. Then plain ctest --test-dir build runs the whole suite.

    Why: the new test proves this bug is gone; the whole suite proves the fix didn't break something that used to work.

  5. 5

    Leave the test in the suite as a regression test. Add any edge case the fix made you think of.

    Example

    Fixing count_words suggested a new edge, spaces at both ends: count_words(" padded ") == 1. It joined the suite with the other three.

    ⚡ Shortcut

    If you wrote a test after the code, do the red step in reverse: break the code on purpose (change >= to > at a boundary), confirm the test fails, then undo the change.

    Why: a test that stays in the suite stops this bug from coming back unnoticed.

Common mistakes

  • Fixing the code first and writing the test afterwards, so you never see it fail.
  • Copying the "expected" value from the program's own output.
  • Writing a check that's always true, like v.size() >= 0 on an unsigned size.
  • Running only the new test and not the whole suite.
  • Deleting the test once it passes.