分享
 
 
 

CppUnit Cookbook中文版

王朝other·作者佚名  2006-01-08
窄屏简体版  字體: |||超大  

这是我的第一份完整的翻译文章。欢迎大家指正,联系我:zgump@sina.com

***************************************************************************

本文是一篇简单的入门指导,帮助你快速上手。

简单测试用例(Simple Test Case)

你希望知道你的代码是否正在工作。

你该怎么办?

有很多种方法。使用调试器直接调试或者在你的代码里乱丢一些流输出指令是两种简单的方法,但是它们都有自己的缺点。直接调试代码是个好主意,但是它不是自动进行的。你不得不在每次改动代码以后重新调试。输出流文本也不错,但是它使代码变得面目可憎,并且大多数情况下,它输出的信息比你想要的要多。

在CppUnit中测试可以自动进行。这些测试可以很容易被建立,并且一旦你书写完毕,他们可以帮助你时刻了解你代码的质量。

为了做一个简单的测试,下面这些是你要做的:

从TestClass中派生一个类。Override runTest()方法。当你希望检查一个值的时候,调用 CPPUNIT_ASSERT(bool),如果测试成功这个assert表达式可以被顺利通过。

比如,为了测试一个复数类的等值比较,书写如下:

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)) );

}

};

这就是一个简单的测试。通常来说,你会有很多小的测试用例,并且希望能在同一个对象集合中测试。为了达到这个目的,使用fixture。

Fixture

一个fixture是一组对象,被用来作为测试一组用例的基础。当你边开发边测试的时候,使用fixture会非常方便。

那我们尝试一下这种开发方式,同时学习一下fixture的使用方法。假定我们就是想开发一个复数的类,我们从定义一个名为Complex的空类开始。

class Complex{};

现在建立上面那个ComplexNumberTest测试用例,编译它们看看会发生什么。我们注意到的第一件事是有一些编译错误。测试使用了操作符==,但是它并没有被定义。修改如下:

bool operator==( const Complex & a, const Complex & b )

{

return true;

}

现在再次编译并运行之。这次编译通过了,但是没有通过测试。我们需要再写些代码使操作符==可以正确工作,所以我们再次修改代码:

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 eq( a.real, b.real ) && eq( a.imaginary, b.imaginary );

}

如果我们现在编译并运行,就可以顺利通过测试。

现在我们准备添加一些新的操作符和新的测试用例。这时使用一个fixture会很方便。如果我们实例化3到4个复数并在测试中反复使用它们,可能我们的测试会更好些。

我们这样做:

* 为fixture的每个部分添加成员变量。

* Override setUp() 初始化这些变量。

* Override tearDown()释放你在setUp()中使用的资源。

class ComplexNumberTest : public CppUnit::TestFixture {

private:

Complex *m_10_1, *m_1_1, *m_11_2;

protected:

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;

}

};

一旦我们拥有了这个fixture,我们就可以添加操作符+,以及整个开发过程中其他的任何操作符。

Test Case

为了使用一个fixture来调用单独的测试,该如何做呢?

分为两个步骤:

*以一个method的形式,在fixture类中写一个测试用例

*创建TestCaller来运行那个method

这里是我们加了一些额外的用例method书写的测试类:

private:

Complex *m_10_1, *m_1_1, *m_11_2;

protected:

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的构造函数的第二个参数是ComplexNumberTest中对应method的地址。当这个TestCaller运行的时候,指定的method会运行。但是,这个办法也效果不彰,因为它不显示诊断信息。我们可以使用TestRunner(下面会讲到)来显示这个诊断信息。

一旦我们有了几个测试用例,可以把它们归入一个suite中。

Suite

为了建立多个用例并且让它们一次全部运行,你该如何做呢?

CppUnit提供了一个TestSuite类来同时运行任意个用例。

在上面我们看到了如何运行一个测试用例。

为了创建含有两个或更多用例的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 );

