Now that we know about unit tests, we can go a step further. TDD (test driven development). But what is that on the first place ? Different from Unit tests, in TDD, tests always comes first.
The best definition on TDD basic idea comes from Manfred Lange, one of the csUnit Lead Developers:
"Test a little, code a little, refactor a little -- in that sequence! And the rhythm here is not in hours or days. The rhythm for the entire sequence is seconds or minutes. Run tests frequently, preferably as often as possible. With TDD the test always comes first, while with 'unit testing' in the broadest sense this is not clear. Generating tests after-the-fact is not considered to be TDD. Sometimes I even prefer the term 'Test-Driven Design' as it makes clearer what this really is about."
When we talk about TDD, we are not talking just about tests. We are talking about a development process, consisting on:
- Create a test code
- See the test fail
- Create the code to make the test pass.
- Refactoring the code
- Refactoring the test
- Do it again
The principal benefit on TDD design is that it will make OO design goals easy to achieve: TDD is a technique for determining class structure by making testability a first class consideration in your design. Focusing on testing a unit of code at a time leads to creating cohesive classes with a distinct purpose and responsibility. The need and desire to quickly setup an isolated unit test on a class will lead to a loosely coupled design.
The use of UML and CRC, is based on the belief that it is easy to design ideas on abstract artifacts, because code is difficult to change once written. This assumption could be changed because TDD resulting code is cohesive and loosely coupled. With a couple of good tools automating the tests and assembling the code, can make this technique much more efficient.
There is an interesting (but incomplete) series of articles on Jeremy D Miller's blog about a set of “TDD laws”.
Coming from Kent Beck's “Test-Driven Development By Example” book, and adapted to Ruby by Rafael C. Schouery, here is a sample on how to create a Fibonacci number function recursively using TDD.
First, we would like that fib(0) = 0. So, we start to write down our test.
require 'test/unit'
class TestFibonacci <>
To get the test pass, we need a Fibonacci class and a self.fib method on it.
class Fibonacci
def self.fib(n)
return 0;
end
end
With the tests going on, we could tests if fib(1) = 1, so we change the test_fib method to include this condition.
def test_fib
assert_equal(0,Fibonacci.fib(0));
assert_equal(1,Fibonacci.fib(1));
end
Which obviously will not pass. We can change the self.fib to the following.
def self.fib(n)
return 0 if(n == 0)
return 1
end
Now the tests are passing, but there is a code repetition, a bad practice. Refactoring the code with the DRY principle, we have:
def test_fib0
cases = [[0,0],[1,1]]
cases.each do |c|
assert_equal(c[1],Fibonacci.fib(c[0]))
end
end
Now we have pair tests to try (n,fib(n)). We can add the pair (2,1) and the tests still pass. If we add the pair (3,2) the test will fail. we can fix the code to fix this problem.
def self.fib(n)
return 0 if(n == 0)
return 1 if(n <= 2) return 2 end
Now we have the tests passing, we can make a more generic code, and instead of 2 we can return:
fib(n-1) + fib(n-2)
And now we have the final code:
require 'test/unit'
class TestFibonacci < cases =" [[0,0],[1,1],[2,1],[3,2]]" n ="="">
That is it TDD.