Test First, Ask No Questions Later
Test driven development (TDD) can help developers create higher quality code on first iteration, as it helps developers avoid common errors of logic and design. The tests serve as a form of documentation, making clear the proper usage of the code. A large test suite facilitates subsystem integration, as it provides immediate feedback when new changes break old code. Test results are explicit and discrete, giving a better overview of the development progress. In short, tested code is confident code.
The TDD cycle:
1.) Take small implementation steps; large changes introduce large bugs,
2.) Design your implementation with testing in mind; determine what defines successful completion of the requirement and how to test for that completion
3.) Write one test; resist the urge to code in large runs,
4.) Write enough code so that your tests compile; do not write an implementation--just stubs,
5.) Run your tests, your new test should fail; if this is a tertiary pass through the TDD cycle, your changes may cause other, previously passing tests to fail,
6.) Write just enough code to pass all the tests; do not continue until all the tests pass, in this way you know that your changes do not break existing code,
7.) Refactor as necessary; minimize duplicate code, eliminate inefficiencies,
8.) Continue from step 2; consider if you need to expand your testing, remember to test boundary cases of input (bad input, good input that is close to being bad, typical input, etc.),
9.) Return to step 1; continue implementing small pieces of the requirement.
Tips on writing good tests:
1.) How to move a mountain: blast it into pebbles. Small tests (as defined by the number of Assertions made by the test) have a very high "Testing Resolution". A failed assertion immediately terminates the test, even if the test contains more assertions. Large tests with multiple assertions may hide assertion failures that occur after the first failed assertion. By splitting multi-assertion tests into single-assertion tests, all Assertions that can fail will fail, giving a higher resolution picture of the defect at hand. The test itself may be long on code to prepare for the Assertion, but it should only have one or two assertions.
2.) Test early, test often, test everything. Developers may feel like TDD requires more time than coding without testing. Initially, this is true. The developer not only takes the time to write the code, they also take the time to write the tests. The total amount of time spent on the code is far less, as higher quality code requires fewer bug fixes.
3.) The only good GUI is a thin GUI. How will you programmatically duplicate user interaction with reliability, precision, accuracy, and flexibility? Design GUI's as only being a front end to well-tested, GUI-independent logic classes. Ensure the GUI elements are receiving the expected bindings and trust that the GUI elements of the Operating System work as advertised. This also aids in code reuse.
4.) In with the good data, out with the bad. How will you programmatically ensure that a map does not render 500 feet off kilter? In general, data validation must be completed via Regression Testing (comparison to known good results) or typically "by eye".
5.) For more information:
- http://msdn.microsoft.com/library/default.asp?url=/library/en-us/dncscol/html/csharp02172004.asp (includes an example project)
- http://www.nunit.org/getStarted.html (getting started with nUnit)