简介

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

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

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

安装

1、  先下个最新版cppunit-1.12.1.tar.gz 解压缩,进入cppunit-1.12.1\src目录,就是源代码所在,打开CppUnitLibraries.dsw工程,是用vc6.0写的,用vs2010转换到CppUnitLibraries.sln ok,

2、  然后依次运行CppUnitLibraries.dsw工程下的每个项目,这样做的目的是为了方面发现问题和找到正确的解决方法。下面是我在运行相应项目时所提示的错误以及解决办法。未贴图,把下述的解决办法都做完即可,不需要查看贴图的错误提示

1)       运行项目Cppunit

解决办法:

选择Cppunit右键属性 ->(debug)配置属性->常规->目标文件名:$(ProjectName)修改成cppunitd(这样做是为保持链接器->常规->目标文件名 一致);

2)       运行项目cppunit_dll

解决办法:

选择Cppunit右键属性->(debug)配置属性->常规->目标文件名:$(ProjectName)修改成cppunitd_dll(这样做是为保持库管理器->常规->目标文件名 一致);

3)到这里这里会发现其实每个项目的错误基本上都是TargeName(xxx)与Linker的OutputFile属性值不匹配;依次修改项目DllPlugInTester、DSPlugIn、TestPlugInRunner、TestRunner的Debug配置属性(每个后面都记得加个”d”,而且库管理器中为:目标文件名.xx,xx不需要修改,但库管理器中的目标文件名可能要修改,该成与常规—目标文件名一致!)

4)当然当修改完DSPlugIn的Debug配置属性后再运行我们发现

我们仔细观察到底新的错误是什么呢?           这里修改方式就是最上面的红色字中提到的

修改TestRunner下UserInterface\DynamicWindow\MsDevCallerListCtrl.cpp文件第67行,改成:#import"libid:80cc9f66-e7d8-4ddd-85b6-d9e6cd0e93e2" version("8.0")lcid("0") raw_interfaces_only named_guids

5)再次运行DSPlugIn项目我们会发现依然有错误

解决方法:

选择项目->属性->配置属性->链接器->高级->无入口点 选择"是(/NOENTRY)"

6)最后为了生成全面的库文件我需要分别在Debug、release、Debug unicode、release unicode四种配置属性中生成全部解决方案。(运行过程中会遇到错误基本上都可以从上文中找到解决方法)

7)编译完成后,提示成功6  失败 0 即安装完毕

**************************************************************************************************
详细说明:

解压后,你可以看到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     _______|_________            |                                    |          |               |            |                           TestSuccessListenerTestComposite   TestLeaf         |                                    |          |               |____________|                           TestResultCollector          TestSuit                  |TestCase                     |TestCaller<Fixture>Outputter                                    TestFactory                    TestRunner____________________|_________________                            ||                   |                |                   TestFactoryRegistryCompilerOutputter  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将自行管理测试对象的生命期。

CppUnit在vs2010中配置:

参考:Vs2010下使用CppUint初体验

http://blog.csdn.net/leer168/article/details/6708732

vs2010代码实例:

CppUnitTest1

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

#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后无异常抛出。

CppUnitTest2

接下来再看看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)
CppUnitTest3

上面的代码从功能上讲没有什么问题,但编写起来太繁琐了,为此,我们可以借助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的简单介绍就到此,相信你已经了解了其中的基本概念,也能够开发单元测试代码了。

其它CppUnit还包括其它一些辅助模块,比如基于MFC的图形化测试界面,下面这篇文章对此有所介绍:

CppUnit测试框架入门

CppUnit使用了很多设计模式,整体构架还算清晰合理,源码也比较简单易懂,这对于学习设计模式是一个不错的选择。网上已有这样的一些资料:

CppUnit源码解读       CppUnit代码简介 - 第一部分,核心类

(freefalcon于2006-05-22)

