Concepts / Testing and Verifying Programs

Testing and Verifying Programs

A program is correct when it correctly implements a complete and correct specification, so passing against a specification does not by itself guarantee correctness.

  • Programming

When Every Test Passes

Imagine a team writes a program, tests it against the requirements they were given, and every test passes. It seems natural to conclude that the program is correct and that the job is finished. This article examines why that conclusion can be wrong, and what it means for a program to be dependable in a more demanding sense.

The key idea is that testing measures a program against a standard, and the standard itself may be flawed. Once you see the difference between checking a program against its requirements and establishing that the requirements are right, many failures that look mysterious start to make sense.

Following the Path from Intention to Behavior

Work through the chain of steps that connects what people want to what the program actually does. The people a program is for have intentions. Someone must turn those intentions into a specification, which is the description of what the program is supposed to do. A programmer then implements the program, and the program's behavior is what runs on a real machine in a real situation. Each link in that chain can drift away from the one before it.

Users' realintentionsSpecificationImplementationActual programbehaviormust be captured byguidesproduces when run
How do the specification, the intended behavior, and the actual program relate, and where does the gap that causes failure sit between them?

Read the diagram from left to right and notice where the source places the most difficult step. Checking that the implementation matches the specification is a question about code. Checking that the specification captures what users really need is a different kind of question, one that depends on understanding people and their needs. That second link is the one that a passing test suite cannot verify.

Why the Specification Can Be Wrong

Definition

A program is correct when it correctly implements a complete and correct specification. Passing against a specification does not by itself guarantee correctness.

A common misconception is that a program is correct simply because it meets its specification. The specification is the description of what the program is supposed to do, and a programmer's work is judged against it. That makes the specification a standard of comparison, not a guarantee that the standard itself is right.

The source states the goal precisely with two requirements. First, the implementation must faithfully do what the specification says. Second, the specification must be complete, meaning it covers what is needed, and correct, meaning it expresses the intentions of the people the program is for. Both must hold. A program can satisfy the first requirement perfectly and still fail the second.

This is why the phrase correct program carries more weight than a passing test suite can establish. A test suite can show that the implementation matches the specification on the cases it covers. It cannot show that the specification was the right one to begin with.

The Mars Polar Lander Case

The source uses two space missions to Mars to show how serious failure can be. Both were prominent in the news, and both failures were probably due to software problems. The source stresses that in neither case was the problem simply an incorrect program. The real question is therefore not only whether the code was written carefully, but whether the program's assumptions matched the world it operated in.

Worked example

How the lander's software went wrong

The Mars Polar Lander's software was supposed to detect the bump when the spacecraft landed and shut off the landing engines at that moment. Trace how an unexpected event early in the descent could cause the engines to shut off too soon.

  1. 1
    The intended signal

    The program was designed to treat a bump at landing as the sign that the spacecraft had reached the ground, and to shut off the engines then.

  2. 2
    The unplanned event

    The source explains that deploying the landing gear might have jarred the spacecraft enough to activate the program while it was still high above the ground.

  3. 3
    The false signal is accepted

    The software treated that jolt as a landing signal, because nothing in the program checked whether the spacecraft was actually near the surface.

  4. 4
    The harmful action

    The engines were turned off while the spacecraft was high above the ground, so the unpowered spacecraft fell to the surface.

Result

The program did what it was written to do in response to the signal it received. The failure came from an assumption that the signal would only occur at landing, which the real world did not guarantee.

What do you think happens?

If the landing-gear jolt is treated as a landing signal and the program has no altitude check, what happens to the engines?

Reveal answer
Answer:

The engines are shut off while the spacecraft is still high above the ground, and the unpowered spacecraft falls to the surface.

The program has no condition to confirm that the spacecraft is near the ground, so the false signal triggers the shutdown directly.

Consequences Grow with Importance

Most computer users have met programs that do not work or that crash. In many cases the result is only an annoyance. Even on a personal computer, though, failure can lead to lost work or lost money. The source makes a scaling point: when computers are given more important tasks, the consequences of failure can be proportionately more serious.

Kind of taskTypical consequence of failure, according to the source
Everyday personal computer useOften only an annoyance, though lost work or lost money is possible
Critical applications such as spacecraft landingsFailure can be a great deal more costly, as the Mars cases illustrate

The same kind of failure carries different stakes depending on the task.

The lesson is that a program's required level of dependability should match the importance of its task. A small failure in a note-taking app and a small failure in a landing system are both failures, but only one of them may end a mission.

Robustness and Checking Conditions Before Acting

Definition

Robustness is the ability to handle unexpected or erroneous inputs and conditions gracefully.

