test-driven-development
Differences
This shows you the differences between two versions of the page.
| Both sides previous revisionPrevious revisionNext revision | Previous revision | ||
| test-driven-development [February 18, 2026 at 17:21] – yanevskiv | test-driven-development [June 13, 2026 at 03:13] (current) – external edit 127.0.0.1 | ||
|---|---|---|---|
| Line 1: | Line 1: | ||
| + | # Test-driven development | ||
| + | **Test-driven development** (**TDD**) is a practice where you write the test before you write the code. The test fails first — because the code does not exist yet — then you write the minimum code to make it pass, then you clean up. The cycle repeats for every new piece of behaviour. | ||
| + | |||
| + | The three steps are often called red-green-refactor: | ||
| + | |||
| + | 1. Write a failing test (red — the test suite is broken). | ||
| + | 2. Write the smallest code that makes the test pass (green). | ||
| + | 3. Refactor: improve names, remove duplication, | ||
| + | |||
| + | Then add the next test and repeat. | ||
| + | |||
| + | ## Why write the test first | ||
| + | |||
| + | Writing a test before the code forces you to think about the interface before the implementation. You have to decide what the function is called, what it takes as arguments, and what it returns — before you write a single line of the function body. This tends to produce cleaner APIs than starting with implementation details and retrofitting a public interface later. | ||
| + | |||
| + | It also means every line of production code exists to satisfy a test. There is no dead code written "for future use" because no test demanded it yet. This is the practical meaning of the "add nothing else" in step 2. | ||
| + | |||
| + | ## The problem with test-later | ||
| + | |||
| + | Writing tests after the code is tempting because the code is already there and working — at least locally. The problem is that code written without tests in mind is often hard to test. Functions have too many responsibilities, | ||
| + | |||
| + | TDD avoids this by treating testability as a first-class design constraint from the start. | ||
| + | |||
| + | ## Mocking and over-mocking | ||
| + | |||
| + | Tests frequently need to isolate the unit under test from its dependencies: | ||
| + | |||
| + | Over-mocking is when the test ends up asserting on the internal implementation rather than the observable behaviour. A test that breaks every time you rename a private method is testing the wrong thing. Tests should verify what the code does, not how it does it. The rule of thumb: if a refactor that leaves the behaviour identical breaks the test, the test is over-specified. | ||
| + | |||
| + | ## When to use TDD | ||
| + | |||
| + | TDD works best for business logic with clear input-output contracts: parsers, calculators, | ||
| + | |||
| + | ## When TDD is awkward | ||
| + | |||
| + | TDD is harder to apply to code whose primary effect is I/O or rendering: a function that draws a frame to a framebuffer, | ||