TestSuites不必仅仅含有测试用例的caller.它们可以包含实现Test 接口的任意对象。例如:你可以在你的代码中创建一个TestSuite,我也可以在我的代码里建立一个,我们通过建立一个同时包含它们两个的TestSuite使它们得以同时运行。

CppUnit::TestSuite suite;

CppUnit::TestResult result;

suite.addTest( ComplexNumberTest::suite() );

suite.addTest( SurrealNumberTest::suite() );

suite.run( &result );

TestRunner

你该如何运行你的用例并收集测试结果呢?

一旦你有了一个TestSuite, 你会想运行它。CppUnit提供了定义这些suite并显示结果的工具。你可以通过在一个TestSuite中加入一个名为suite的静态的method使你的suite与TestRunner建立联系。

例如,为了使TestRunner可以看到一个ComplexNumberTest 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;

}

为了使用这个版本,在Main.cpp中包含以下头文件:

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

#include "ExampleTestCase.h"

#include "ComplexNumberTest.h"

然后在main()中加入addTest(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会运行这些用例。如果所有的测试都顺利通过,你会得到一个反馈。如果任何一个测试没有通过,你会得到以下信息:

*失败的测试用例的名字

*包含这个测试源文件的名字

*错误发生的行号

*发现错误的CPPUNIT_ASSERT()调用中的所有文字。

Helper Macros

你可能已经注意到了,实现fixture中的static suite()是一个要反复要做的,并且容易出错的任务。我们可以使用一组写 test fixture的宏来自动执行这些静态的suite method.

下面是使用这些宏重写了类ComplexNumberTest后的代码:

#include <cppunit/extensions/HelperMacros.h>

class ComplexNumberTest : public CppUnit::TestFixture {

首先我们声明这个suite,把这个类的名字传递给宏:

CPPUNIT_TEST_SUITE( ComplexNumberTest );

这个使用静态的suite() method建立的suite以类的名字来命名。然后,我们为每个测试用例声明:

CPPUNIT_TEST( testEquality );

CPPUNIT_TEST( testAddition );

最后,我们结束这个suite声明:

CPPUNIT_TEST_SUITE_END();

在这里下面这个method已经被实现了:

static CppUnit::TestSuite *suite();

剩下的fixture保持不动:

private:

Complex *m_10_1, *m_1_1, *m_11_2;

protected:

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的TestCaller的名字是这个fixture名字和method名字的组合。

对目前这个用例来说,名字会是"ComplexNumberTest.testEquality" 和"ComplexNumberTest.testAddition".

helper macros帮你写些常用的断言。例如。检查当一个数被零除的时候ComplexNumber是否会抛出MathException异常:

*把这个测试用例加入使用CPPUNIT_TEST_EXCEPTION的suite中,指定希望的异常的类型。

*写这个测试用例的method

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是用来解决以下两个缺陷的:

*忘了把你的fixture suite加入test runner(因为它在另外一个文件中,很容易忘)

*因为加入所有测试用例头文件造成的编译瓶颈。

TestFactoryRegistry是在初始化的时候注册suite的地方。

为了注册ComplexNumber suite,在.cpp中加入:

#include <cppunit/extensions/HelperMacros.h>

CPPUNIT_TEST_SUITE_REGISTRATION( ComplexNumber );

事实上,桌面下的动作是,一个静态的AutoRegisterSuite类型变量被声明。在构建的时候,它会注册一个TestSuiteFactory到TestFactoryRegistry。 TestSuiteFactory返回ComplexNumber::suite()返回的TestSuite。

为了运行这些用例,使用文字的test runner,我们不必包含fixture了:

#include <cppunit/extensions/TestFactoryRegistry.h>

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

int main( int argc, char **argv)

{

CppUnit::TextUi::TestRunner runner;

首先我们得到TestFactoryRegistry的实例:

CppUnit::TestFactoryRegistry &registry = CppUnit::TestFactoryRegistry::getRegistry();

然后我们获得并添加一个由TestFactoryRegistry产生的新的TestSuite,它包含了使用CPPUNIT_TEST_SUITE_REGISTRATION()注册的所有的test suite.

runner.addTest( registry.makeTest() );

runner.run();

return 0;

}

Post-build check

好了,现在我们已经可以使测试运行了,那么把它集成到编译过程中去怎么样?

为了达到这个目的,应用程序必须返回一个非0值表明出现了错误。

TestRunner::run()返回一个布尔值来表明run()是否成功。

更新一下我们的main函数,我们得到:

#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 wasSucessful = runner.run( "", false );

return wasSucessful;

}

现在,你需要编译后运行你的应用程序。

使用 Visual C++的话,可以在Project Settings/Post-Build step中加入下面的命令。它被扩展到应用程序的执行路径。使用这个技术的时候看看project examples/cppunittest/CppUnitTestMain.dsp 中是如何设置的。

Original version by Michael Feathers. Doxygen conversion and update by Baptiste Lepilleur.

 
 
 
免责声明:本文为网络用户发布,其观点仅代表作者个人观点,与本站无关,本站仅提供信息存储服务。文中陈述内容未经本站证实,其真实性、完整性、及时性本站不作任何保证或承诺,请读者仅作参考,并请自行核实相关内容。
2023年上半年GDP全球前十五强
 百态   2023-10-24
美众议院议长启动对拜登的弹劾调查
 百态   2023-09-13
上海、济南、武汉等多地出现不明坠落物
 探索   2023-09-06
印度或要将国名改为“巴拉特”
 百态   2023-09-06
男子为女友送行,买票不登机被捕
 百态   2023-08-20
手机地震预警功能怎么开?
 干货   2023-08-06
女子4年卖2套房花700多万做美容:不但没变美脸,面部还出现变形
 百态   2023-08-04
住户一楼被水淹 还冲来8头猪
 百态   2023-07-31
女子体内爬出大量瓜子状活虫
 百态   2023-07-25
地球连续35年收到神秘规律性信号,网友:不要回答!
 探索   2023-07-21
全球镓价格本周大涨27%
 探索   2023-07-09
钱都流向了那些不缺钱的人,苦都留给了能吃苦的人
 探索   2023-07-02
倩女手游刀客魅者强控制(强混乱强眩晕强睡眠)和对应控制抗性的关系
 百态   2020-08-20
美国5月9日最新疫情:美国确诊人数突破131万
 百态   2020-05-09
荷兰政府宣布将集体辞职
 干货   2020-04-30
倩女幽魂手游师徒任务情义春秋猜成语答案逍遥观:鹏程万里
 干货   2019-11-12
倩女幽魂手游师徒任务情义春秋猜成语答案神机营:射石饮羽
 干货   2019-11-12
倩女幽魂手游师徒任务情义春秋猜成语答案昆仑山:拔刀相助
 干货   2019-11-12
倩女幽魂手游师徒任务情义春秋猜成语答案天工阁:鬼斧神工
 干货   2019-11-12
倩女幽魂手游师徒任务情义春秋猜成语答案丝路古道:单枪匹马
 干货   2019-11-12
倩女幽魂手游师徒任务情义春秋猜成语答案镇郊荒野:与虎谋皮
 干货   2019-11-12
倩女幽魂手游师徒任务情义春秋猜成语答案镇郊荒野:李代桃僵
 干货   2019-11-12
倩女幽魂手游师徒任务情义春秋猜成语答案镇郊荒野:指鹿为马
 干货   2019-11-12
倩女幽魂手游师徒任务情义春秋猜成语答案金陵:小鸟依人
 干货   2019-11-12
倩女幽魂手游师徒任务情义春秋猜成语答案金陵:千金买邻
 干货   2019-11-12
 
推荐阅读
 
 
 
>>返回首頁<<
 
靜靜地坐在廢墟上,四周的荒凉一望無際,忽然覺得,淒涼也很美
© 2005- 王朝網路 版權所有