NU EECS UnitTest++ Notes

Chris Riesbeck
Last updated: July 3, 2009

UnitTest++ is an open-source library for doing unit testing and test-driven development in C++. These notes describe

Installing UnitTest++

UnitTest++ is available in a Zip file here. This file is good for Linux, Windows, and MacOS X.

Unfortunately, the UnitTest++ Makefile needs a little modification to work with GCC 4 on Cygwin (Cygwin's fault). It also doesn't package up the files for use by gcc. Both of those are fixed in my modified Makefile. It puts UnitTest++ in /usr/local, a directory intended for local additions to the Unix distribution. The NU EECS Magic Makefile assumes that UnitTest++ has been placed in /usr/local.

Cygwin note: /usr/local is in your Cygwin directory.

MacOS X note: /usr/local is a hidden directory. To see it in the Macintosh Finder, use Finder menu command Go | Go to Folder... and type /usr/local.

To get the modified Makefile:

Now you're ready to build and install UnitTest++.

Testing UnitTest++

Test your setup

If that sample project compiles and runs and produces the messages shown in the sample output, you should be all set.

If you get errors about UnitTest++ not existing, check to see if the following exist:

If they don't, post and/or contact me.

Using UnitTest++

Very little additional code is required when writing test code with UnitTest++. The general pattern for a test file is:

#include <UnitTest++/UnitTest++.h>

...one or more #include's for your code...

...one or more (more is better) test forms like this:

TEST(testName)
{
  ...testing code using CHECK_EQUAL() or CHECK()...
}

The following main() function which never changes:

int main()
{
    return UnitTest::RunAllTests();
}

For example, this is the test code in the sample project:

#include <UnitTest++/UnitTest++.h>
#include "geometry.h"

TEST(areaPositiveSides)
{
    CHECK_EQUAL( 10, geometry::area( 2, 5 ) );
    CHECK_EQUAL( 10, geometry::area( 5, 2 ) );
    CHECK_EQUAL( 36, geometry::area( 6, 6 ) );
}

TEST(areaZeroSides)
{
    CHECK_EQUAL( 0, geometry::area( 5, 0 ) );
    CHECK_EQUAL( 0, geometry::area( 0, 5 ) );
    CHECK_EQUAL( 0, geometry::area( 0, 0 ) );
}

TEST(areaNegativeSides)
{
    CHECK_EQUAL( 0, geometry::area( 5, -2 ) );
    CHECK_EQUAL( 0, geometry::area( -2, 5 ) );
    CHECK_EQUAL( 0, geometry::area( -2, -5 ) );
}

int main()
{
    return UnitTest::RunAllTests();
}

Doing TDD with UnitTest++

The following is an example of doing test-driven development, using UnitTest++. 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.

In it I put the NU EECS Magic Makefile.

Now I create a file tests.cpp with test code -- not account code! I start by testing something very basic, e.g., that a new account has a zero balance. I write a test case that creates a default account object, acct, then checks that its balance is 0.

#include <UnitTest++/UnitTest++.h>

TEST(testDefaultBalanceZero)
{
  Account acct;
  CHECK_EQUAL( 0, acct.getBalance() );
}

int main()
{
  return UnitTest::RunAllTests();
}

Now I try to compile the tests by executing make.

"Whoops!" you may say. "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've never seen your tests fail, maybe they're broken and always pass. This happens far more often than you think.

In this case, my compile fails, but more importantly, it fails for the right reason: error: 'Account' was not declared in this scope. If I saw a different error, that would mean there's something wrong with my test.

tdd account compile failure

So now I write just enough code to pass the test, and no more. Here's my Account.h file:

#ifndef ACCOUNT_H
#define ACCOUNT_H

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

#endif

Here's my Account.cpp file:

#include "Account.h"

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

I add #include "Account.h" to my test file:

#include <UnitTest++/UnitTest++.h>
#include "Account.h"

TEST(testDefaultBalanceZero)
{
  Account acct;
  CHECK_EQUAL( 0, acct.getBalance() );
}

int main()
{
  return UnitTest::RunAllTests();
}

I try to build again. Compiles fine.

tdd account compiles

Now to run the tests. I type ./account.exe. Yay! My tests pass. 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.

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

CHECK_EQUAL(expected, expression)

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

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

For this to work,

CHECK_CLOSE(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.,

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

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

Account acct;
acct.setBalance( 32.35 );
CHECK_EQUAL( 32.35, acct.getBalance() );
CHECK(expression)

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

CHECK( !gBook.isEmpty() );
CHECK_THROW( expression, type )

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

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

Comments? comment icon Let me know!

Valid HTML 4.01 Transitional