A program that is correct on the cases its designers imagined may still behave badly when something unusual happens, such as a sensor event that the designers did not plan for. A robust system is built to anticipate that such things can happen and to respond in a safe way. Robustness does not prevent every failure, but the source notes that a more robust system can mitigate the consequences of program failure, reducing the harm when one occurs.

The source's own illustration is the lander case. A more robust system would have checked the altitude before turning off the engines. The landing-gear jolt was an unexpected event that the software treated as a landing signal. A check on a condition that should be true at the moment of landing would have caught the false signal before any harmful action occurred.

Action requestedPrecondition holds?Perform actionContinue normallyRefuse and stay safeyesno
What path does the program take when a precondition fails versus when it holds, and where is the check relative to the action that could fail?

In the flowchart, the decision sits between the request and the action. If the precondition holds, the action proceeds. If it fails, the program takes the safe path instead. The check's position matters: placed after the harmful action, it cannot prevent that action.

Invalid inputarrivesInvalid inputarrivesProgram acts on itCondition checkedfirstHarmful outcomeSafe response
How does a fragile program and a robust program differ in what happens to the same invalid input, step by step?

Guarding an Action in Code

The source does not include code, so the following is an original illustration of the guarded pattern described above. It applies the same idea as the altitude check: confirm that a condition holds before taking an action that could cause harm if the condition is false.

Code walkthrough
def release_engines(altitude_m, landing_signal):
    if landing_signal and altitude_m < 5:
        return "engines off"
    return "engines remain on"

print(release_engines(1200, True))
print(release_engines(3, True))
  1. 1
    The function receives a reported altitude and a landing signal.
  2. 2
    The if statement tests both the signal and the altitude before any action is taken.
  3. 3
    When the signal arrives at 1200 meters, the condition fails, so the engines stay on.
  4. 4
    When the signal arrives at 3 meters, the condition holds, so the engines are turned off.
  • The check sits before the action that could fail
  • A landing signal alone is not enough to shut off the engines
  • The altitude condition confirms the spacecraft is near the ground
Output
engines remain on
engines off

Mistakes Beginners Make About Correctness

  • Common mistake

    Treating a passing test suite as proof that the program is correct

    Why it fails

    Tests compare the program against the specification. If the specification is incomplete or does not express what users need, passing tests say nothing about that gap.

    Do this instead

    Ask separately whether the implementation matches the specification and whether the specification matches the real intentions of the people the program is for.

  • Common mistake

    Assuming that a rare or unexpected event cannot happen

    Why it fails

    The lander's software treated a jolt from the landing gear as a landing signal, an event its designers had not planned for.

    Do this instead

    Identify the conditions your program relies on and check that they hold before acting on them, especially when the action is hard to undo.

  • Common mistake

    Placing a safety check after the action it was meant to protect

    Why it fails

    A check that runs after the harmful action has already occurred cannot prevent that action.

    Do this instead

    Place the condition check before the action, so the safe path is taken when the precondition fails.

  • Common mistake

    Believing that robustness guarantees no failure will ever occur

Best practice

Match the level of checking to the importance of the task. The source's point about consequences growing with importance suggests that a program controlling something costly or dangerous deserves more careful condition checking than a program whose failures are merely inconvenient.

Trace the Lander Scenario Yourself

MEDIUM

Consider a spacecraft program that shuts off its engines when it detects a bump, with no other condition checked. A sensor event unrelated to landing produces a bump while the spacecraft is still high above the ground. Describe, step by step, what the program does, and then describe how adding a check on altitude before the shutdown would change the outcome.

Hints
  • Identify the signal the program treats as the landing event and whether anything confirms it.
  • Ask whether the check you add runs before or after the engine shutdown.
  • Consider what the robust version does when the altitude condition is not met.

Key Points to Retain

  1. 1
    A program is correct only when it correctly implements a complete and correct specification, so meeting the specification is not by itself a guarantee of correctness.
  2. 2
    Whether a specification expresses the real intentions of users lies largely outside computer science and cannot be settled by careful coding alone.
  3. 3
    The consequences of failure grow as the importance of the task grows, from annoyance in everyday use to serious harm in critical applications.
  4. 4
    Robustness is the ability to handle unexpected or erroneous inputs and conditions gracefully, and checking a condition such as altitude before a critical action can reduce the harm of failure.

Key Takeaways

  • A program is correct when it correctly implements a complete and correct specification, so passing tests does not by itself guarantee correctness.
  • The specification must be both complete and correct, and whether it captures real user intentions lies largely outside computer science.
  • The consequences of failure grow with the importance of the task, as the Mars cases illustrate.
  • Robustness means handling unexpected or erroneous inputs gracefully, and checking conditions before acting reduces the harm when failure occurs.