随笔 - 298  文章 - 377  trackbacks - 0
<2008年6月>
25262728293031
1234567
891011121314
15161718192021
22232425262728
293012345

常用链接

留言簿(34)

随笔分类

随笔档案

文章档案

相册

收藏夹

搜索

  •  

最新评论

阅读排行榜

评论排行榜

[文章来源]

           引用

[文章内容]

简介

测试是软件开发过程中极其重要的一环,详尽周密的测试能够减少软件BUG,提高软件品质。测试包括单元测试、系统测试等。其中单元测试是指针对软件功能单元所作的测试,这里的功能单元可以是一个类的属性或者方法,测试的目的是看这些基本单元是否工作正常。由于单元测试的内容很基础,因此可以看作是测试工作的第一环,该项工作一般由开发人员自行完成。如果条件允许,单元测试代码的开发应与程序代码的开发同步进行。

虽然不同程序的单元测试代码不尽相同,但测试代码的框架却非常相似,于是便出现了一些单元测试类库,CppUnit便是其中之一。

CppUnit是XUnit中的一员,XUnit是一个大家族,还包括JUnit和PythonUnit等。CppUnit简单实用,学习和使用起来都很方便,网上已有一些文章对其作介绍,但本文更着重于讲解其中的基本概念和使用方法,以帮助初次接触CppUnit的人员快速入门。

安装

目前,CppUnit的最新版本是1.10.2,你可以从下面地址获取:

http://sourceforge.net/projects/cppunit

解压后,你可以看到CppUnit包含如下目录:

config:  配置文件    contrib: contribution,其他人贡献的外围代码    doc:     文档,需要通过doxygen工具生成,也可以直接从sourceforge站点上下载打包好的文档    examples:示例代码    include: 头文件    lib:     存放编译好的库    src:     源文件,以及编译库的工程等

然后打开src目录下的CppUnitLibraries工程,执行build/batch build,编译成功的话,生成的库文件将被拷贝到lib目录下。

你也可以根据需要选择所需的项目进行编译,其中项目cppunit为静态库,cppunit_dll为动态库,生成的库文件为:

cppunit.lib:     静态库release版    cppunitd.lib:    静态库debug版    cppunit_dll.lib: 动态库release版    cppunitd_dll.lib:动态库debug版

要使用CppUnit,还得设置好头文件和库文件路径,以VC6为例,选择Tools/Options/Directories,在Include files和Library files中分别添加%CppUnitPath%\include和%CppUnitPath%\lib,其中%CppUnitPath%表示CppUnit所在路径。

做好准备工作后,我们就可以编写自己的单元测试代码了。需说明的是,CppUnit所用的动态运行期库均为多线程动态库,因此你的单元测试程序也得使用相应设置,否则会发生冲突。

概念

在使用之前,我们有必要认识一下CppUnit中的主要类,当然你也可以先看后面的例子,遇到问题再回过头来看这一节。

CppUnit核心内容主要包括六个方面,

1. 测试对象(Test,TestFixture,...):用于开发测试用例,以及对测试用例进行组织管理。

2. 测试结果(TestResult):处理测试用例执行结果。TestResult与下面的TestListener采用的是观察者模式(Observer Pattern)。

3. 测试结果监听者(TestListener):TestListener作为TestResult的观察者,担任实际的结果处理角色。

4. 结果输出(Outputter):将结果进行输出,可以制定不同的输出格式。

5. 对象工厂(TestFactory):用于创建测试对象,对测试用例进行自动化管理。

6. 测试执行体(TestRunner):用于运行一个测试。

以上各模块的主要类继承结构如下:

Test              TestFixture      TestResult          TestListener             _______|_________            |                                    |                  |               |            |                           TestSuccessListener    TestComposite   TestLeaf         |                                    |                  |               |____________|                           TestResultCollector              TestSuit                  |                           TestCase                                                   |                      TestCaller<Fixture>                                              Outputter                                    TestFactory                    TestRunner        ____________________|_________________                            |        |                   |                |                   TestFactoryRegistry    CompilerOutputter  TextOutputter    XmlOutputter                      |                                                             TestSuiteFactory<TestCaseType>

接下来再对其中一些关键类作以介绍。

Test:所有测试对象的基类。

