An Practical Example of TDD

Home Page
Documentation

Fibonacci Sequence

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.

Acknowledgement

Thank Mr. Kent Beck for permitting unit-- to use this interesting and illustrative example on it's website.


unit--, the unit test aid for C++ SourceForge.net Logo