Planning Unit Tests
Plan tests from the requirements, as a table of scenarios. Each scenario changes one thing, and the edges (at a limit, just inside it, just past it) find the most bugs.
Apprentice · Unit Testing · Updated
After this lesson you can
- Plan test scenarios from requirements, with the starting state, the input and the expected result
- Choose edge cases that sit at, just inside and just outside a limit
Key takeaways
- Plan tests from the requirements, not from the code: the requirements say what the class should do.
- Write each scenario down with its starting state, its input, what the method returns and what the properties should be afterward.
- Each scenario changes one thing from the others, so a failing test points to one broken requirement.
- Bugs live at edges. For every limit, test at it, just inside it, just past it, and one ordinary value: for channels 3 to 15, test 15, 14, 16 and 7.
Tests are planned from the requirements, not from the code. The code says what the class does; the requirements say what it should do, and a test's job is to check the two agree.
Here are the requirements for a Television class, the example this chapter tests:
- A TV starts off, on channel 3.
- Its channels run from 3 to 15.
ChannelUp()goes up one channel and returns the new channel. From 15 it goes back to 3. While the TV is off, nothing changes.ChangeChannel(int channel)changes to that channel, if the TV is on and the channel exists, and returnstrue. Otherwise nothing changes, and it returnsfalse.
Scenarios
A scenario is one situation to test. Write each one down with its parts:
| Part | Means |
|---|---|
| Name | What the scenario checks, in a sentence |
| Starting state | How the object is before the test: on or off, which channel |
| Input | The values passed to the method, if any |
| Returns | What the method should return, if it returns anything |
| Afterward | What the object's properties should be afterward |
A simple table is enough. Here are the scenarios for ChannelUp():
| Scenario | Starting state | Input | Returns | CurrentChannel afterward |
|---|---|---|---|---|
| Goes up one channel | On, channel 3 | 4 | 4 | |
| From the highest channel, goes back to the lowest | On, channel 15 | 3 | 3 | |
| While off, doesn't change | Off, channel 3 | 3 | 3 |
And for ChangeChannel(int):
| Scenario | Starting state | Input | Returns | CurrentChannel afterward |
|---|---|---|---|---|
| Changes to a channel that exists | On, channel 3 | 7 | true |
7 |
| Changes to the highest channel | On, channel 3 | 15 | true |
15 |
| Doesn't change past the highest channel | On, channel 3 | 16 | false |
3 |
| Changes to the lowest channel | On, channel 7 | 3 | true |
3 |
| Doesn't change below the lowest channel | On, channel 7 | 2 | false |
7 |
| While off, doesn't change | Off, channel 3 | 7 | false |
3 |
One change per scenario
Many of these scenarios look alike, and that's the point. Each one changes one thing from the others: the input, or whether the TV is on. When a test fails, you know exactly which requirement is broken.
It's tempting to fold several checks into one big scenario. Resist it: when a combined test fails, you have to work out which part failed before you can even start fixing.
Edges find bugs
Look at the inputs for ChangeChannel: 15 and 16, then 3 and 2. Those are the edges: the values at a limit, and just either side of it. Bugs live at edges, because that's where < and <= behave differently, and where a loop runs once too often or once too few.
For any limit, test:
- at the limit (15, the highest channel),
- just inside it (14),
- just past it (16),
- and one ordinary value well inside (7).
The same goes for every limit you meet: "at most 5 books" means test 4, 5 and 6; "free for the first 15 minutes" means test 14, 15 and 16.
Check yourself
2 questions to try on the lesson page.
So what
A table of scenarios turns vague requirements into a checklist, and each row becomes one test. Plan the edges first: they're where your bugs, and everyone else's, are hiding.