CppUnit采用树形结构来组织管理测试对象(类似于目录树),因此这里采用了组合设计模式(Composite Pattern),Test的两个直接子类TestLeaf和TestComposite分别表示“测试树”中的叶节点和非叶节点,其中TestComposite主要起组织管理的作用,就像目录树中的文件夹,而TestLeaf才是最终具有执行能力的测试对象,就像目录树中的文件。

Test最重要的一个公共接口为:

virtual void run(TestResult *result) = 0;

其作用为执行测试对象,将结果提交给result。

在实际应用中,我们一般不会直接使用Test、TestComposite以及TestLeaf,除非我们要重新定制某些机制。

TestFixture:用于维护一组测试用例的上下文环境。

在实际应用中,我们经常会开发一组测试用例来对某个类的接口加以测试,而这些测试用例很可能具有相同的初始化和清理代码。为此,CppUnit引入TestFixture来实现这一机制。

TestFixture具有以下两个接口,分别用于处理测试环境的初始化与清理工作:

virtual void setUp();
virtual void tearDown();

TestCase:测试用例,从名字上就可以看出来,它便是单元测试的执行对象。

TestCase从Test和TestFixture多继承而来,通过把Test::run制定成模板函数(Template Method)而将两个父类的操作融合在一起,run函数的伪定义如下:

// 伪代码
void TestCase::run(TestResult* result)
{
     result->startTest(this); // 通知result测试开始
    if( result->protect(this, &TestCase::setUp) ) // 调用setUp,初始化环境
         result->protect(this, &TestCase::runTest); // 执行runTest,即真正的测试代码
     result->protect(this, &TestCase::tearDown); // 调用tearDown,清理环境
     result->endTest(this); // 通知result测试结束
}

这里要提到的是函数runTest,它是TestCase定义的一个接口,原型如下:

virtual void runTest();

用户需从TestCase派生出子类并实现runTest以开发自己所需的测试用例。

另外还要提到的就是TestResult的protect方法,其作用是对执行函数(实际上是函数对象)的错误信息(包括断言和异常等)进行捕获,从而实现对测试结果的统计。

TestSuit:测试包,按照树形结构管理测试用例

TestSuit是TestComposite的一个实现,它采用vector来管理子测试对象(Test),从而形成递归的树形结构。

TestCaller:TestCase适配器(Adapter),它将成员函数转换成测试用例

虽然我们可以从TestCase派生自己的测试类,但从TestCase类的定义可以看出,它只能支持一个测试用例,这对于测试代码的组织和维护很不方便,尤其是那些有共同上下文环境的一组测试。为此,CppUnit提供了TestCaller以解决这个问题。

TestCaller是一个模板类,它以实现了TestFixture接口的类为模板参数,将目标类中某个符合runTest原型的测试方法适配成TestCase的子类。

在实际应用中,我们大多采用TestFixture和TestCaller相组合的方式,具体例子参见后文。

TestResult和TestListener:处理测试信息和结果

前面已经提到,TestResult和TestListener采用了观察者模式,TestResult维护一个注册表,用于管理向其登记过的TestListener,当TestResult收到测试对象(Test)的测试信息时,再一一分发给它所管辖的TestListener。这一设计有助于实现对同一测试的多种处理方式。

TestFactory:测试工厂

这是一个辅助类,通过借助一系列宏定义让测试用例的组织管理变得自动化。参见后面的例子。

TestRunner:用于执行测试用例

TestRunner将待执行的测试对象管理起来,然后供用户调用。其接口为:

virtual void addTest( Test *test ); virtual void run( TestResult &controller, const std::string &testPath = "" );

这也是一个辅助类,需注意的是,通过addTest添加到TestRunner中的测试对象必须是通过new动态创建的,用户不能删除这个对象,因为TestRunner将自行管理测试对象的生命期。

使用

先让我们看看一个简单的例子:

#include <cppunit/TestCase.h>
#include <cppunit/TestResult.h>
#include <cppunit/TestResultCollector.h>
#include <cppunit/TextOutputter.h>

// 定义测试用例
class SimpleTest : public CppUnit::TestCase
{
public:
    void runTest() // 重载测试方法
     {
        int i = 1;
         CPPUNIT_ASSERT_EQUAL(0, i);
     }
};

int main(int argc, char* argv[])
{
     CppUnit::TestResult r;
     CppUnit::TestResultCollector rc;
     r.addListener(&rc); // 准备好结果收集器

     SimpleTest t;
     t.run(&r); // 运行测试用例

     CppUnit::TextOutputter o(&rc, std::cout);
     o.write(); // 将结果输出

    return 0;
}

