CppUnit Cookbook

全文编译自:http://cppunit.sourceforge.net/doc/lastest/cppunit_cookbook.html

如何确认已写的代码近预想的方案运行?

单步调试 (stepping through a debugger)或是用标准输出语句充斥代码(littering your code with stream output call)是最简单的两种办法,但缺点也显而易见,前者不能自动完成,每次修改代码都要重新进行一遍,后者会使代码和输出同样的混乱。

cppUnit进行测试一是可以自动测试,二是只要写好一次测试用例,测试用例就可以重复使用。下面用一个例子展示单元测试是如此简单有效。

class ComplexNumberTest : public CppUnit::TestCase {
public:
ComplexNumberTest( std::
string name ) : CppUnit::TestCase( name ) {}

void runTest() {
CPPUNIT_ASSERT( Complex (
10, 1) == Complex (10, 1) );
CPPUNIT_ASSERT(
!(Complex (1, 1) == Complex (2, 2)) );
}
};

解释:ComplexNumberTest继承自CppUnit::TestCase,重载了runTest方法,CPPUNIT_ASSERT检验被测的条件。

一般情况下,可能有很多个测试用例在同一组对象的基础上运行,这时使用Fixture.

Fixture多个测试用例共同使用的测试前提条件

Fixture用做运行测试用例的前提条件,即所有的测试用例都在同一组测试条件下运行(读者不必事先了解Fixture的意义,按下面的例子做时就会了解)。Fixture一般是在开发过程中手工编写,一边开发一般写测试代码。下面用这种形式开发,并介绍Fixture的意义。假设我们正在开发一个复数类型,定义如下:

class Complex {};

这时,生成一个ComplexNumberTest的实例,编译代码,这时会出现编译错误,因为没有为ComplexNumber类定义operator ==,所以需做如下定义

class Complex {
friend
bool operator ==(const Complex& a, const Complex& b);
double real, imaginary;
public:
Complex(
double r, double i = 0 )
: real(r) , imaginary(i)
{
}
};

bool operator ==( const Complex &a, const Complex &b )
{
return a.real == b.real && a.imaginary == b.imaginary;
}

编译通过,测试也通过了。

现在开始增加新的方法和新的测试。这是需要Fixture了,当我们做测试时,可能需要多个复数并在多个测试中重用它们。

如下是定义Fixture的方法:(1)为每个Fixture定义变量;(2)重载setUp(),初始化变量;(3)重载tearDown(),释放在setUp中分配的全部资源。如下:

class ComplexNumberTest : public CppUnit::TestFixture {
private:
Complex
*m_10_1, *m_1_1, *m_11_2;
public:
void setUp()
{
m_10_1
= new Complex( 10, 1 );
m_1_1
= new Complex( 1, 1 );
m_11_2
= new Complex( 11, 2 );
}

void tearDown()
{
delete m_10_1;
delete m_1_1;
delete m_11_2;
}
};

TestCase:一个被测试功能点的测试

如何在测试用例中使用Fixture呢?有两个步骤,一是在TestFixture的子类中定义方泷,二是用一个TestCaller调用具体的方法,如下:

class ComplexNumberTest : public CppUnit::TestFixture {
private:
Complex
*m_10_1, *m_1_1, *m_11_2;
public:
void setUp()
{
m_10_1
= new Complex( 10, 1 );
m_1_1
= new Complex( 1, 1 );
m_11_2
= new Complex( 11, 2 );
}

void tearDown()
{
delete m_10_1;
delete m_1_1;
delete m_11_2;
}

void testEquality()
{
CPPUNIT_ASSERT(
*m_10_1 == *m_10_1 );
CPPUNIT_ASSERT(
!(*m_10_1 == *m_11_2) );
}

void testAddition()
{
CPPUNIT_ASSERT(
*m_10_1 + *m_1_1 == *m_11_2 );
}
};

调用这些测试方法如下

CppUnit::TestCaller<ComplexNumberTest> test( "testEquality",
&ComplexNumberTest::testEquality );
CppUnit::TestResult result;
test.run(
&result );

其中TestCaller的第二个参数是成员函数指针,当TestCaller运行时,会调用相应的函数,当然一般并不这样做,这样做不会输出测试结果,一般会用一个TestRunner显示结果。

Suite把多个测试用例组织在一起运行

当有多个测试用例时,组织到一个Suite中,可以一次运行多个测试用例。以下是如何组织一个测试用例

CppUnit::TestSuite suite;
CppUnit::TestResult result;
suite.addTest(
new CppUnit::TestCaller<ComplexNumberTest>(
"testEquality",
&ComplexNumberTest::testEquality ) );
suite.addTest(
new CppUnit::TestCaller<ComplexNumberTest>(
"testAddition",
&ComplexNumberTest::testAddition ) );
suite.run(
&result );

TestSuite不只包含TestCase的调用者,它们可以包含实现Test接口的任何对象,比如TestSuite,如下

CppUnit::TestSuite suite;
CppUnit::TestResult result;
suite.addTest( ComplexNumberTest::suite() );
suite.addTest( SurrealNumberTest::suite() );
suite.run(
&result );

TestRunner:运行测试用例并收集结果

CppUnit提供了运行test suite并显示结果的方法,为test suite写一个静态方法suite用于生成一个test suite的对象,TestRunner就可以调用test suite了。如在ComplexNumberTest中增加方法

