EECS 211
CppUnit Notes

Test Driven Development

Testing and test-driven development is a large and important area of software development too often overlooked in computer science courses. Here are three things to read about testing to get you started for this course:

Installing CppUnit

Getting CppUnit

Testing CppUnit

Doing TDD with CppUnit

The following is an example of doing test-driven development, using CppUnit. The task is to define a bank account class, with functions for crediting and debiting the account, and getting the current balance.

First, I create a project directory account.

Next, I create some test code -- not the account code! I start by testing something very basic, e.g., that a new account has a zero balance. In CppUnit, the essence of that test is:

Account acct;
CPPUNIT_ASSERT_EQUAL( 0, acct.getBalance() );

This creates a default account object in acct, then checks that the balance is 0.

I have to put this inside boilerplate code that creates what is called a test fixture. This boilerplate takes care of running the tests and printing the results. I use this simple CppUnit template as a guide to make this test file:

#include <cppunit/extensions/HelperMacros.h>
#include <cppunit/ui/text/TestRunner.h>

class AccountTests : public CppUnit::TestFixture  {

  CPPUNIT_TEST_SUITE( AccountTests );
  CPPUNIT_TEST( testConstructor );
  CPPUNIT_TEST_SUITE_END();

public: 

  void testConstructor()
  {
    Account acct;
    CPPUNIT_ASSERT_EQUAL( 0, acct.getBalance() );
  }
};

int main( int argc, char **argv)
{
  CppUnit::TextUi::TestRunner runner;
  runner.addTest( AccountTests::suite() );
  runner.run();
  return 0;
}

Now to compile and run the tests. At this point, you may say "Whoops! You forgot to write the account code!"

No, I didn't. A key part of TDD is to test before coding. The only way to know if a test works is to see it fail first. If you write the code before running the tests, and the tests pass, maybe the tests are broken and always pass. This happens far more often than you think. Always run the tests before writing or changing any code to make sure the tests are actually testing what you think they do.

To run the tests, I need this Makefile:

# Account Project
SRCS = AccountTests.cpp
HDRS = 
PROJ = account

# Remaining lines shouldn't need changing
...the rest of the standard Makefile

In this case, the test file doesn't even compile.

tdd account compile failure

So now I write just enough code to make an empty bank account, and no more. Here's my Account.h file:

class Account {
private:
    int balance;
public:
    Account( );
    int getBalance();
};

Here's my Account.cpp file:

#include "Account.h"

Account::Account() { }

int Account::getBalance()
{
	return balance;
}

I add Account.cpp and Account.h to my Makefile:

# Account Project
SRCS = Account.cpp AccountTests.cpp
HDRS = Account.h
PROJ = account

# Remaining lines shouldn't need changing
...

I try to build again. Compiles fine, except for the expected Cygwin warning.

tdd account compiles

Time to run the tests by typing ./account.exe. Ooops! They failed.

tdd account test fails

Ah -- I forgot to initialize the balance. I'll use a member initializer list because that's best practice.

#include "Account.h"

Account::Account() : balance( 0 ) { }

int Account::getBalance()
{
	return balance;
}

Try again... Yay! My test passes. Time to write some new tests for some new functionality.

tdd account test passes

And on I go, writing tests, running tests, writing code, running tests, etc.

Types of Assertions

The heart of any test function are the assertions. A typical test will create a data structure, possibly modify it in some way, and then make several assertions about it. If any assertion turns out to be false, the test fails.

CppUnit provides several special forms for writing assertion. See the online documentation for more details.

CPPUNIT_ASSERT_EQUAL(expected, expression)

Use this to test if an expression returns some expected value, e.g.,

GradeBook gBook;
CPPUNIT_ASSERT_EQUAL( 0, gBook.size() );

For this to work,

CPPUNIT_ASSERT_DOUBLES_EQUAL(expected, expression, delta)

Use this when an expression involves floating point calculations, such as division, that will often not be exactly equal to some predicted value, because of round-off, e.g.,

CPPUNIT_ASSERT_DOUBLES_EQUAL( 76.5, gBook.average(), 0.001 );

If such calculations are not involved, use CPPUNIT_ASSERT_EQUAL, because it's a stronger test, e.g.,

Account acct;
acct.setBalance( 32.35 );
CPPUNIT_ASSERT_EQUAL( 32.35, acct.getBalance() );
CPPUNIT_ASSERT(expression)

Use this to test for anything other than equality, e.g.,

CPPUNIT_ASSERT( !gBook.isEmpty() );
CPPUNIT_ASSERT_THROW( expression, type )

Use this to test code that is supposed to throw an exception of the given type, e.g.,

CPPUNIT_ASSERT_THROW( employees.setHours("John", 20 ), Payroll::Exception );