编译后运行,输出结果为:

!!!FAILURES!!!
Test Results:
Run: 1 Failures: 1 Errors: 0

1) test: (F) line: 18 E:\CppUnitExamples\SimpleTest.cpp
equality assertion failed
- Expected: 1
- Actual : 0

上面的例子很简单,需说明的是CPPUNIT_ASSERT_EQUAL宏。CppUnit定义了一组宏用于检测错误,CPPUNIT_ASSERT_EQUAL是其中之一,当断言失败时,CppUnit便会将错误信息报告给TestResult。这些宏定义的说明如下:

CPPUNIT_ASSERT(condition):判断condition的值是否为真,如果为假则生成错误信息。

CPPUNIT_ASSERT_MESSAGE(message, condition):与CPPUNIT_ASSERT类似,但结果为假时报告messsage信息。

CPPUNIT_FAIL(message):直接报告messsage错误信息。

CPPUNIT_ASSERT_EQUAL(expected, actual):判断expected和actual的值是否相等,如果不等输出错误信息。

CPPUNIT_ASSERT_EQUAL_MESSAGE(message, expected, actual):与CPPUNIT_ASSERT_EQUAL类似,但断言失败时输出message信息。

CPPUNIT_ASSERT_DOUBLES_EQUAL(expected, actual, delta):判断expected与actual的偏差是否小于delta,用于浮点数比较。

CPPUNIT_ASSERT_THROW(expression, ExceptionType):判断执行表达式expression后是否抛出ExceptionType异常。

CPPUNIT_ASSERT_NO_THROW(expression):断言执行表达式expression后无异常抛出。

接下来再看看TestFixture和TestCaller的组合使用:

#include <cppunit/TestCase.h>
#include <cppunit/TestResult.h>
#include <cppunit/TestResultCollector.h>
#include <cppunit/TextOutputter.h>
#include <cppunit/TestCaller.h>
#include <cppunit/TestRunner.h>

// 定义测试类
class StringTest : public CppUnit::TestFixture
{
public:
    void setUp() // 初始化
     {
         m_str1 = "Hello, world";
         m_str2 = "Hi, cppunit";
     }

    void tearDown() // 清理
     {
     }

    void testSwap() // 测试方法1
     {
        std::string str1 = m_str1;
        std::string str2 = m_str2;
         m_str1.swap(m_str2);
        
         CPPUNIT_ASSERT(m_str1 == str2);
         CPPUNIT_ASSERT(m_str2 == str1);
     }

    void testFind() // 测试方法2
     {
        int pos1 = m_str1.find(',');
        int pos2 = m_str2.rfind(',');

         CPPUNIT_ASSERT_EQUAL(5, pos1);
         CPPUNIT_ASSERT_EQUAL(2, pos2);
     }

protected:
    std::string      m_str1;
    std::string      m_str2;
};

int main(int argc, char* argv[])
{
     CppUnit::TestResult r;
     CppUnit::TestResultCollector rc;
     r.addListener(&rc); // 准备好结果收集器

     CppUnit::TestRunner runner; // 定义执行实体
     runner.addTest(new CppUnit::TestCaller<StringTest>("testSwap", &StringTest::testSwap)); // 构建测试用例1
     runner.addTest(new CppUnit::TestCaller<StringTest>("testFind", &StringTest::testFind)); // 构建测试用例2
     runner.run(r); // 运行测试

     CppUnit::TextOutputter o(&rc, std::cout);
     o.write(); // 将结果输出

    return rc.wasSuccessful() ? 0 : -1;
}

编译后运行结果为:

OK (2 tests)

上面的代码从功能上讲没有什么问题,但编写起来太繁琐了,为此,我们可以借助CppUnit定义的一套辅助宏,将测试用例的定义和注册变得自动化。上面的代码改造后如下:

#include <cppunit/TestResult.h>
#include <cppunit/TestResultCollector.h>
#include <cppunit/TextOutputter.h>
#include <cppunit/TestRunner.h>
#include <cppunit/extensions/HelperMacros.h>


// 定义测试类
class StringTest : public CppUnit::TestFixture
{
     CPPUNIT_TEST_SUITE(StringTest);  // 定义测试包
     CPPUNIT_TEST(testSwap);  // 添加测试用例1
     CPPUNIT_TEST(testFind);  // 添加测试用例2
     CPPUNIT_TEST_SUITE_END();  // 结束测试包定义
    
public:
    void setUp() // 初始化
     {
         m_str1 = "Hello, world";
         m_str2 = "Hi, cppunit";
     }