public:
static CppUnit::Test *suite()
{
CppUnit::TestSuite
*suiteOfTests = new CppUnit::TestSuite( "ComplexNumberTest" );
suiteOfTests
->addTest( new CppUnit::TestCaller<ComplexNumberTest>(
"testEquality",
&ComplexNumberTest::testEquality ) );
suiteOfTests
->addTest( new CppUnit::TestCaller<ComplexNumberTest>(
"testAddition",
&ComplexNumberTest::testAddition ) );
return suiteOfTests;
}

如要使用文本行形式的TestRunner,需要在main.cpp中包含如下头文件

#include <cppunit/ui/text/TestRunner.h>
#include
"ExampleTestCase.h"
#include
"ComplexNumberTest.h"

并在main.cpp文件中调用TestRunneraddTest(CppUnit::Test *)方法

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

TestRunner会调用这些测试,如果测试通过,会输出一条提示性消息;如果失败,会输出如下几方面的消息

1)失败的测试的名字,即TestSuite::addTest()中指定的名字

2)包含该测试的文件名

3)发生失败的行数

4)包含在CPPUNIT_ASSERT() 中的全部文本

CppUnit区分failureerror。前者是可以预料的,并用断言检查,后者不是预料的问题,比如除零,或C++运行时或其他代码抛出的异常。

Helper Macros

实现Fixture中的静态函数suite()是一项重复性较强,比较容易出错的操作。CppUnit提供了一组宏用于自动实现该方法。如下是用这些宏重写的ComplexNumberTest

#include <cppunit/extensions/HelperMacros.h>
class ComplexNumberTest : public CppUnit::TestFixture {

CPPUNIT_TEST_SUITE( ComplexNumberTest );
CPPUNIT_TEST( testEquality );
CPPUNIT_TEST( testAddition );
CPPUNIT_TEST( testEquality );
CPPUNIT_TEST( testAddition );
CPPUNIT_TEST_SUITE_END();

private:
Complex
*m_10_1, *m_1_1, *m_11_2;
public:
void setUp()
{
m_10_1
= new Complex( 10, 1 );
m_1_1
= new Complex( 1, 1 );
m_11_2
= new Complex( 11, 2 );
}

void tearDown()
{
delete m_10_1;
delete m_1_1;
delete m_11_2;
}

void testEquality()
{
CPPUNIT_ASSERT(
*m_10_1 == *m_10_1 );
CPPUNIT_ASSERT(
!(*m_10_1 == *m_11_2) );
}

void testAddition()
{
CPPUNIT_ASSERT(
*m_10_1 + *m_1_1 == *m_11_2 );
}
};

用这个宏实现suite()时,给每个测试用例的命名为“类名.方法名”,如以上定义中,定义的测试用例的名分别为ComplexNumberTest.testEqualityComplexNumberTest.testAddition

用帮助宏也可以写出断言,如下断言除零时抛出一个MathException 异常。

1)先用CPPUNIT_TEST_EXCEPTION suite中加入一个测试用例,指明要抛出的异常类型

2)写出测试用例,即

CPPUNIT_TEST_SUITE( ComplexNumberTest );
// [...]
CPPUNIT_TEST_EXCEPTION( testDivideByZeroThrows, MathException );
CPPUNIT_TEST_SUITE_END();

// [...]

void testDivideByZeroThrows()
{
// The following line should throw a MathException.
*m_10_1 / ComplexNumber(0);
}

TestFactoryRegistry

TestFactoryRegistry用于解决两个问题,一是忘记将测试组件加入到testRunner中,二是主测试文件中包含全部的测试用例的头文件会在编译阶段带来很大时间开销。所以需要一个所谓的TestFactoryRegistry,思路是在定义测试用例的文件中将全部测试用例注册到一个全局变量中,然后再转交给TestRunner.

如在ComplexNumber的测试用例文件中注意测试组件时,在相应的.cpp文件中增加如下内容

#include <cppunit/extensions/HelperMacros.h>
CPPUNIT_TEST_SUITE_REGISTRATION( ComplexNumberTest );

背后的机制不必关心,无非是生成一个静态变量(只在本编译单元中可见),在其构造函数中向TestFactoryRegistery注册一个TestSuite

要运行这些测试用例,需要如下代码:

#include <cppunit/extensions/TestFactoryRegistry.h>
#include
<cppunit/ui/text/TestRunner.h>
int main( int argc, char **argv)
{
CppUnit::TextUi::TestRunner runner;
CppUnit::TestFactoryRegistry
&registry = CppUnit::TestFactoryRegistry::getRegistry();
runner.addTest( registry.makeTest() );
runner.run();
return 0;
}

Post-build check

在编译完成后自动运行测试用例,一是测试主程序要给出一个结果表示是否发现错误,二是对编译器进行设置,以便自动运行测试用例。所以,需要修改测试主程序为

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

int main( int argc, char **argv)
{
CppUnit::TextUi::TestRunner runner;
CppUnit::TestFactoryRegistry
&registry = CppUnit::TestFactoryRegistry::getRegistry();
runner.addTest( registry.makeTest() );
bool wasSuccessful = runner.run( "", false );
return wasSuccessful;
}

VC中可以设置编译后需要执行的命令,将该命令指定为运行主测试程序。

posted on 2011-02-23 23:07  zhihuichien  阅读(619)  评论(0编辑  收藏  举报

导航