Chapter 08
Testing your code
Unit tests on the logic that matters, test-driven development, test doubles and the edge cases that break most beginner programs.
Unit tests
Also called assertions, pytest, Vitest, JUnit, GoogleTest, xUnit, arrange act assert
- What
- Small automated tests that call one function or class with known input and assert on the result. A test runner finds and runs them all in seconds.
- Why
- Tests catch regressions the moment you break something and let you refactor with confidence. Structure each as arrange, act, assert.
#include <gtest/gtest.h>
#include <stdexcept>
#include "pricing.h"
TEST(WithTax, AddsTwentyPercent) {
EXPECT_DOUBLE_EQ(with_tax(10), 12.0);
}
TEST(WithTax, RejectsNegativeAmounts) {
EXPECT_THROW(with_tax(-1), std::invalid_argument);
}Watch out
Testing implementation details (private methods, exact calls) instead of behaviour, so every refactor breaks tests that should still pass.
Test-driven development
Also called TDD, red green refactor, red, green, refactor
- What
- Writing a failing test first (red), then the least code that makes it pass (green), then cleaning up (refactor), in short cycles.
- Why
- It forces you to decide what the code should do before how, keeps designs testable and builds a safety net as you go.
Watch out
Writing many tests at once before any code, which loses the short feedback loop that makes TDD useful.
Test doubles
Also called mocks, stubs, fakes, dependency injection
- What
- Stand-ins for real dependencies in tests: a stub returns fixed answers, a fake is a simple working version (an in-memory store), and a mock checks how it was called.
- Why
- They keep unit tests fast and repeatable when the real code talks to a network, a clock, a database or email.
- Example
- Pass a
FakeClockfixed at 1 March intois_overdue(invoice, clock)instead of reading the system time, so the test gives the same result every day.
Watch out
Mocking so much that the test only checks your mocks and passes while the real code is broken.
Edge cases
Also called boundary values, corner cases
- What
- Inputs at or beyond the limits of normal use: empty collections, zero, negative numbers, one item, very large values, missing fields and unexpected text.
- Why
- Most bugs live at the edges. Listing them before coding shapes both the tests and the implementation.
- Example
- For an average function: an empty list, one value, all equal values, negative numbers and a list too big to sum in a 32-bit int.
Watch out
Testing only the happy path with the example from the task description.
The test pyramid
Also called integration tests, end-to-end tests, E2E
- What
- A guideline for balancing test types: many fast unit tests, fewer integration tests that check parts working together, and a few slow end-to-end tests through the whole system.
- Why
- It keeps the suite fast enough to run on every change while still catching problems where parts connect.
Watch out
Relying mainly on slow, flaky end-to-end tests, so people stop running them.