    void tearDown() // 清理
     {
     }

    void testSwap() // 测试方法1
     {
        std::string str1 = m_str1;
        std::string str2 = m_str2;
         m_str1.swap(m_str2);
        
         CPPUNIT_ASSERT(m_str1 == str2);
         CPPUNIT_ASSERT(m_str2 == str1);
     }

    void testFind() // 测试方法2
     {
        int pos1 = m_str1.find(',');
        int pos2 = m_str2.rfind(',');

         CPPUNIT_ASSERT_EQUAL(5, pos1);
         CPPUNIT_ASSERT_EQUAL(2, pos2);
     }

protected:
    std::string      m_str1;
    std::string      m_str2;
};

CPPUNIT_TEST_SUITE_REGISTRATION(StringTest); // 自动注册测试包

int main(int argc, char* argv[])
{
     CppUnit::TestResult r;
     CppUnit::TestResultCollector rc;
     r.addListener(&rc); // 准备好结果收集器

     CppUnit::TestRunner runner; // 定义执行实体
     runner.addTest(CppUnit::TestFactoryRegistry::getRegistry().makeTest());
     runner.run(r); // 运行测试

     CppUnit::TextOutputter o(&rc, std::cout);
     o.write(); // 将结果输出

    return rc.wasSuccessful() ? 0 : -1;
}

CppUnit的简单介绍就到此,相信你已经了解了其中的基本概念,也能够开发单元测试代码了。

posted on 2008-06-18 12:21 聂文龙 阅读(2533) 评论(6)  编辑 收藏 引用

FeedBack:
# re: 【CppUnit】快速入门 2008-06-18 12:33 聂文龙
如果以上编译安装没有出错。CPPUNIT会在系统的/usr/local/bin下安装cppunit-config文件。可以运行如下命令进行验证。

查看版本信息

$cppunit-config --version

查看编译标志

$cppunit-config --cflags

查看链接标志

$cppunit-config --libs
  回复  更多评论
  
# re: 【CppUnit】快速入门 2008-06-18 15:53 聂文龙

便利的开发工具 CppUnit 快速使用指南

CppUnit 是个基于 LGPL 的开源项目,最初版本移植自 JUnit,是一个非常优秀的开源测试框架。CppUnit 和 JUnit 一样主要思想来源于极限编程(XProgramming)。主要功能就是对单元测试进行管理,并可进行自动化测试。这样描述可能没有让您体会到测试框架的强大威力,那您在开发过程中遇到下列问题吗?如果答案是肯定的,就应该学习使用这种技术:

测试代码没有很好地维护而废弃,再次需要测试时还需要重写;
投入太多的精力,找 bug,而新的代码仍然会出现类似 bug;
写完代码,心里没底,是否有大量 bug 等待自己;
新修改的代码不知道是否影响其他部分代码;
由于牵扯太多,导致不敢进行修改代码;
...
这些问题下文都会涉及。这个功能强大的测试框架在国内的 C++ 语言开发人员中使用的不是很多。本文从开发人员的角度,介绍这个框架,希望能够使开发人员用最少的代价尽快掌握这种技术。下面从基本原理,CppUnit 原理,手动使用步骤,通常使用步骤,其他实际问题等方面进行讨论。以下讨论基于 CppUnit1.8.0。

1. 基本原理

对于上面的问题仅仅说明 CppUnit 的使用是没有效果的,下面先从测试的目的,测试原则等方面简要说明,然后介绍 CppUnit 的具体使用。

首先要明确我们写测试代码的目的,就是验证代码的正确性或者调试 bug。这样写测试代码时就有了针对性,对那些容易出错的,易变的编写测试代码;而不用对每个细节,每个功能编写测试代码,当然除非有过量精力或者可靠性要求。

编码和测试的关系是密不可分的,推荐的开发过程并不要等编写完所有或者很多的代码后再进行测试,而是在完成一部分代码,比如一个函数,之后立刻编写测试代码进行验证。然后再写一些代码,再写测试。每次测试对所有以前的测试都进行一遍。这样做的优点就是,写完代码,也基本测试完一遍,心里对代码有信心。而且在写新代码时不断地测试老代码,对其他部分代码的影响能够迅速发现、定位。不断编码测试的过程也就是对测试代码维护的过程,以便测试代码一直是有效的。有了各个部分测试代码的保证,有了自动测试的机制,更改以前的代码没有什么顾虑了。在极限编程(一种软件开发思想)中,甚至强调先写测试代码,然后编写符合测试代码的代码,进而完成整个软件。

