This post originated from an RSS feed registered with Agile Buzz
by Keith Ray.
Original Post: Part 1 - Test Driven Development from Model to Model-View-Controller
Feed Title: MemoRanda
Feed URL: http://homepage.mac.com/1/homepage404ErrorPage.html
Feed Description: Keith Ray's notes to be remembered on agile software development, project management, oo programming, and other topics.
This is an example of test-driven development. We're going to implement a simple calculator object
test-first and hook it up to a user-interface. First thing we need to do is #include the test
framework. I'm using my version of Michael Feather's CppUnit.
So I've decided that a calculator has a display representable by a string, and a history
that is a string-list. In order to get this to compile, I better define the minimal
classes required. Normally you only implement one test at a time, but these are two very simple tests. Slow C++ compile-link-run turn-around-time tends make you want to batch things up. Be careful to keep the batches small.
Note that the initial implementations of GetDisplay() and GetHistory() are going to
make the test fail. That is intentional; I want the tests to inially fail in order
to test the tests . I also initially wrote a single test, but changed it to
two tests, so that each test only tests one thing. It's too easy to get false negatives
or false positives if a test is testing multiple things. If you can't do the "right thing"
when things are simple, what's going to make you do the right thing when thigns are
more difficult.
And why did I make the destructor and other methods virtual? First,
it's a habit I've developed - if there is inheritance and the destructor and other
methods are not virtual, you can get certain bugs that can be hard to debug. Second,
I'm doing Object Oriented Programming. Polymorphism is a big part of that, and C++
does run-time polymorphism by declaring things virtual. Third, NOT declaring a
destructor or a method virtual is a form of optimization... and optimizing code
before you have hard evidence that it needs it is "premature optimization". Just
don't do it.
I also need to declare a test-runner and main function...
int main (int argc, char * const argv[])
{
TestRunner calcTests("CalcTests");
calcTests.addTest( SimpleCalcTests::Suite() );
calcTests.runAllTestsOnce();
return 0;
}
Here's the output from this code so far:
TestSuite start CalcTests
TestSuite start SimpleCalcTests
TestCase CalcDisplayStartsEmpty... AssertionFailedError
'expected: '' but was: 'display'', line 77,
file 'simplecalc/main.cpp'
AssertionFailedError
FAIL 0 seconds
TestCase CalcHistoryStartsEmpty... AssertionFailedError
'expected: 0 but was: 1', line 85,
file 'simplecalc/main.cpp'
AssertionFailedError
FAIL 0 seconds
TestSuite end SimpleCalcTests
TestSuite end CalcTests
!!!FAILURES!!!
Test Results:
Run: 2 Failures: 2 Errors: 0
There were 2 failures:
1) line: 77 simplecalc/main.cpp "expected: '' but was: 'display'"
2) line: 85 simplecalc/main.cpp "expected: 0 but was: 1"
This output is a little verbose because at various times using this test-suite I have needed to see where crashes were occurring (thus output about which suite and test are beginning to run and finishing running) and where slow code/tests were. Yes, I do care about code-speed, but I measure before I optimize.
Now that I have the tests failing, time to make them pass. In this case, by permitting the
display string to get empty and the history list to be empty. I'll demonstrate the use of
a test-suite. (TestRunner is a sub-class of TestSuite, by the way.)