Lesson 5 of 34 beginner 5 min read

Before this:What is testing?

The testing mindset

Key takeaways Good testing is a mindset before it’s a technique: assume your code is wrong until shown otherwise, and actively try to break it rather than demonstrate it. The enemy is confirmation bias — the builder’s instinct to run the inputs that will succeed. Counter it by hunting boundaries (empty, zero, one, maximum, malformed) and by asking “what would have to be true for this to fail?” The output of testing is evidence, not reassurance.

You now have the map of testing. Before writing any tests, though, there’s a mental switch to flip — because the same test tools, in the hands of someone trying to confirm their code versus someone trying to break it, produce completely different results.

Builder brain vs breaker brain

When you’ve just written code, you are its least qualified tester in one specific way: you want it to work, and you unconsciously know how to make it work. You’ll type the input you had in mind while writing. You’ll follow the sequence you designed for. This isn’t dishonesty — it’s confirmation bias, the universal human habit of seeking evidence that supports what we already believe.

The testing mindset is deliberately switching sides. Once the code is written, your job flips: you are now the adversary, paid to find the input that makes it fail. The question changes from “does it work?” — which invites a demonstration — to “how could this be wrong?” — which invites a hunt.

Rule of thumb: a test that can’t plausibly fail isn’t testing anything. If you already know it will pass, you chose it with builder brain.

Where to aim: the boundary checklist

Adversarial isn’t random. Decades of bug data say failures cluster at boundaries — the edges of what code accepts. When you’re hunting, walk this list against any function you meet:

Boundary Ask Classic failure
Empty What if the list/string/file has nothing in it? Index out of range on element 0
Zero & negative What if the count, size, or offset is 0 or −1? Division by zero; infinite loop
One What if there’s exactly one element? Off-by-one that “worked” at 10
Maximum What at the size limit — and one past it? Overflow, truncation
Malformed What if input violates the format entirely? Crash instead of an error
Duplicates & order Repeated values? Reversed order? Same timestamp? Sort/dedup logic breaks

Notice these are exactly lesson 1’s edge cases — the inputs nobody imagined. The mindset makes imagining them your explicit job. Radio software sharpens the habit: a decoder’s input is whatever the airwaves deliver, including transmissions that cut off mid-message and signals barely above the noise. Code that has only ever met clean input is code that hasn’t met its users.

From “seems to work” to evidence

Here’s the practical cash-out. Compare two claims about the same function:

  • “I ran it and it seems to work.” — Translation: on some inputs I don’t fully remember, output looked plausible to me at the time.
  • “It handles the normal case, empty input, a single element, and the maximum size — here are the four tests.” — Translation: specific predictions, checked mechanically, re-checkable by anyone in milliseconds, forever.

The second claim is evidence: it’s precise about what was verified, and it survives you. Six months later, the “seems to work” claim has evaporated, while the four tests are still running on every change, still defining exactly what “works” means. This is why experienced reviewers read a change’s tests first — they’re the most honest statement of what the author actually verified.

Humility, institutionalized

One more reframe, because it makes the whole discipline feel different. Testing can look like distrust — of your code, even of yourself. It’s really the opposite of arrogance: it’s the working assumption that everyone’s attention fails, yours included, and that a system of mechanical checks is kinder and more reliable than a culture of blame. The best engineers you’ll meet aren’t the ones who never write bugs; they’re the ones whose bugs die young, in private, killed by a test the engineer wrote while wearing the breaker hat.

That’s also why this mindset scales beyond your own code. Reviewing a teammate’s change? Same boundary list. Reading a bug report? Ask what assumption broke. Evaluating a library? Feed it something malformed and watch what happens. The question “how could this be wrong?” is the most portable tool in this module — and in Unit 6 you’ll watch it catch a bug that a passing test suite was actively hiding.

Quick check: with the testing mindset, which input is most worth trying first on a function that splits audio into chunks?

Recap

  • Testing is a mindset: assume the code is wrong, and hunt for the proof.
  • Confirmation bias makes builders test the happy path; flip to the adversary once the code is written.
  • Aim at boundaries: empty, zero, one, maximum, malformed, duplicates — that’s where failures cluster.
  • The product of testing is evidence — precise, mechanical, re-runnable — not a feeling of reassurance.
  • It’s humility institutionalized: everyone’s attention fails; good engineers build nets and let their bugs die young.

Next up: What is a unit test?