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
UnitTest++ is available in a Zip file
here.
This file is good for Linux, Windows, and MacOS X.
- Download the UnitTest++ archive.
- Extract all its files into some directory.
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:
- Right-click (control-click on MacOS X)
on this link.
- Choose "Save Link As..." from the pop-up menu.
- Save the file in your UnitTest++ directory, replacing the existing
Makefile.
Now you're ready to build and install UnitTest++.
- Open a terminal window (Linux or MacOS X) or Cygwin (Windows).
- cd to the UnitTest++ directory.
Cygwin note:If you put UnitTest++ in
C:\C++\UnitTest++\, you would do cd /cygdrive/c/C++/UnitTest++
.
- Use ls to verify you see Makefile.
If you see Makefile.txt -- unavoidable
on MacOS X -- do mv Makefile.txt Makefile
to replace the
old Makefile with the .txt version.
- Execute
make install
. This will compile and test
UnitTest++ for your machine, then copy the relevant files to /usr/local.
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:
- the file /usr/local/lib/libUnitTest++.a
- a non-empty directory /usr/local/include/UnitTest++/
If they don't, post and/or contact me.
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.
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.
Now to run the tests. I type ./account.exe
.
Yay! My tests pass. Time to write some new tests for
some new functionality.
And on I go, writing tests, running tests, writing code, running tests,
etc.
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,
- expected and expression must be comparable with
==.
- expected and expression must be printable with
<<.
- 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?
Let me know!