根据上面说的目的、思想,下面总结一下平时开发过程中单元测试的原则:

先写测试代码,然后编写符合测试的代码。至少做到完成部分代码后,完成对应的测试代码;
测试代码不需要覆盖所有的细节,但应该对所有主要的功能和可能出错的地方有相应的测试用例;
发现 bug,首先编写对应的测试用例,然后进行调试;
不断总结出现 bug 的原因,对其他代码编写相应测试用例;
每次编写完成代码,运行所有以前的测试用例,验证对以前代码影响,把这种影响尽早消除;
不断维护测试代码,保证代码变动后通过所有测试;
有上面的理论做指导,测试行为就可以有规可循。那么 CppUnit 如何实现这种测试框架,帮助我们管理测试代码,完成自动测试的?下面就看看 CppUnit 的原理。

2. CppUnit 的原理

在 CppUnit 中,一个或一组测试用例的测试对象被称为 Fixture(设施,下文为方便理解尽量使用英文名称)。Fixture 就是被测试的目标,可能是一个对象或者一组相关的对象,甚至一个函数。

有了被测试的 fixture,就可以对这个 fixture 的某个功能、某个可能出错的流程编写测试代码,这样对某个方面完整的测试被称为TestCase(测试用例)。通常写一个 TestCase 的步骤包括:

对 fixture 进行初始化,及其他初始化操作,比如:生成一组被测试的对象,初始化值;
按照要测试的某个功能或者某个流程对 fixture 进行操作;
验证结果是否正确;
对 fixture 的及其他的资源释放等清理工作。
对 fixture 的多个测试用例,通常(1)(4)部分代码都是相似的,CppUnit 在很多地方引入了 setUp 和 tearDown 虚函数。可以在 setUp 函数里完成(1)初始化代码,而在 tearDown 函数中完成(4)代码。具体测试用例函数中只需要完成(2)(3)部分代码即可,运行时 CppUnit 会自动为每个测试用例函数运行 setUp,之后运行 tearDown,这样测试用例之间就没有交叉影响。

对 fixture 的所有测试用例可以被封装在一个 CppUnit::TestFixture 的子类(命名惯例是[ClassName]Test)中。然后定义这个fixture 的 setUp 和 tearDown 函数,为每个测试用例定义一个测试函数(命名惯例是 testXXX)。下面是个简单的例子:


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 );
}
// 没有什么清理工作没有定义 tearDown.
}



在测试函数中对执行结果的验证成功或者失败直接反应这个测试用例的成功和失败。CppUnit 提供了多种验证成功失败的方式:


CPPUNIT_ASSERT(condition) // 确信condition为真
CPPUNIT_ASSERT_MESSAGE(message, condition) // 当condition为假时失败, 并打印message
CPPUNIT_FAIL(message) // 当前测试失败, 并打印message
CPPUNIT_ASSERT_EQUAL(expected, actual) // 确信两者相等
CPPUNIT_ASSERT_EQUAL_MESSAGE(message, expected, actual) // 失败的同时打印message
CPPUNIT_ASSERT_DOUBLES_EQUAL(expected, actual, delta) // 当expected和actual之间差大于delta时失败



要把对 fixture 的一个测试函数转变成一个测试用例,需要生成一个 CppUnit::TestCaller 对象。而最终运行整个应用程序的测试代码的时候,可能需要同时运行对一个 fixture 的多个测试函数,甚至多个 fixture 的测试用例。CppUnit 中把这种同时运行的测试案例的集合称为 TestSuite。而 TestRunner 则运行测试用例或者 TestSuite,具体管理所有测试用例的生命周期。目前提供了 3 类TestRunner,包括:


CppUnit::TextUi::TestRunner // 文本方式的TestRunner
CppUnit::QtUi::TestRunner // QT方式的TestRunner
CppUnit::MfcUi::TestRunner // MFC方式的TestRunner



下面是个文本方式 TestRunner 的例子:


CppUnit::TextUi::TestRunner runner;
CppUnit::TestSuite *suite= new CppUnit::TestSuite();

// 添加一个测试用例
suite->addTest(new CppUnit::TestCaller<MathTest> (
"testAdd", testAdd));

