1.简介
CppUnit是个基于LGPL的开源项目,最初版本移植自Junit,是一个非常优秀的开源测试框架。CppUnit和JUnit一样主要思想来源于极限编程(XProgramming)。主要功能就是对单元测试进行管理,并可进行自动化测试。
对于代码测试的目的,就是要验证代码的正确性或者调试 bug。在写测试代码时需要有针对性,即对那些容易出错的,易变的编写测试代码;而不用对每个细节、每个功能编写测试代码,除非有很好的可靠性要求。
2.安装
(1)到http://sourceforge.net/projects/cppunit下载,然后复制cppunit-1.12.1.tar.gz到/usr/src。运行命令:tar -xf cppunit-1.12.1.tar.gz进行解压。
(2)进入目录/usr/src/cppunit-1.12.1,依次运行下列命令:
A:./configure
B: make
C: make check
D: makeinstall
(3).o和.a文件已经安装到/usr/local/lib中去了,但头文件没有安装到/usr/include中。需要把/cppunit-1.12.1/include/下的cppunit目录复制到/usr/include下。
(4)设置环境变量LD_LIBRARY_PATH到cppunit的安装目录,也就是/usr/local/lib。命令为:export LD_LIBRARY_PATH=/usr/local/lib:$LD_LIBRARY_PATH。
(5)在Eclipse项目工程中整合所需资源。在GCCC++ Linker->Libiary加入cppunit库,在GCC C++ Linker-> Miscellaneous中加入-ldl。
注意:如果写好程序后进行编译,编译器报告没有库文件,则需要把/usr/local/lib下的和cppunit有关的库文件拷贝到/usr/lib64/下,命令为:
cp -rf /usr/local/lib/* /usr/lib64/。
3.使用介绍
3.1 CppUnit基本原理
在 CppUnit中,一个或一组测试对象被称为 Fixture。Fixture就是被测试的目标,可能是一个对象或者一组相关的对象,甚至一个函数。
有了被测试的Fixture,就可以对这个Fixture的某个功能、某个可能出错的流程编写测试代码,这样对某个方面完整的测试被称为TestCase(测试用例)。通常写一个 TestCase的步骤包括:
(1) 对 Fixture进行初始化,及其他初始化操作,比如:生成一组被测试的对象,初始 化值;
(2) 按照要测试的某个功能或者某个流程对 Fixture进行操作;
(3) 验证结果是否正确;
(4) 对 Fixture的及其他的资源释放等清理工作;
对 Fixture的许多测试用例来说,通常(1)、(4)部分代码都是相似的,CppUnit引入了 setUp()和 tearDown()虚函数,可以在 setUp()函数里完成(1)中的初始化代码,而在 tearDown()函数中完成(4)中的释放代码。具体测试用例函数中只需要完成(2)、(3)部分代码即可,运行时 CppUnit会自动为每个测试用例函数运行 setUp(),之后运行 tearDown(),这样测试用例之间就没有交叉影响。
以下是一个简单的测试例子:
class MathTest : public CppUnit::TestFixture
{
protected:
int m_value1, m_value2;
public:
MathTest() {}
// 初始化函数
void setUp ()
{
m_value1 = 2;
m_value2 = 3;
}
// 测试加法的测试函数
void testAdd ()
{
// 步骤(2),对 fixture 进行操作
int result = m_value1 + m_value2;
// 步骤(3),验证结果是否正确
CPPUNIT_ASSERT( result == 5 );
}
void tearDown()
{}
};
在测试函数中对执行结果的验证成功或者失败直接反应这个测试用例的成功和失败。CppUnit提供了多种验证成功失败的方式:
// 确信condition为真
CPPUNIT_ASSERT(condition)
// 当condition为假时失败, 并打印message
CPPUNIT_ASSERT_MESSAGE(message, condition)
// 当前测试失败, 并打印message
CPPUNIT_FAIL(message)
// 确信两者相等
CPPUNIT_ASSERT_EQUAL(expected, actual)
// 两者不等时为失败,失败的同时打印message
CPPUNIT_ASSERT_EQUAL_MESSAGE(message, expected, actual)
// 当expected和actual之间差大于delta时失败
CPPUNIT_ASSERT_DOUBLES_EQUAL(expected, actual, delta)
3.2 基本数据类型的测试实例
//MathTest.h
#include <cppunit/extensions/HelperMacros.h>
class MathTest : public CPPUNIT_NS::TestFixture
{
// 声明一个TestSuite
CPPUNIT_TEST_SUITE(MathTest);
//添加测试用例testEqual()到TestSuite
CPPUNIT_TEST(testEqual);
//添加测试用例testAdd()到TestSuite
CPPUNIT_TEST(testAdd);
//结束声明TestSuite
CPPUNIT_TEST_SUITE_END();
protected:
int m_value1, m_value2;
public:
void setUp();
void tearDown();
void testAdd();
void testEqual();
};
//MathTest.cpp
#include "MathTest.h"
#include <cppunit/config/SourcePrefix.h>
CPPUNIT_TEST_SUITE_REGISTRATION(MathTest);
//初始化函数
void MathTest::setUp()
{
m_value1 = 2;
m_value2 = 3;
}
//释放函数
void MathTest::tearDown()
{
}
void MathTest::testAdd()
{
double result = m_value1 + m_value2;
CPPUNIT_ASSERT(result == 6);
}
void MathTest::testEqual()
{
CPPUNIT_ASSERT_EQUAL(m_value1, m_value2);
}
//Main.cpp
#include <cppunit/BriefTestProgressListener.h>
#include <cppunit/CompilerOutputter.h>
#include <cppunit/extensions/TestFactoryRegistry.h>
#include <cppunit/TestResult.h>
#include <cppunit/TestResultCollector.h>
#include <cppunit/TestRunner.h>
int main( int argc, char* argv[])
{
// Create the event manager and test controller
CPPUNIT_NS::TestResult controller;
// Add a listener that colllects test result
CPPUNIT_NS::TestResultCollector result;
controller.addListener( &result );
// Add a listener that print dots as test run.
CPPUNIT_NS::BriefTestProgressListener progress;
controller.addListener( &progress );
// Add the top suite to the test runner
CPPUNIT_NS::TestRunner runner;
runner.addTest( CPPUNIT_NS::TestFactoryRegistry::getRegistry().makeTest() );
runner.run(controller );
// Print test in a compiler compatible format.
CPPUNIT_NS::CompilerOutputter outputter( &result, CPPUNIT_NS::stdCout() );
outputter.write();
return result.wasSuccessful() ? 0 : 1;
}
在3.1的基本数据类型测试实例中,MathTest.h主要负责创建了测试类MathTest,该类继承了CPPUNIT_NS::TestFixture,在类中定义了初始化函数setUp、释放函数tearDown和两个测试用例testAdd()、testEqual()。同时,在类中还声明了TestSuite(MathTest),并把测试用例testAdd()和testEqual()添加到TestSuite(MathTest)中。而MathTest.cpp中主要是注册了MathTest这个TestSuite,同时具体化每个类函数。
3.3 对象类型的测试实例
//Money.h
class IncompatibleMoneyError : public std::runtime_error
{
public:
IncompatibleMoneyError() : std::runtime_error( "不合法的 moneys" )
{
}
};
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;
}
bool operator ==( const Money &other ) const
{
return m_amount == other.m_amount && m_currency == other.m_currency;
}
bool operator !=( const Money &other ) const
{
return !(*this == other);
}
Money &operator +=( const Money &other )
{
if ( m_currency != other.m_currency )
{
throw IncompatibleMoneyError();
}
m_amount += other.m_amount;
return *this;
}
private:
double m_amount;
std::string m_currency;
};
inline CPPUNIT_NS::OStream &operator <<( CPPUNIT_NS::OStream &os, const Money &value )
{
return os << "Money< value =" << value.getAmount() << "; currency = " << value.getCurrency() << ">";
}
//MoneyTest.h
#include <cppunit/extensions/HelperMacros.h>
class MoneyTest : public CPPUNIT_NS::TestFixture
{
CPPUNIT_TEST_SUITE(MoneyTest);
CPPUNIT_TEST(testConstructor);
CPPUNIT_TEST(testEqual);
CPPUNIT_TEST(testAdd);
CPPUNIT_TEST(testAddThrow);
CPPUNIT_TEST_SUITE_END();
public:
void setUp();
void tearDown();
void testConstructor();
void testEqual();
void testAdd();
void testAddThrow();
};
//MoneyTest.cpp
#include <cppunit/config/SourcePrefix.h>
#include "Money.h"
#include "MoneyTest.h"
// Registers the fixture into the 'registry'
CPPUNIT_TEST_SUITE_REGISTRATION( MoneyTest );
void MoneyTest::setUp()
{
}
void MoneyTest::tearDown()
{
}
void MoneyTest::testConstructor()
{
const std::string currencyFF( "FF" );
const double longNumber = 1234.5678;
Money money( longNumber, currencyFF );
CPPUNIT_ASSERT_EQUAL( longNumber, money.getAmount() );
CPPUNIT_ASSERT_EQUAL( currencyFF, money.getCurrency() );
}
void MoneyTest::testEqual()
{
const Money money123FF( 123, "FF" );
const Money money123USD( 123, "USD" );
const Money money12FF( 12, "FF" );
const Money money12USD( 12, "USD" );
CPPUNIT_ASSERT( money123FF == money123FF );
CPPUNIT_ASSERT( money12FF != money123FF );
CPPUNIT_ASSERT( money123USD != money123FF );
CPPUNIT_ASSERT( money12USD != money123FF );
}
void MoneyTest::testAdd()
{
const Money money12FF( 12, "FF" );
const Money expectedMoney( 135, "FF" );
Money money( 123, "FF" );
money += money12FF;
CPPUNIT_ASSERT_EQUAL( expectedMoney, money );
}
void MoneyTest::testAddThrow()
{
const Money money123FF( 123, "FF" );
Money money( 123, "USD" );
money += money123FF; // should throw an exception
}
对比上述两个例子可以看到,对象类型的测试会比基本类型数据的测试要稍微复杂一些,但是始终离不开声明测试类、在测试类中声明TestSuite、setUp()、tearDown()以及测试用例函数,并在测试类的实现文件中把TestSuite注册、具体化测试用例函数等过程。