Design through Tests
Writing tests before writing production code is essential to creating a sympathetic design and architecture.
Implications
It's important to define the different types of tests that one might typically write or find in a codebase.
- Unit tests - tests that exercise a single piece of behaviour in isolation. These run rapidly (milliseconds) and there may be many of these (thousands).
- Integration tests - tests that exercise behaviour at the boundary of the system and generally deal with I/O (e.g. database, API call, filesystem etc.). These tests run slowly (up to a second per test) and as a result we want as few of these as possible (tens rather than hundreds).
- Functional tests - tests that exercise a number of components that together satisfy a feature of the system (e.g. an API call / webpage). These tests also run rapidly (up to 100s of milliseconds) by avoiding I/O with in-memory, programmed boundaries. These tests are typically the most beneficial as they demonstrate that the features of the software are working as expected and as a result we will add at least one for each story played (hundreds)
- Journey tests - these tests exercise the whole of the system from the point of view of a user of the system. For a web application these are typically browser based tests and without care and attention can result in brittle long-running tests which require constant upkeep. Care and thought required to keep the number of these journeys to less than ten.
Typically, whenever we create a new feature we will start with a functional test (3. above), once we have passing functional tests for each of the story's acceptance criteria we can be reasonably confident that we're done and don't need to write any more code.
Rationale
Test driven development is not about quality (although it does help) it is about creating and designing software that is fit for purpose and easy to change with confidence. Once the test is satisfied we know that we are done and don't need to write more code (unless we can think of another test). By writing code from the perspective of how we expect to use it we create something that is fit for purpose and gain rapid feedback on our design and how easy it is to use.