// 指定运行TestSuite
runner.addTest( suite );
// 开始运行, 自动显示测试进度和测试结果
runner.run( "", true ); // Run all tests and wait



对测试结果的管理、显示等功能涉及到另一类对象,主要用于内部对测试结果、进度的管理,以及进度和结果的显示。这里不做介绍。

下面我们整理一下思路,结合一个简单的例子,把上面说的思路串在一起。

3. 手动使用步骤

首先要明确测试的对象 fixture,然后根据其功能、流程,以及以前的经验,确定测试用例。这个步骤非常重要,直接关系到测试的最终效果。当然增加测试用例的过程是个阶段性的工作,开始完成代码后,先完成对功能的测试用例,保证其完成功能;然后对可能出错的部分,结合以前的经验(比如边界值测试、路径覆盖测试等)编写测试用例;最后在发现相关 bug 时,根据 bug 完成测试用例。

比如对整数加法进行测试,首先定义一个新的 TestFixture 子类,MathTest,编写测试用例的测试代码。后期需要添加新的测试用例时只需要添加新的测试函数,根据需要修改 setUp 和 tearDown 即可。如果需要对新的 fixture 进行测试,定义新的 TestFixture 子类即可。注:下面代码仅用来表示原理,不能编译。


/// MathTest.h
// A TestFixture subclass.
// Announce: use as your owner risk.
// Author : liqun (liqun@nsfocus.com)
// Data : 2003-7-5

#include "cppunit/TestFixture.h"


class MathTest : public CppUnit::TestFixture {
protected:
int m_value1, m_value2;

public:
MathTest() {}

// 初始化函数
void setUp ();
// 清理函数
void tearDown();

// 测试加法的测试函数
void testAdd ();
// 可以添加新的测试函数
};

/// MathTest.cpp
// A TestFixture subclass.
// Announce: use as your owner risk.
// Author : liqun (liqun@nsfocus.com)
// Data : 2003-7-5

#include "MathTest.h"
#include "cppunit/TestAssert.h"

void MathTest::setUp()
{
m_value1 = 2;
m_value2 = 3;
}

void MathTest::tearDown()
{

}

void MathTest::testAdd()
{
int result = m_value1 + m_value2;
CPPUNIT_ASSERT( result == 5 );
}



然后编写 main 函数,把需要测试的测试用例组织到 TestSuite 中,然后通过 TestRuner 运行。这部分代码后期添加新的测试用例时需要改动的不多。只需要把新的测试用例添加到 TestSuite 中即可。


/// main.cpp
// Main file for cppunit test.
// Announce: use as your owner risk.
// Author : liqun (liqun@nsfocus.com)
// Data : 2003-7-5
// Note : Cannot compile, only for study.
#include "MathTest.h"
#include "cppunit/ui/text/TestRunner.h"
#include "cppunit/TestCaller.h"
#include "cppunit/TestSuite.h"

int main()
{
CppUnit::TextUi::TestRunner runner;
CppUnit::TestSuite *suite= new CppUnit::TestSuite();

// 添加一个测试用例
suite->addTest(new CppUnit::TestCaller<MathTest> (
"testAdd", testAdd));

// 指定运行TestSuite
runner.addTest( suite );
// 开始运行, 自动显示测试进度和测试结果
runner.run( "", true ); // Run all tests and wait
}



4. 常用使用方式

按照上面的方式,如果要添加新的测试用例,需要把每个测试用例添加到 TestSuite 中,而且添加新的 TestFixture 需要把所有头文件添加到 main.cpp 中,比较麻烦。为此 CppUnit 提供了 CppUnit::TestSuiteBuilder,CppUnit::TestFactoryRegistry 和一堆宏,用来方便地把 TestFixture 和测试用例注册到 TestSuite 中。下面就是通常的使用方式:


/// MathTest.h
// A TestFixture subclass.
// Announce: use as your owner risk.
// Author : liqun (liqun@nsfocus.com)
// Data : 2003-7-5

#include "cppunit/extensions/HelperMacros.h"


class MathTest : public CppUnit::TestFixture {
// 声明一个TestSuite
CPPUNIT_TEST_SUITE( MathTest );
// 添加测试用例到TestSuite, 定义新的测试用例需要在这儿声明一下
CPPUNIT_TEST( testAdd );
// TestSuite声明完成
CPPUNIT_TEST_SUITE_END();
// 其余不变

protected:
int m_value1, m_value2;

public:
MathTest() {}

// 初始化函数
void setUp ();
// 清理函数
void tearDown();

// 测试加法的测试函数
void testAdd ();
// 可以添加新的测试函数
};

