# 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 November 3, 2026

## 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

- [Test Case](https://www.ask-codey.com/glossary/test-case)
- [Edge Case](https://www.ask-codey.com/glossary/edge-case)

## Cheat sheets

- [Planning Unit Tests](https://www.ask-codey.com/cheat-sheets/unit-testing-with-mstest-planning-unit-tests-cheat-sheet)

Source: https://www.ask-codey.com/lessons/unit-testing-with-mstest-planning-unit-tests
