Black Box Testing¶
Black box testing derives tests from what the system is supposed to do, without looking at how it does it. The tester sees inputs, outputs and observable behaviour, and treats the internals as opaque.
graph LR
IN[Inputs] --> BOX["System: internals not consulted"]
BOX --> OUT[Outputs and observable effects]
SPEC[Specification, requirements, contract] --> TESTS[Test cases]
TESTS --> IN
OUT --> CMP{Matches the specification?}
The source of truth is the specification, never the code. That independence is the whole point: tests written from the implementation inherit the implementation's misunderstandings and confirm bugs instead of exposing them.
Techniques in the family¶
| Technique | Use when | Finds |
|---|---|---|
| Equivalence partitioning | Inputs fall into classes treated alike | Whole classes handled wrongly |
| Boundary value analysis | Partitions have edges | Off-by-one and comparison operator defects |
| Decision table testing | Several conditions combine into rules | Missing or contradictory business rules |
| State transition testing | Behaviour depends on history | Illegal transitions, stuck states |
| Pairwise testing | Many independent configuration options | Defects caused by option interactions |
| Use case testing | Journeys matter more than fields | Broken flows across correct components |
| Error guessing | Experience says where it breaks | Nulls, empty lists, huge inputs, odd encodings |
Combining two or three of these covers far more than exhausting any one of them.
Strengths and blind spots¶
| Strengths | Blind spots |
|---|---|
| Tests survive refactoring, since they depend only on behaviour | Cannot see untested code that no specified input reaches |
| Can be written before the code exists | Cannot detect dead code or hidden branches |
| Needs no programming knowledge of the internals | Redundant cases are invisible: many inputs may hit one path |
| Applies at every test level | Depends entirely on specification quality |
The classic gap: a function has a special case for a magic value that no requirement mentions. No black box technique will ever generate that value, and only white box testing sees the branch.
That is why the two are complements rather than rivals, and why measuring coverage of a black box suite is worth doing: the uncovered lines are exactly the specification's blind spots.
A worked pass¶
Requirement: password must be 8 to 64 characters, contain a digit, and must not equal the username.
graph TD
R[Requirement] --> P1[Length partitions: too short, valid, too long]
R --> P2[Digit present or absent]
R --> P3[Equals username or not]
P1 --> B[Boundaries: 7, 8, 64, 65]
P2 --> D[Decision table across the three conditions]
P3 --> D
| Case | Length | Digit | Equals username | Expected |
|---|---|---|---|---|
| 1 | 7 | yes | no | Reject, too short |
| 2 | 8 | yes | no | Accept |
| 3 | 64 | yes | no | Accept |
| 4 | 65 | yes | no | Reject, too long |
| 5 | 12 | no | no | Reject, needs a digit |
| 6 | 12 | yes | yes | Reject, equals username |
Six cases, derived only from the requirement, covering every rule and every edge. No knowledge of the implementation was needed to write them, which means they are still valid after the validator is rewritten.
Check Your Understanding¶
Why should black box test cases be derived from the specification rather than from reading the code?
A function contains a branch triggered by a value no requirement mentions. Which statement is true?