/// MathTest.cpp
// A TestFixture subclass.
// Announce: use as your owner risk.
// Author : liqun (liqun@nsfocus.com)
// Data : 2003-7-5

#include "MathTest.h"

// 把这个TestSuite注册到名字为"alltest"的TestSuite中, 如果没有定义会自动定义
// 也可以CPPUNIT_TEST_SUITE_REGISTRATION( MathTest );注册到全局的一个未命名的TestSuite中.
CPPUNIT_TEST_SUITE_NAMED_REGISTRATION( MathTest, "alltest" );

// 下面不变

void MathTest::setUp()
{
m_value1 = 2;
m_value2 = 3;
}

void MathTest::tearDown()
{

}

void MathTest::testAdd()
{
int result = m_value1 + m_value2;
CPPUNIT_ASSERT( result == 5 );
}


/// main.cpp
// Main file for cppunit test.
// Announce: use as your owner risk.
// Compile : g++ -lcppunit MathTest.cpp main.cpp
// Run : ./a.out
// Test : RedHat 8.0 CppUnit1.8.0
// Author : liqun ( a litthle modification. liqun@nsfocus.com)
// Data : 2003-7-5

// 不用再包含所有TestFixture子类的头文件
#include <cppunit/extensions/TestFactoryRegistry.h>
#include <cppunit/ui/text/TestRunner.h>

// 如果不更改TestSuite, 本文件后期不需要更改.

int main()
{
CppUnit::TextUi::TestRunner runner;

// 从注册的TestSuite中获取特定的TestSuite, 没有参数获取未命名的TestSuite.
CppUnit::TestFactoryRegistry &registry = CppUnit::TestFactoryRegistry::getRegistry("alltest");
// 添加这个TestSuite到TestRunner中
runner.addTest( registry.makeTest() );
// 运行测试
runner.run();
}



这样添加新的测试用例只需要在类定义的开始声明一下即可。

5. 其他实际问题

通常包含测试用例代码和被测试对象是在不同的项目中。应该在另一个项目(最好在不同的目录)中编写 TestFixture,然后把被测试的对象包含在测试项目中。

对某个类或者某个函数进行测试的时候,这个 TestFixture 可能引用了别的类或者别的函数,为了隔离其他部分代码的影响,应该在源文件中临时定义一些桩程序,模拟这些类或者函数。这些代码可以通过宏定义在测试项目中有效,而在被测试的项目中无效。
  回复  更多评论
  
# re: 【CppUnit】快速入门 2008-06-18 16:20 末日之刃
很清楚,支持!  回复  更多评论
  
# re: 【CppUnit】快速入门 2008-06-18 16:27 聂文龙
你可以链接静态库也可以链接静态库。
(a) 链接静态库。编译命令:
g++ -L/usr/local/lib/libcppunit.a mytest.cpp -lcppunit -ldl -o mytest

运行:
./mytest

结果:
Test::testHelloWorldHello, world!
: OK

(b) 链接动态库。编译命令:
g++ mytest.cpp -lcppunit -ldl -o mytest

运行:
./mytest
结果:
Test::testHelloWorldHello, world!
: OK

如果你没有执行步骤(5),那么你也可以在每次运行之前设置下临时的环境变量LD_LIBRARY_PATH命令如下:
export LD_LIBRARY_PATH=/usr/local/lib:$LD_LIBRARY_PATH  回复  更多评论
  
# re: 【CppUnit】快速入门 2008-06-18 17:46 聂文龙
#include <cppunit/extensions/TestFactoryRegistry.h>
#include <cppunit/CompilerOutputter.h>
#include <cppunit/ui/text/TestRunner.h>
#include <cppunit/TestFixture.h>
#include <cppunit/extensions/HelperMacros.h>


#include "complex.h"
class complexNumberTest : public CppUnit::TestFixture {
private:
long_double_complex *m_10_1,*m_1_1,*m_11_2;
protected:
void setUp()
{
m_10_1 = new long_double_complex( 10, 1 );
m_1_1 = new long_double_complex( 1, 1 );
m_11_2 = new long_double_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) );
}
public:
complexNumberTest(){}
/* void testAddition()
{
CPPUNIT_ASSERT( *m_10_1 + *m_1_1 == *m_11_2 );
}*/
};

