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 returns true. Otherwise nothing changes, and it returns false.

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.

Key terms

Cheat sheets