[vs2010 project] CppUnit快速入门相关推荐

  1. CppUnit快速入门

    From:http://blog.csdn.net/freefalcon/archive/2006/05/25/753819.aspx 简介 测试是软件开发过程中极其重要的一环,详尽周密的测试能够减少 ...

  2. [ 物联网篇 ] 02 - Yocto Project (YP)快速入门

    NXP i.MX 8M Mini 的源码构建系统使用的是Yocto,如果不熟悉Yocto,完全看不懂 i.MX 8M Mini 的相关代码. 废话不多说,直接到Yocto 官网看文档,写Demo.参考 ...

  3. Yocto Project 快速入门指南

    原文:http://www.yoctoproject.org/docs/current/yocto-project-qs/yocto-project-qs.html Yocto Project 快速入 ...

  4. QT快速入门、三点求圆心实现详解

    在编程中,会经常用到数学计算,所以C++将常用的数学计算,例如求正余弦等,封装成函数(正是我们在3.2 数学计算中学习到的),我们只需要写入简单的语句就可以执行所需要的功能,这正是函数的意义.在这一章 ...

  5. Spring Boot 2 快速教程:WebFlux 快速入门(二)

    2019独角兽企业重金招聘Python工程师标准>>> 摘要: 原创出处 https://www.bysocket.com 「公众号:泥瓦匠BYSocket 」欢迎关注和转载,保留摘 ...

  6. Expression Blend实例中文教程(2) - 界面快速入门

    上一篇主要介绍Expression系列产品,另外概述了Blend的强大功能,本篇将用Blend 3创建一个新Silverlight项目,通过创建的过程,对Blend进行快速入门学习. 在开始使用Ble ...

  7. springcloud项目打包_SpringCloud 快速入门

    前言 最近入职了新公司,主要负责Java后端开发工作.目前正在开展一个全新的业务,技术选型为SpringCloud全家桶,项目的骨架由我负责搭建.由于前几家公司的微服务框架都是使用Dubbo + Sp ...

  8. MongoDB学习(五)使用Java驱动程序3.3操作MongoDB快速入门

    [引言] 毕竟现在MongoDB还是出于成长阶段,所以现在网上相关的资料很少,而且大部分还都是针对于MongoDB的老版本的.再加上MongoDB的频繁升级.重大更新等等,导致菜鸟学习的难度增大. 好 ...

  9. OUYA游戏开发快速入门教程

     OUYA游戏开发快速入门教程 试读地址:http://pan.baidu.com/s/1o63a3W2 本教程是国内唯一OUYA游戏开发教程.本教程基于Unity全面讲解OUYA游戏开发方式.内容包 ...

最新文章

  1. 鸟哥的Linux私房菜(基础篇)-第一章、Linux是什么(一.3. Linux的特色)
  2. Edge on Linux?微软:会有的,但不是现在
  3. 二进制_简学:二进制数制的应用
  4. vue下拉框值改变事件_vue和element ui 下拉框select的change事件
  5. 知秋源码解读分享系列
  6. oracle lead_lag wm_concat,oracle函数 lag()和lead()
  7. 装上WPS后导入Excel 的代码出错
  8. 备案有哪些类型,今天给大家讲讲备案类型概览,云南网站备案
  9. Robot Framework+Autoit 安装教程
  10. 如何把微信公众号平台做成找券机器人并自动回复优惠券
  11. 代理自动配置PAC学习
  12. python提取txt数据到excel
  13. C/C++基础 hypot函数
  14. 本地调试微信授权跳转
  15. linux启动redis进程,Linux安装Redis实现过程及报错解决方案
  16. java 过滤特殊字符串和emoji表情符号
  17. CF寒假补题集——1.21
  18. 从携程瘫痪事件看运维的85条军规
  19. XTU OJ 1381表格
  20. 异构图神经网络(1)Heterogenous Graph Attention Networks

热门文章

  1. 根据一个属性,剔除 Json 中重复元素(删除 JSON 中重复的部分)
  2. Eclipse搭建Android开发环境(安装ADT,Android4.4.2)
  3. java开启新线程的三种方法
  4. vue实战(9):总结二
  5. 十、eclipse快捷键大全
  6. java常用简略语含义
  7. DOM-动态操作心得
  8. Linux 文件打乱顺序
  9. Android学习四、Android中的Adapter
  10. Java实现串口通信的小样例