• CppUnit快速入门


    转自:http://blog.csdn.net/freefalcon/article/details/753819

    简介

    测试是软件开发过程中极其重要的一环,详尽周密的测试能够减少软件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):将结果进行输出,可以制定不同的输出格式。(CompilerOutputter TextOutputter XmlOutputter

    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的简单介绍就到此,相信你已经了解了其中的基本概念,也能够开发单元测试代码了。

    其它

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

        CppUnit测试框架入门

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

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

    (freefalcon于2006-05-22)

  • 相关阅读:
    调试D2JS
    PG 中 JSON 字段的应用
    面试----
    机器学习面试题
    闭包和装饰器
    scss-混合@mixin @include @function
    scss基本使用及操作函数
    常用的scss函数(mixin)
    二叉搜索树基本操作实现
    判断一棵树是否是二叉搜索树
  • 原文地址:https://www.cnblogs.com/zhangxiaosong/p/3360023.html
Copyright © 2020-2023  润新知