An Practical Example of TDD |
To explain what the Test Driven Development (TDD) is, and how a unit test framework can aid TDD practice, a small but illustrative example is borrowed from Kent Beck's great work Test-Driven Development By Example, Appendix B. This example shows how to get a Fibonacci Sequence with Test Driven Development.
Before start, let's declair a test suite, and also the function fib():
#include "unit--.h" testSuite(FibonacciSuite); unsigned fib(unsigned n); |
The first test case shows fib(0) = 0 . The implementation simply returns a constant.
testCase(FibCase, FibonacciSuite) { assertTrue(0 == fib(0)); } unsigned fib(unsigned n) { return 0; } |
A second case shows fib(1) = 1 :
testCase(FibCase, FibonacciSuite) { assertTrue(0 == fib(0)); } |
|\ |\ | \ | \ | / | / |/ |/ |
testCase(FibCase, FibonacciSuite) { assertTrue(0 == fib(0)); assertTrue(1 == fib(1)); } |
The second assertion was put into the same testCase, for a stand alone FibonacciOfOneIsOneCase is not that necessary.
There are several ways to make the test run. Here we choose to treat 0 as a special case:
unsigned fib(unsigned n) { return 0; } |
|\ |\ | \ | \ | / | / |/ |/ |
unsigned fib(unsigned n) { if (0 == n) return 0; return 1; } |
Duplication between the assertions starts to be bothering, and it would get worse when new cases are added. With a table containing matched input and output pairs, the common structure of assertions can be extracted.
testCase(FibCase, FibonacciSuite) { assertTrue(0 == fib(0)); assertTrue(1 == fib(1)); } |
|\ |\ | \ | \ | / | / |/ |/ |
testCase(FibCase, FibonacciSuite) { unsigned cases[][2] = {{0, 0}, {1, 1}}; const unsigned CASE_COUNT = sizeof(cases) / sizeof(cases[0]); for (unsigned i = 0; i < CASE_COUNT; ++i) { assertTrue(cases[i][1] == fib(cases[i][0])); } } |
Now it is easier to add a new case: only 6 key strokes and no extra line.
testCase(FibCase, FibonacciSuite) { unsigned cases[][2] = {{0, 0}, {1, 1}}; const unsigned CASE_COUNT = sizeof(cases) / sizeof(cases[0]); for (unsigned i = 0; i < CASE_COUNT; ++i) { assertTrue(cases[i][1] == fib(cases[i][0])); } } |
|\ |\ | \ | \ | / | / |/ |/ |
testCase(FibCase, FibonacciSuite) { unsigned cases[][2] = {{0, 0}, {1, 1}, {2, 1}}; const unsigned CASE_COUNT = sizeof(cases) / sizeof(cases[0]); for (unsigned i = 0; i < CASE_COUNT; ++i) { assertTrue(cases[i][1] == fib(cases[i][0])); } } |
With slight fear, the test is passed. Returning constant 1 still works. Now add a new case:
testCase(FibCase, FibonacciSuite) { unsigned cases[][2] = {{0, 0}, {1, 1}, {2, 1}}; const unsigned CASE_COUNT = sizeof(cases) / sizeof(cases[0]); for (unsigned i = 0; i < CASE_COUNT; ++i) { assertTrue(cases[i][1] == fib(cases[i][0])); } } |
|\ |\ | \ | \ | / | / |/ |/ |
testCase(FibCase, FibonacciSuite) { unsigned cases[][2] = {{0, 0}, {1, 1}, {2, 1}, {3, 2}}; const unsigned CASE_COUNT = sizeof(cases) / sizeof(cases[0]); for (unsigned i = 0; i < CASE_COUNT; ++i) { assertTrue(cases[i][1] == fib(cases[i][0])); } } |
Hurray, it failed! Unfortunately, the output only told us where the failed assertion is located, but it didn't tell us which input had caused the failure. We can deal it with unit_minus::equalValueInfo().
testCase(FibCase, FibonacciSuite) { unsigned cases[][2] = {{0, 0}, {1, 1}, {2, 1}, {3, 2}}; const unsigned CASE_COUNT = sizeof(cases) / sizeof(cases[0]); for (unsigned i = 0; i < CASE_COUNT; ++i) { assertTrue(cases[i][1] == fib(cases[i][0])); } } |
|\ |\ | \ | \ | / | / |/ |/ |
testCase(FibCase, FibonacciSuite) { using namespace unit_minus; unsigned cases[][2] = {{0, 0}, {1, 1}, {2, 1}, {3, 2}}; const unsigned CASE_COUNT = sizeof(cases) / sizeof(cases[0]); for (unsigned i = 0; i < CASE_COUNT; ++i) { assertTrue( equalValueInfo(cases[i][1], fib(cases[i][0])) ); } } |
As expected, it is the newly added case who caused the failure. Reusing the previous strategy (treating the smaller input as a special case), we wrote this:
unsigned fib(unsigned n) { if (0 == n) return 0; return 1; } |
|\ |\ | \ | \ | / | / |/ |/ |
unsigned fib(unsigned n) { if (0 == n) return 0; if (n <= 2) return 1; return 2; } |
And we can begin generalization. We wrote 2, bet we mean 1 + 1 .
unsigned fib(unsigned n) { if (0 == n) return 0; if (n <= 2) return 1; return 2; } |
|\ |\ | \ | \ | / | / |/ |/ |
unsigned fib(unsigned n) { if (0 == n) return 0; if (n <= 2) return 1; return 1 + 1; } |
The left hand side 1 is an instantiation of fib(n - 1);
unsigned fib(unsigned n) { if (0 == n) return 0; if (n <= 2) return 1; return 1 + 1; } |
|\ |\ | \ | \ | / | / |/ |/ |
unsigned fib(unsigned n) { if (0 == n) return 0; if (n <= 2) return 1; return fib(n - 1) + 1; } |
The right hand side 1 is an instantiation of fib(n - 2);
unsigned fib(unsigned n) { if (0 == n) return 0; if (n <= 2) return 1; return fib(n - 1) + 1; } |
|\ |\ | \ | \ | / | / |/ |/ |
unsigned fib(unsigned n) { if (0 == n) return 0; if (n <= 2) return 1; return fib(n - 1) + fib(n - 2); } |
Review it, the same structure also fits fib(2), so the second condition can be more strict:
unsigned fib(unsigned n) { if (0 == n) return 0; if (n <= 2) return 1; return fib(n - 1) + fib(n - 2); } |
|\ |\ | \ | \ | / | / |/ |/ |
unsigned fib(unsigned n) { if (0 == n) return 0; if (1 == n) return 1; return fib(n - 1) + fib(n - 2); } |
And we got the whole Fibonacci sequence, exclusively from testing.
Thank Mr. Kent Beck for permitting unit-- to use this interesting and illustrative example on it's website.