White Box Testing¶
White box testing, also called structural or glass box testing, derives tests from the internal structure of the code: its statements, branches, conditions, paths and data flow. The question it answers is not "does it meet the specification" but "which parts of this code have never been executed, and why".
graph TD
CODE[Source code] --> CFG[Control flow graph]
CFG --> CRIT[Choose a coverage criterion]
CRIT --> CASES[Design inputs that reach the uncovered elements]
CASES --> RUN[Run and measure]
RUN --> GAP{Anything still uncovered?}
GAP -->|yes| ASK[Is it dead code, defensive code, or a missing requirement?]
GAP -->|no| DONE[Structural criterion satisfied]
The last box is where the value is. Uncovered code is one of three things: unreachable and deletable, defensive and deliberate, or a real behaviour nobody specified or tested.
Coverage criteria, weakest to strongest¶
| Criterion | Requirement | Minimum cases here |
|---|---|---|
| Statement | Every executable line runs | 2 |
| Branch (decision) | Every decision outcome taken at least once | 2 |
| Condition | Each atomic condition evaluates both true and false | 2 |
| Decision/condition | Both of the above together | 2 |
| MC/DC | Each condition shown to independently change the outcome | 3 |
| Path | Every executable path through the routine | Explodes with loops |
Two cases, discount(20, False) and discount(1, False), give 100% statement and branch
coverage while never once setting member to True. That is the standard demonstration
that a high percentage is not the same as a strong test.
MC/DC is required for the highest criticality levels in avionics software precisely because it is the weakest criterion that still forces each condition to matter.
Control flow and paths¶
graph TD
S[Start] --> C1{qty > 10?}
C1 -->|true| R1[return 0.9]
C1 -->|false| C2{member?}
C2 -->|true| R1
C2 -->|false| R2[return 1.0]
R1 --> E[End]
R2 --> E
Path coverage is complete but usually unreachable: a loop that may run zero to n times multiplies the path count by n, and a routine with a few nested branches inside a loop has more paths than there is time to test. This is the same exhaustiveness wall described in the testing principles, one level down.
Data flow testing¶
A second structural family, tracking each variable from definition to use.
| Anomaly | Meaning | Usually indicates |
|---|---|---|
| Define then define | Assigned twice with no use between | Dead assignment or a lost update |
| Use before define | Read before assignment | Undefined behaviour, null defect |
| Define then never use | Computed and discarded | Dead code or a missing use |
Most of this is now caught by compilers and static analysers, which is the cheapest form of white box checking available and requires no test cases at all.
Where it belongs¶
- Unit level, mostly. Structure is visible and cheap to steer there.
- After black box design, not instead of it. Write cases from the specification, then measure coverage, then use white box thinking on what is left.
- On critical modules. Payment, authorisation, pricing, safety logic justify branch or MC/DC targets. A settings page does not.
- Paired with mutation testing where it matters, since coverage proves execution and mutation score proves the assertions can actually fail.
The characteristic failure of white box testing used alone: the suite confirms that the code does what the code does. Missing requirements are structurally invisible, because code that was never written has no branches to cover.
Check Your Understanding¶
A module reports 100% branch coverage. What follows?
Why is MC/DC required in safety-critical domains rather than plain branch coverage?