Output-based testing feeds an input to the system under test and verifies the value it returns. It applies when the code has no hidden inputs, hidden outputs, or side effects. section 6.1 section 6.6
The strongest tests target observable behavior through the method under test alone. They usually resist refactoring better, remain concise, and cost less to maintain than state-based or communication-based tests. section 6.2 section 6.6
Choose the testing style according to what the operation visibly produces:
- Use output-based testing for pure calculations whose inputs and outputs appear explicitly in the method signature. section 6.3
- Use state-based testing when the operation’s outcome is a changed object, collaborator, database, or filesystem state. section 6.1
- Use communication-based testing sparingly, verifying only external interactions whose side effects are visible outside the application. section 6.2
Testing a discount calculation
PriceEngine.CalculateDiscount receives products and returns a discount capped at 20 percent. A test supplies two products and asserts 0.02 as the returned value, without inspecting internal state or interactions.
Refactoring filesystem work into outputs
An audit manager initially reads and writes files directly, making tests slow and difficult to maintain. Refactoring separates a functional AuditManager, which returns a FileUpdate, from a mutable Persister that applies it. The test then compares the returned update directly, without mocks. section 6.4
