The Artima Developer Community
Sponsored Link

Agile Buzz Forum
Part 1 - Test Driven Development from Model to Model-View-Controller

0 replies on 1 page.

Welcome Guest
  Sign In

Go back to the topic listing  Back to Topic List Click to reply to this topic  Reply to this Topic Click to search messages in this forum  Search Forum Click for a threaded view of the topic  Threaded View   
Previous Topic   Next Topic
Flat View: This topic has 0 replies on 1 page
Keith Ray

Posts: 658
Nickname: keithray
Registered: May, 2003

Keith Ray is multi-platform software developer and Team Leader
Part 1 - Test Driven Development from Model to Model-View-Controller Posted: Aug 8, 2004 1:19 PM
Reply to this message Reply

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.
Latest Agile Buzz Posts
Latest Agile Buzz Posts by Keith Ray
Latest Posts From MemoRanda

Advertisement

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.

#include "TestDrivenDesign.h"
#include <string>
#include <list>

Now define our first test. I like to put these into a namespace.

namespace SimpleCalcTests
{
    DECLARE_TEST(CalcDisplayStartsEmpty);
    DECLARE_TEST(CalcHistoryStartsEmpty);

    DEFINE_TEST(CalcDisplayStartsEmpty)
    {
        SimpleCalc aCalculator;
        assertStringsEqual( "", aCalculator.GetDisplay() );
    }

    DEFINE_TEST(CalcHistoryStartsEmpty)
    {
        SimpleCalc aCalculator;
        StringList astringList = aCalculator.GetHistory();
        assertLongsEqual( 0, astringList.size() );
    }

    TestSuite* Suite()
    {
        TestSuite* result = new TestSuite("SimpleCalcTests");
        result->addTest( new SimpleCalcTests::CalcDisplayStartsEmpty );
        result->addTest( new SimpleCalcTests::CalcHistoryStartsEmpty );

        return result;
    }
}

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.

typedef std::list StringList;

class SimpleCalc
{
public:
    SimpleCalc()
    {
    }

    virtual ~SimpleCalc()
    {
    }

    virtual std::string GetDisplay() const
    {
        return "display";
    }

    virtual StringList GetHistory() const
    {
        StringList result;
        result.push_back( "history" );
        return result;
    }
};

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.)

class SimpleCalc
{
public:
    [other code unchanged ...]

    virtual std::string GetDisplay() const
    {
        return "";
    }

    virtual StringList GetHistory() const
    {
        StringList result;
        return result;
    }
};

Here's the output of the passing tests. It's much more succinct.

    TestSuite start CalcTests
        TestSuite start SimpleCalcTests
            TestCase CalcDisplayStartsEmpty...  0 seconds
			TestCase CalcHistoryStartsEmpty...  0 seconds
		TestSuite end SimpleCalcTests
	TestSuite end CalcTests
	OK( 2 tests)

Part two will get into the meatier part of the calculator.

Read: Part 1 - Test Driven Development from Model to Model-View-Controller

Topic: Results of May-June Raytracing Competition Previous Topic   Next Topic Topic: Re: Experiments in Smalltalk

Sponsored Links



Google
  Web Artima.com   

Copyright © 1996-2019 Artima, Inc. All Rights Reserved. - Privacy Policy - Terms of Use