Skip to content

Why software tests need concrete failure cases

Tests are meant to prove that something works. In fact, a test only proves that it passes — and that is not the same thing. This text explains the difference and what follows from it.

  • Who it is forClients, project managers
  • LevelUnderstanding
  • Reading timeabout 7 minutes
  • Last updated16 October 2026

What you will learn

  • What a test proves and what it does not
  • Why the counter-check is part of the test
  • Which cases are worth testing
  • Why a coverage figure says little

A green test only proves itself

A test is a small program that calls another program and compares the result with an expectation. If it passes, that means: in this one case, what came out was what the test expected.

An uncomfortable possibility follows from this: a test can be green because it checks nothing. It calls something, expects nothing concrete, and stays green even when the code under test no longer exists.

PHPtested with PHP 8.3
// Proves nothing: only checks that nothing was thrown
public function testBerechnung(): void
{
    $ergebnis = $this->rechner->brutto(1000);
    self::assertNotNull($ergebnis);
}

// Proves something: states input and expected result
public function testBruttoMitRegelsatz(): void
{
    self::assertSame(1190, $this->rechner->brutto(1000));
}

// Proves the failure case: this is where the real value lies
public function testNegativerBetragWirdAbgewiesen(): void
{
    $this->expectException(InvalidArgumentException::class);
    $this->rechner->brutto(-1);
}
The first test stays green even if brutto() calculates incorrectly. The second does not. The third checks the case that actually occurs in operation.

The counter-check is part of it

A new test for a bug that has been found only counts as one if it fails WITHOUT the fix. That sounds obvious, but it is skipped regularly — and then you have a test that would not have noticed the bug.

We therefore do this explicitly: revert the fix, run the test, see red, reapply the fix, see green. Only then does it count. The same applies to tests with placeholders: if the placeholder dictates the result that is supposed to be checked, the test checks the placeholder.

Which cases are worth it

  • Business rules with boundaries: the last valid and the first invalid value, not an arbitrary one from the middle.
  • Everything to do with money, permissions or deadlines.
  • Every bug that has occurred once — so that it does not come back.
  • Steps that change several records at once: either everything applies or nothing does.

What is rarely worth it: tests for code without decisions, such as pure passing through of values. There the test costs maintenance and finds nothing.

An example from practice

What a coverage figure does not tell you

"85 percent test coverage" means: 85 percent of the lines of code were executed once during the tests. It does not mean that their behaviour was verified. A series of tests without real expectations produces high coverage and finds nothing.

The better question for a project is therefore not "how high is the coverage" but: for the three rules that would hurt most in operation, is there a test each with input and expected result — and do these tests fail if you deliberately break the rule?

Common misconceptions

It works in the cases that were tested. Whether the page is reachable in the browser and the parts work together is something a small-scale test does not tell you.

They cost time when building and save it with every later change — because afterwards you know whether you have broken something, instead of hoping.

You know better than any developer which cases would hurt. Naming those cases is the most valuable contribution you can make.

About the author

Fabian Lücker is the managing partner of PHP-Schmiede KG in Rheine and has worked in software development since 2007. He is responsible for development and concept design, and here he writes about decisions that arise in real projects.