I decided to give it a try myself while working on one of the labs from my son's Computer Science class at RPI. It was a very simple assignment - write a function that returns the maximum of three integers and test the function with some sample data. Test Driven Development recommends the following:
- Write a test.
- Run the test and verify it fails.
- Write some code.
- Run the tests and fix the code until the tests pass.
- Re-factor the code.
- Repeat.
I found myself drawn towards the functional code, but forced myself to begin by writing:
int max3(int a, int b, int c) {
return -1;
}
I then wrote a test harness with the sample data and ran the tests. As expected, all of the tests failed except one case where -1 was the expected result. Next, I updated the function to return the maximum value, re-ran the tests, and they all passed! So far so good...
In class, the TA asked the students a teaser question - write the same function using only a single if statement. My original implementation had two if statements, so I decided to give it a try and I came up with this:
int max3 (int a, int b, int c) {
int max = a;
if ( (b>max) && (max=b) && (c>max)) max = c;
}
I ran the tests, and they all passed so I thought I was pretty smart. Always curious and never satisfied, I decided to add a few more tests. To my surprise (and disappointment), this test case failed:
a = -2, b = -2, c = -1, expect: -1
Guess I'm better at writing tests than code and I'm not as smart as I first thought. Given a specific failing test case, it was pretty easy to find the flaw in the code so I updated the function to:
int max3 (int a, int b, int c) {
int max = a;
if ( ((b>max) && (max=b)) || ((c>max) && (max=c)));
return max;
}
I re-ran the tests, and they all passed. I'm sure a more skilled developer would have recognized the flaw in my first attempt at using a single if statement, but I'm also sure that even the best programmers make mistakes.
[Update 07-Nov-2010: OK, this code doesn't work either (it has the same type of flaw as my first attempt) and the tests did *not* all pass. When I ran the tests, I took a quick look at the tests that failed with my first attempt, and those cases passed. The unit tests worked great - I just made the mistake of rushing and not checking all the results...]
When I started on this exercise, I thought it was too trivial to be of much interest and pursued it solely as a simple way to start exploring test driven development. To my surprise, this simple example illustrated an important concept - don't write "clever code."
Attempting to be too clever obscures the purpose of the code, making it difficult to understand and maintain, and increases the odds of introducing subtle, hard to detect errors. Imagine trying to debug the flawed max3 implementation if it was buried deep inside 1,000's of lines of code, with the flaw exposed by some complex system test. System level tests would work correctly in many cases, but then fail for unexplained reasons in other cases, leading to a long and difficult debugging session. With a simple failing unit test, it was quick and easy to find the flaw by inspection.
Although this example is far too simple to draw conclusions about the value of Test Driven Development for commercial software development projects, I do feel it's worth exploring the technique in more complex situations.