Test-Driven Development
In these exercises you will practise Test-Driven Development (TDD): writing a failing test first, writing the smallest amount of code needed to make it pass, then refactoring before moving on. This is often called the red-green-refactor cycle:
- Red — write a single test for a small piece of behaviour you don't have yet. Run it and watch it fail.
- Green — write the minimum code needed to make that test pass. Don't add anything the test doesn't demand yet.
- Refactor — with a passing test suite as a safety net, clean up the code (and the tests) without changing behaviour. Re-run the tests after every change.
Repeat this cycle in small steps until the feature is complete.
You can use any programming language and test framework you're comfortable with (JUnit, xUnit, pytest, Jest, Go's testing package, etc.) — the discipline of the cycle matters far more than the language.
Using AI coding assistants (GitHub Copilot, Claude Code, etc.)
These exercises are about building the habit of TDD, not about producing working code as fast as possible. AI coding assistants are very good at jumping straight to a full implementation, which skips the entire point of the exercise.
If you're using an AI assistant, either:
- Switch it off for these exercises, or
- Keep it on a short leash: prompt it one step at a time, e.g.
- "Only write the test for this next piece of behaviour, do not write the implementation yet."
- "Now write the minimum code needed to make this one test pass — nothing more."
Never let the assistant "race ahead" and generate the whole solution in one go, and don't let it add error handling, extra methods, or edge-case handling that no test has demanded yet. If it does, delete the code back down to what your current tests require and pick it up from there.
Thinking about test cases
Before (and while) you write each test, deliberately think through:
- The happy path — the normal, expected inputs and outputs.
- The unhappy path — what should happen when the input is invalid or malformed?
- Edge cases — boundaries, empty input, minimum/maximum values, off-by-one scenarios.
- Error conditions — what should the function do when it genuinely cannot produce an answer? Throw? Return a sentinel value? Decide deliberately rather than by accident.
A good technique (from Kent Beck's original TDD book) is to jot down a test list before you start: a running list of test case ideas in plain English, in roughly the order you plan to tackle them. Cross them off (and add new ones) as you go. Each lab below suggests a starting test list — use it as a prompt, not a checklist to copy verbatim.
Test code structure
Pick a structure for your testing methods, such as
- Given this scenario (set up the objects and configuration)
- When this happens (a funciton is called with certain parameters)
- Then we expect this outcome
or
- Arrange - set up the preconditions
- Act - carry out some action
- Assert - verify the outcome
The labs
Pick any of these to try - if you have time, try more than 1!