Waterfall is an old-school project management ideology, still favored by defense contractors, aerospace firms, and other risk-averse entities. Under waterfall you write the code first then write the tests. Because the testing phase could be months—or years—after starting to write the code, problems which could have been easily fixed early in the development cycle require much more work to correct.
The least initial deviation from the truth is multiplied later a thousandfold.
Under test-driven development you avoid this by writing the tests first, then the code.
Why write tests first?
Writing the tests first makes you to think about the code’s public interface which results in clean, modular code. Writing the test first also makes you define success upfront so you don’t end up writing more code and adding more features than you need to.
Refactoring code—rewriting and simplifying code without altering its external behavior—is much easier when you can run a test suite afterward and verify you broke nothing. Many projects collect sections of code that are opaque to reasoning. Without a test suite no one dare change anything.
How to write a unit test
Test-driven development revolves around the unit test which exercises a single method or function in a module. In contrast an integration test exercises multiple modules to see how well they interact. To write a unit test:
- Write the test for one method or function.
- Run the test. It should fail because you haven’t written the function or method the test is exercising.
- Write just enough code to pass the test.
When you’re done you’ll have a collection of modular components that need only be glued together.
- The test suite shouldn’t a special configuration to run, otherwise tests may pass on one machine and fail on another.
- Tests should be fully independent without requiring tests to be run in a specific order.
A good naming convention uses the test’s unit of work and initial condition:
Dependencies should be mocked or stubbed to make tests run faster.
- Stub: a fake object holding predefined data for returning calls.
- Mock: a smarter stub. Verifies that expected actions were performed.
Test-driven development leads to better code by forcing you to think about how to connect the module to the test, which results in a modular design and a clean API.