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.
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.
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
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.
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.
- 1The 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.
- 2The 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.
- 3The 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.
- 4The harmful action
The engines were turned off while the spacecraft was high above the ground, so the unpowered spacecraft fell to the surface.
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
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 task | Typical consequence of failure, according to the source |
|---|---|
| Everyday personal computer use | Often only an annoyance, though lost work or lost money is possible |
| Critical applications such as spacecraft landings | Failure 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
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.
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.
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.
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))- 1The function receives a reported altitude and a landing signal.
- 2The if statement tests both the signal and the altitude before any action is taken.
- 3When the signal arrives at 1200 meters, the condition fails, so the engines stay on.
- 4When 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
engines remain on
engines offMistakes Beginners Make About Correctness
Common mistake Treating a passing test suite as proof that the program is correct
Why it failsTests 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 insteadAsk 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 failsThe lander's software treated a jolt from the landing gear as a landing signal, an event its designers had not planned for.
Do this insteadIdentify 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 failsA check that runs after the harmful action has already occurred cannot prevent that action.
Do this insteadPlace 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
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
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
- 1A 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.
- 2Whether a specification expresses the real intentions of users lies largely outside computer science and cannot be settled by careful coding alone.
- 3The consequences of failure grow as the importance of the task grows, from annoyance in everyday use to serious harm in critical applications.
- 4Robustness 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.