int main( void)
{

CppUnit::TextUi::TestRunner runner;
CppUnit::TestFactoryRegistry &registry = CppUnit::TestFactoryRegistry::getRegistry();
runner.addTest( registry.makeTest() );
bool wasSucessful = runner.run( "", false );
return wasSucessful;

}



[root@localhost cppunit-test]# g++ -L/usr/local b -I/usr/local/include test1.cpp -o test1
/tmp/cccaLfPs.o(.text+0x12): In function `main':
: undefined reference to `CppUnit::TextUi::TestRunner::TestRunner(CppUnit::Outputter *)'
/tmp/cccaLfPs.o(.text+0x1a): In function `main':
: undefined reference to `CppUnit::TestFactoryRegistry::getRegistry(void)'
/tmp/cccaLfPs.o(.text+0x44): In function `main':
: undefined reference to `CppUnit::TextUi::TestRunner::addTest(CppUnit::Test *)'
/tmp/cccaLfPs.o(.text+0x7f): In function `main':
: undefined reference to `CppUnit::TextUi::TestRunner::run(basic_string<char, string_char_traits<char>, __default_alloc_template<true, 0> >, bool, bool, bool)'
/tmp/cccaLfPs.o(.text+0x99): In function `main':
: undefined reference to `CppUnit::TextUi::TestRunner::~TestRunner(void)'
/tmp/cccaLfPs.o(.text+0xc0): In function `main':
: undefined reference to `CppUnit::TextUi::TestRunner::~TestRunner(void)'
collect2: ld returned 1 exit status
[root@localhost cppunit-test]#  回复  更多评论
  
# re: 【CppUnit】快速入门 2009-09-18 11:10 紧急求助
你好,我就是按上面介绍的步骤执行的操作并写了程序,可编译总是出现下面的错误,请你看看这是什么错误,多谢了。

$ g++ MoneyApp.cpp MoneyTest.cpp -lcppunit -o test
/cygdrive/c/DOCUME~1/ADMINI~1/LOCALS~1/Temp/cc36iu30.o:MoneyTest.cpp:(.text$_ZN9
MoneyTest5suiteEv[MoneyTest::suite()]+0x1c4): undefined reference to `CppUnit::T
estSuiteBuilderContextBase::~TestSuiteBuilderContextBase()'
/cygdrive/c/DOCUME~1/ADMINI~1/LOCALS~1/Temp/cc36iu30.o:MoneyTest.cpp:(.text$_ZN9
MoneyTest5suiteEv[MoneyTest::suite()]+0x267): undefined reference to `CppUnit::T
estSuiteBuilderContextBase::~TestSuiteBuilderContextBase()'
/cygdrive/c/DOCUME~1/ADMINI~1/LOCALS~1/Temp/cc36iu30.o:MoneyTest.cpp:(.text$_ZN7
CppUnit23TestSuiteBuilderContextI9MoneyTestED1Ev[CppUnit::TestSuiteBuilderContex
t<MoneyTest>::~TestSuiteBuilderContext()]+0x16): undefined reference to `CppUnit
::TestSuiteBuilderContextBase::~TestSuiteBuilderContextBase()'
/cygdrive/c/DOCUME~1/ADMINI~1/LOCALS~1/Temp/cc36iu30.o:MoneyTest.cpp:(.text$_ZN7
CppUnit27TestSuiteBuilderContextBaseC2ERKS0_[CppUnit::TestSuiteBuilderContextBas
e::TestSuiteBuilderContextBase(CppUnit::TestSuiteBuilderContextBase const&)]+0xb
): undefined reference to `vtable for CppUnit::TestSuiteBuilderContextBase'
/cygdrive/c/DOCUME~1/ADMINI~1/LOCALS~1/Temp/cc36iu30.o:MoneyTest.cpp:(.text$_ZN7
CppUnit23TestSuiteBuilderContextI9MoneyTestED0Ev[CppUnit::TestSuiteBuilderContex
t<MoneyTest>::~TestSuiteBuilderContext()]+0x16): undefined reference to `CppUnit
::TestSuiteBuilderContextBase::~TestSuiteBuilderContextBase()'
collect2: ld returned 1 exit status

Administrator@6013E40A38DE4D6 ~/money
$  回复  更多评论
  

只有注册用户登录后才能发表评论。
网站导航: 博客园   IT新闻   BlogJava   知识库   博问   管理