3/04/2014
1
Unit testing and code
coverage for C++
cppUnit and gcov
1
This lecture segment
cppUnit
Reference material
TDD
Partial development of Bitmap class in the “Test
Driven Development” style
TDD ideas illustrated
cppUnit code illustrated
Code coverage example
2
cppunit
Available at
http://sourceforge.net/projects/cppunit
Is installed on Linux machines in lab used
for CSCI222
3
Tutorials …
http://www.comp.nus.edu.sg/~cs3214s/too
ls/cppunitSol.html
http://www.evocomp.de/tutorials/tutorium_
cppunit/howto_tutorial_cppunit_en.html
Documentation with cppUnit
Docs folder in /share/cs-pub/222/testing
“Money” tutorial
4
cppUnit
Reference
5
cppUnit reference
Coding an individual test
Use assertion macros defined in unit test library
Elements for building tests
TestFixture, TestSuite, TestRunner
Output formatters
Compilation and linking notes
makefile etc
6
3/04/2014
2
Test classes - Money
Money – example in cppUnit
(yeeech – looks more like Java than C++. Reason? It’s based on Beck’s own
Money example which is in Java)
class Money {
public:
Money( double amount, std::string currency ) :
m_amount( amount ) , m_currency( currency ) { }
double getAmount() const { return m_amount; }
std::string getCurrency() const
{ return m_currency; }
private:
double m_amount;
std::string m_currency;
};
7
Test classes – Fraction (A. Müller) –
the "EvoComp" Web reference
class Fraction
{
public:
// constructor
Fraction (int = 0, int = 1) throw (DivisionByZeroException);
// copy-constructor and assignment-operator
Fraction (const Fraction&);
Fraction& operator= (const Fraction&);
// comparing operators
bool operator== (const Fraction&) const;
bool operator!= (const Fraction&) const;
// arithmetic operators
friend Fraction operator+ (const Fraction&, const Fraction&);
friend Fraction operator- (const Fraction&, const Fraction&);
// output on stdout
friend ostream& operator<< (ostream&, const Fraction&);
private:
// method for reducing
void reduce (void);
// variables for saving the numerator and denominator
int numerator, denominator;
};
8
Code in an individual test
Use assertion macros defined in cppunit
(Macros are used because compiler can add in source file line number)
If assertion fails, an exception is thrown
CPPUNIT_ASSERT(condition):
Checks the condition and throws an exception if it's false.
CPPUNIT_ASSERT_MESSAGE(message, condition):
Checks the condition and throws an exception and showing specified message if
it is false.
CPPUNIT_ASSERT_EQUAL(expected,current):
Checks whether the expected condition is the same as current, and raises an
exception showing the expected and current values.
CPPUNIT_ASSERT_EQUAL_MESSAGE(message,expected,curr
ent):
Checks whether the expected is the same as the actual, and raises an exception
showing the expected and current values, and specified message.
CPPUNIT_ASSERT_DOUBLES_EQUAL(expected,current,delta):
Checks whether the expected and current difference is smaller than delta. If it
fails, the expected and current values are shown.
9
Assertion macro expands …
CPPUNIT_NS::Asserter::failIf( !(condition), \
CPPUNIT_NS::Message( "assertion failed", \ "Expression: " #condition), \ CPPUNIT_SOURCELINE() ) )
Inserts into code a call to (static) method in Asserter class
static public void CPPUNIT_API fail (const Message &message, const
SourceLine &sourceLine=SourceLine())
Throws a Exception with the specified message and location.
#define CPPUNIT_SOURCELINE() CPPUNIT_NS::SourceLine( __FILE__,
__LINE__ )
Constructs a SourceLine object initialized with the location where the macro is
expanded.
__FILE__, __LINE__ are compiler variables updated as code is compiled
10
Assertion examples
void MoneyTest::testEqual()
{
// Set up
const Money money123FF( 123, "FF" );
const Money money123USD( 123, "USD" );
const Money money12FF( 12, "FF" );
const Money money12USD( 12, "USD" );
// Process & Check
CPPUNIT_ASSERT( money123FF == money123FF );
CPPUNIT_ASSERT( money12FF != money123FF );
…
11
Assertion examples
void MoneyTest::testAdd()
{ // Set up
const Money money12FF( 12, "FF" );
const Money expectedMoney( 135, "FF" );
// Process
Money money( 123, "FF" );
money += money12FF;
// Check
CPPUNIT_ASSERT( expectedMoney == money ); // add
// add should return ref. on 'this'.
CPPUNIT_ASSERT( &money == &(money += money12FF) );
}
12
3/04/2014
3
Assertion examples …
void fractiontest :: setUp (void)
{
// set up test environment (initializing objects)
a = new Fraction (1, 2);
b = new Fraction (2, 3);
c = new Fraction (2, 6);
d = new Fraction (-5, 2);
e = new Fraction (5, -2);
f = new Fraction (-5, -2);
g = new Fraction (5, 2);
h = new Fraction ();
}
13
Assertion examples …
void fractiontest :: addTest (void)
{
// check subtraction results
CPPUNIT_ASSERT_EQUAL (*a + *b, Fraction (7, 6));
CPPUNIT_ASSERT_EQUAL (*b + *c, Fraction (1));
CPPUNIT_ASSERT_EQUAL (*d + *e, Fraction (-5));
…
14
Assertion examples …
void fractiontest :: equalTest (void)
{
// test successful, if true is returned
CPPUNIT_ASSERT (*d == *e);
CPPUNIT_ASSERT (Fraction (1) == Fraction (2, 2));
CPPUNIT_ASSERT (Fraction (1) != Fraction (1, 2));
// both must have equal valued
CPPUNIT_ASSERT_EQUAL (*f, *g);
CPPUNIT_ASSERT_EQUAL (*h, Fraction (0));
…
15
Assertion examples …
void fractiontest :: exceptionTest (void)
{
// an exception has to be thrown here
CPPUNIT_ASSERT_THROW (Fraction (1, 0),
DivisionByZeroException);
}
16
Elements for building tests
TestFixture, TestSuite,
TestRunner
17
TestFixture
Introduced to express idea of
setUp
tearDown
operations that would be invoked prior to and after each
individual test.
Keep the individual tests independent
Typically, TestFixture “manages resources”
Defines some pointers to instances of class under test
setUp does a series of new operations to create new clean
instances of class ready for next test
tearDown deletes instances
Can work with instance members that are instance of
class under test provided the class defines a member
function that restores instance to standard state
18
3/04/2014
4
TestSuite, TestRunner
TestSuite
Groups a number of tests (using TestFixture) so
that they can all be run
cppUnit C++ code needed to define a TestSuite
is ugly
HelperMacros make it clean!
TestRunner
Driver program
19
Helper macros
#define CPPUNIT_TEST_SUITE(ATestFixtureType)
Begin test suite.
#define CPPUNIT_TEST_SUITE_END()
End declaration of the test suite.
#define CPPUNIT_TEST(testMethod)
Add a method to the suite.
#define CPPUNIT_TEST_EXCEPTION(testMethod, ExceptionType)
Add a test which fail if the specified exception is not caught.
#define CPPUNIT_TEST_FAIL(testMethod) CPPUNIT_TEST_EXCEPTION(
testMethod, CPPUNIT_NS::Exception )
Adds a test case which is excepted to fail.
#define CPPUNIT_TEST_SUITE_REGISTRATION(ATestFixtureType)
#define CPPUNIT_REGISTRY_ADD(which, to)
#define CPPUNIT_REGISTRY_ADD_TO_DEFAULT(which)
There are additional less commonly used macros
20
Example TestFixtures and suites
class fractiontest : public CPPUNIT_NS :: TestFixture
{
CPPUNIT_TEST_SUITE (fractiontest);
CPPUNIT_TEST (addTest);
CPPUNIT_TEST (subTest);
CPPUNIT_TEST (exceptionTest);
CPPUNIT_TEST (equalTest);
CPPUNIT_TEST_SUITE_END ();
public:
void setUp (void);
void tearDown (void);
protected:
void addTest (void);
void subTest (void);
void exceptionTest (void);
void equalTest (void);
private:
Fraction *a, *b, *c, *d, *e, *f, *g, *h;
};
Tests
Compose tests
into “suite”
“Resources”
managed by fixture21
fractiontest
#include "fractiontest.h "
CPPUNIT_TEST_SUITE_REGISTRATION (fractiontest);
void fractiontest :: setUp (void)
{
// set up test environment (initializing objects)
a = new Fraction (1, 2);
b = new Fraction (2, 3);
…
}
void fractiontest :: tearDown (void)
{
// finally delete objects
delete a; delete b; delete c; delete d;
…
}
Test suite registration macro defines some variables with
all actual TestSuite etc instantiated (at link load time) 22
Money
class MoneyTest : public CppUnit::TestFixture {
CPPUNIT_TEST_SUITE( MoneyTest );
CPPUNIT_TEST( testConstructor );
CPPUNIT_TEST_SUITE_END();
public:
void setUp();
void tearDown();
void testConstructor();
};
23
Money
#include "MoneyTest.h"
// Registers the fixture into the 'registry'
CPPUNIT_TEST_SUITE_REGISTRATION(
MoneyTest );
void MoneyTest::setUp() { }
void MoneyTest::tearDown() { }
void MoneyTest::testConstructor() {
CPPUNIT_FAIL( "not implemented" );
}
“the macro CPPUNIT_TEST_SUITE_REGISTRATION() is used to create a static
variable that automatically registers its test suite in a global registry “ 24
3/04/2014
5
Helper macros – template classes
The test suite macros can even be used with
templated test classes. For example:
template class StringTest :
public CppUnit::TestFixture
{
CPPUNIT_TEST_SUITE( StringTest );
CPPUNIT_TEST( testAppend );
CPPUNIT_TEST_SUITE_END();
public: ...
};
25
TestRunner and driver program
Basics are simple
Create a TestRunner
Link it to some “reporting” component
Link it to test suite
Run the tests
Lots of options
Mainly relating to different output formats
26
Money
int main(int argc, char* argv[])
{
// Get the top level suite from the registry
CppUnit::Test *suite =
CppUnit::TestFactoryRegistry::getRegistry().makeTest();
// Adds the test to the list of test to run
CppUnit::TextUi::TestRunner runner; runner.addTest( suite );
// Change the default outputter to a compiler error format outputter
runner.setOutputter( new CppUnit::CompilerOutputter(
&runner.result(), std::cerr ) );
// Run the tests.
bool wasSucessful = runner.run();
// Return error code 1 if the one of test failed.
return wasSucessful ? 0 : 1;
}
27
Fraction
int main (int argc, char* argv[])
{
CPPUNIT_NS :: TestResult testresult;
CPPUNIT_NS :: TestResultCollector collectedresults;
testresult.addListener (&collectedresults);
CPPUNIT_NS :: TestRunner testrunner;
testrunner.addTest (CPPUNIT_NS :: TestFactoryRegistry :: getRegistry
().makeTest ());
testrunner.run (testresult);
CPPUNIT_NS :: CompilerOutputter compileroutputter (&collectedresults,
std::cerr);
compileroutputter.write ();
return collectedresults.wasSuccessful () ? 0 : 1;
}
28
Outputters …
CompilerOutputter
Outputs a TestResultCollector in a compiler
compatible format.
Printing the test results in a compiler compatible
format (assertion location has the same format
as compiler error), allow you to use your IDE to
jump to the assertion failure.
TextOutputter
XmlOutputter
Save the test result as a XML stream.
29
Compilation
Need to include appropriate headers
TestFixture class
#include
#include
Testdriver program
#include
#include
#include
#include
#include
30
3/04/2014
6
Now where are those header files …
#include “Money.h”
Your stuff, current directory
#include
Standard C and C++ headers
Somewhere on your system in a directory called include
Your compiler knows where
What about etd?
Cygwin treats cppunit as core
It adds the header-libraries to the main C/C++ include collection
Others?
Typical install creates /usr/local/include/cppunit
But these include files are not processed automatically
You must modify your “include path”
31
And where are the run-time libraries …
This was covered in the lecture introducing
the NetBeans IDE
32
Makefile ? ! …
Leave it to NetBeans
You simply add the reference to
/usr/include/cppunit for the compiler
And you add the reference to /usr/lib/cppunit.a
for the linker
Use "wizard" dialogs to add these references
33
Test driven development
34
Test twice, code once
“The style here is to write a few lines of
code, then a test that should run, or even
better, to write a test that won't run, then
write the code that will make it run.”
OK,
I’ll play your game (but just this once)
I’ll develop “Bitmap” in TDD style
35
TestFixture, suite, and driver
Driver program
Based on A Müller’s test program for his
fraction class
Doesn’t get changed during development of
Bitmap class and its tests
BitmapTest, Bitmap
These classes evolve as we expand Bitmap
functionality
BitmapTest is a TestFixture, helper macros
used to create TestSuite using it
36
3/04/2014
7
Driver
#include
#include
#include
#include
#include
#include
int main (int argc, char* argv[])
{
// Result collection
CPPUNIT_NS :: TestResult testresult;
CPPUNIT_NS :: TestResultCollector collectedresults;
testresult.addListener (&collectedresults);
CPPUNIT_NS :: TextTestProgressListener tracker;
testresult.addListener(&tracker);
37
Driver - contd
…
// Setting up runner and running test
CPPUNIT_NS :: TestRunner testrunner;
testrunner.addTest (CPPUNIT_NS :: TestFactoryRegistry ::
getRegistry ().makeTest ());
testrunner.run (testresult);
// Reporting
CPPUNIT_NS :: CompilerOutputter compileroutputter
(&collectedresults, std::cerr);
compileroutputter.write ();
return collectedresults.wasSuccessful () ? 0 : 1;
}
38
Test driven development – step 0
Define class Bitmap
Owns an array of unsigned long integers
Methods
boolean Identity(const Bitmap& other)
Are we the same Bitmap
Compare our address with other’s address!
(Think we can get this right)
boolean Equals(const Bitmap& other)
Are we equal?
What do you mean equal?
• This is the part we test twice – code once!
void Zero()
Set my bits all to zero.
39
TestFixture
Owns couple of instances of Bitmap
setUp
Clean them out using Zero
tearDown
Nothing to do
Test identity
Test equality
40
#ifndef BITMAPTEST_H
#define BITMAPTEST_H
#include
#include
#include "Bitmap.h"
class BitmapTest : public CppUnit::TestFixture
{
CPPUNIT_TEST_SUITE (BitmapTest);
CPPUNIT_TEST (testEquals);
CPPUNIT_TEST_SUITE_END ();
private:
Bitmap bitmap1;
Bitmap bitmap2;
public:
void setUp();
void tearDown();
protected:
void testEquals();
};
#endif
41
#include "BitmapTest.h"
#include "Bitmap.h"
CPPUNIT_TEST_SUITE_REGISTRATION (BitmapTest);
void BitmapTest::setUp()
{
// Put the bitmaps back in standard state
// (Need at least two as doing things like
// checking equality)
bitmap1.Zero();
bitmap2.Zero();
}
void BitmapTest::tearDown()
{
// Nothing to do
}
42
3/04/2014
8
void BitmapTest::testEquals()
{
// Identity should work
CPPUNIT_ASSERT(bitmap1.Identity(bitmap1));
CPPUNIT_ASSERT(bitmap2.Identity(bitmap2));
CPPUNIT_ASSERT(!bitmap1.Identity(bitmap2));
// At this stage - equality incorrectly defined
// It is identity
// (It shouldn't be!)
// This test should succeed - I'm equal to me
CPPUNIT_ASSERT(bitmap1.Equals(bitmap1));
// so should this
CPPUNIT_ASSERT(bitmap2.Equals(bitmap2));
// But this should fail
CPPUNIT_ASSERT(bitmap1.Equals(bitmap2));
}
43
Class Bitmap
#ifndef __MYBITSCLASS__
#define __MYBITSCLASS__
// Code assumes 32-bit unsigned long integers
#define MAXBITS 512
#define NUMWORDS 16
typedef unsigned long Bits;
class Bitmap {
public:
Bitmap();
void Zero(void);
bool Equals(const Bitmap &other) const;
bool Identity(const Bitmap &other) const;
private:
Bits fBits[NUMWORDS];
};
#endif
44
Class Bitmap
#include "Bitmap.h"
Bitmap::Bitmap()
{
Zero();
}
void Bitmap::Zero(void)
{
for(int i=0;i
BitmapTest::testEquals
Assertion
BitmapTest.cc
34
assertion failed
- Expression: bitmap1.Equals(bitmap2)
1
1
0
1
46
“Refactor”
bool Bitmap::Equals(const Bitmap& other) const
{
for(int i = 0; i < NUMWORDS; i++)
if(this->fBits[i] != other.fBits[i]) return false;
return true;
}
47
GUI interfaces
Possible using Qt C++ graphics package
48
3/04/2014
9
Next increment – more functionality in
Bitmap
class Bitmap {
public:
Bitmap();
void Zero(void);
void SetBit(int bitnum);
int Count(void) const;
bool Equals(const Bitmap &other) const;
bool Identity(const Bitmap &other) const;
private:
Bits fBits[NUMWORDS];
};
49
BitmapTest
class BitmapTest : public CppUnit::TestFixture
{
CPPUNIT_TEST_SUITE (BitmapTest);
CPPUNIT_TEST (testEquals);
CPPUNIT_TEST (testSetBit);
CPPUNIT_TEST (testSetBitAndCount);
CPPUNIT_TEST_SUITE_END ();
private:
Bitmap bitmap1;
Bitmap bitmap2;
public:
void setUp();
void tearDown();
protected:
void testEquals();
void testSetBit();
void testSetBitAndCount();
};
50
BitmapTest
Extended equality test
Can now make Bitmaps that are not equal (by
using set bit operation)
Add two more extensive tests on new test
and count operations
51
BitmapTestvoid BitmapTest::testEquals(){
// Identity should work
CPPUNIT_ASSERT(bitmap1.Identity(bitmap1));
CPPUNIT_ASSERT(bitmap2.Identity(bitmap2));
CPPUNIT_ASSERT(!bitmap1.Identity(bitmap2));
CPPUNIT_ASSERT(bitmap1.Equals(bitmap1));
CPPUNIT_ASSERT(bitmap2.Equals(bitmap2));
CPPUNIT_ASSERT(bitmap1.Equals(bitmap2));
// Have some functionality to change the bits
// See if equals works after that
// Set a different bit
bitmap1.SetBit(22);
bitmap2.SetBit(123);
// Shouldn't be equals any more!
CPPUNIT_ASSERT(!bitmap1.Equals(bitmap2));
}
52
BitmapTestvoid BitmapTest::testSetBit(){
// If set bit 0 in one, they should not be equal
bitmap1.SetBit(0);
CPPUNIT_ASSERT(!bitmap1.Equals(bitmap2));
// Just being pedantic - reverse roles in test
CPPUNIT_ASSERT(!bitmap2.Equals(bitmap1));
bitmap1.Zero();
// But should be equal after it got cleared
CPPUNIT_ASSERT(bitmap1.Equals(bitmap2));
CPPUNIT_ASSERT(bitmap2.Equals(bitmap1));
// If set a non-existent bit it isn't supposed
// to change anything
bitmap1.SetBit(-1);
bitmap2.SetBit(513);
CPPUNIT_ASSERT(bitmap2.Equals(bitmap1));
}
53
BitmapTestvoid BitmapTest::testSetBitAndCount(){
// Set some bits and check that count equals number of bits set
int bits1[] = { 0, 3, 17, 21, 33, 54, 68, 77, 91, 103, 211, 304 };
int bits2[] = { 0, 31, 32, 33, 63, 64, 65, 300, 400, 500, 511 };
int count1 = sizeof(bits1)/sizeof(int);
int count2 = sizeof(bits2)/sizeof(int);
for(int i=0;i
BitmapTest::testSetBitAndCount
Assertion
BitmapTest.cc
77
equality assertion failed
- Expected: 12
- Actual : 11
BitmapTest::testEquals
BitmapTest::testSetBit
3
1
0
1
Compiler
57
Code coverage
g++ compiler and
gcov analysis tool
58
Compilation for code coverage
Set extra compile flags
In NetBeans setup, it is simplest just to specify
--coverage
in the "command line" options for both the compilation
and linker steps
59
Run, then use gcov to analyze
NetBeans can be used to run the application
under test
Best to run several times with different input data so as
to try to test all options
gcov analysis should be done in terminal
session
cd to directory with C++ source
Identify directory where gcov files (.gcno, .gcda) were
created – should be ./build/Debug/GNU-Linux-x86
Run gcov specifying the data directory and file for
analysis
60
3/04/2014
11
Gcov analysis : function summaries
gcov –o ./build/Debug/GNU-Linux-x86 -f Bitmap.cc
100.00% of 3 source lines executed in function void Bitmap::Zero()
100.00% of 7 source lines executed in function void Bitmap::SetBit(int)
100.00% of 8 source lines executed in function void Bitmap::ClearBit(int)
0.00% of 4 source lines executed in function void Bitmap::SetAs(int, int)
85.71% of 7 source lines executed in function int Bitmap::TestBit(int) const
100.00% of 7 source lines executed in function void Bitmap::FlipBit(int)
100.00% of 2 source lines executed in function void
Bitmap::ReadFrom(std::fstream&)
…
100.00% of 4 source lines executed in function int Bitmap::Equals(const
Bitmap&) const
94.32% of 88 source lines executed in file Bitmap.cc
61
Gcov analysis: branch counts
$ gcov –o ./build/Debug/GNU-Linux-x86 -c Bitmap.cc
…
94.32% of 88 source lines executed in file Bitmap.cc
Creating Bitmap.cc.gcov.
$ cat Bitmap.cc.gcov
#include "Bitmap.h"
Bitmap::Bitmap()
14 {
14 Zero();
}
void Bitmap::Zero(void)
15 {
255 for(int i=0;i= MAXBITS))
113 return;
113 int word = bitnum / 32;
…
62
Inspect profiles
Profiles and function summaries
immediately reveal untested code.
63