简介

CppUnit 是一个基于 LGPL 的开源项目,最初版本移植自 JUnit,是一个非常优秀的开源测试框架。CppUnit 和 JUnit 一样主要思想来源于极限编程(XProgramming)。主要功能就是对单元测试进行管理,并可进行自动化测试。本文主要介绍如何利用 CppUnit 进行单元测试。

本文的开发环境为:WIN10 + VS2015

本例工程下载地址:CppUnit入门代码,编译时请指定编译版本为 Debug|x86。

编译 CppUnit

工欲善其事,必先利其器,使用 CppUnit 的第一步就是要下载并编译它。

下载 CppUnit

首先 下载 CppUnit,本例下载的是:cppunit-1.12.1.tar.gz 。下载后解压到合适的目录。解压后可以在 [解压目录]\cppunit-1.12.1\src 目录下找到VC6的工程文件 CppUnitLibraries.dsw。

编译 CppUnit

  • 用 VS2015 打开 CppUnitLibraries.dsw,会弹出提示升级的对话框,点击[确定]升级。
  • 打开后可以看到如下项目:

    cppunit
    cppunit_dll
    DllPlugInTester
    DSPlugIn
    TestPlugInRunner
    TestRunner

    其中 cppunit 和 cppunit_dll 分别是 CppUnit 的静态库和动态库。DllPlugInTester, DSPlugIn 和 TestPlugInRunner 是关于 VC6 的 CppUnit 插件工程,忽略即可。TestRunner 是包含用户界面的测试用例运行器,本例不涉及它。下面主要介绍 cppunit 和 cppunit_dll 的编译方法。

编译 cppunit 项目

  • [工程属性] -> [配置属性] -> [常规]:[字符集] 选择 [使用 Unicode 字符集]。如果是 Debug 版,还需要修改 [目标文件名] 为$(ProjectName)d
  • 分别编译 cppunit 的 Debug 和 Release 版本,成功后会在 cppunit-1.12.1\lib 目录下找到生成的 CppUnit 静态库 cppunitd.lib(Debug版) 和 cppunit.lib(Release版)。

编译 cppunit_dll 项目

  • [工程属性] -> [配置属性] -> [常规]。[字符集] 选择 [使用 Unicode 字符集]。如果是 Debug 版,还需要修改 [目标文件名] 为cppunitd_dll
  • 分别编译 cppunit_dll 的 Debug 和 Release 版本,成功后会在 cppunit-1.12.1\lib 目录下找到生成的 CppUnit 动态库 cppunitd_dll.lib、cppunitd_dll.dll(Debug版) 和 cppunit_dll.lib、cppunit_dll.dll(Release版)。

使用 CppUnit

CppUnit核心类和基本概念:

  • TestCase:测试用例。通过继承它来实现测试用例。
  • TestFixture:测试固件。用来建立测试基准或构建测试用例的先决条件。
  • TestSuite:测试套具。用来整合测试用例。
  • TestRunner:用来运行测试用例。
  • TestListener:用来接收执行测试用例过程中产生的结果。
  • TestResult:用来管理TestListener。

使用 CppUnit 流程如下:

Created with Raphaël 2.1.0 开始 创建TestCase 创建TestRunner 将TestCase注册到TestRunner 创建TestResult 创建TestListener 注册TestListener到TestResult 用TestResult作为参数调用TestRunner::run() 从TestListener中获取并处理结果 结束

下面通过实例来介绍 CppUnit 的相关概念以及如何使用它。

准备工作

  • 新建一个工程,工程类型选择 [空白解决方案],命名为 Calculator。
  • 添加一个Win32静态库项目,命名为 Calculator。
  • 新建 Calculator.h。代码如下:
#ifndef CALCULATOR_H
#define CALCULATOR_H

class Calculator
{
public:
    int add(int num1, int num2);
    int sub(int num1, int num2);
};

#endif
  • 新建 Calculator.cpp,代码如下。注意这里故意将 sub 的实现写错,目的是为了在运行测试用例时观察效果。
#include "Calculator.h"

int Calculator::add(int num1, int num2)
{
    return num1 + num2;
}

int Calculator::sub(int num1, int num2)
{
    return num1 - num1;  // 错误,应该是 num1 - num2
}
  • 编译工程,生成 Calculator.lib。
  • 下面就开始写测试用例来测试这个类。

使用 TestCase

  • 新建控制台项目,命名为 CalculatorTest1。
  • 将 CppUnit 的 include 目录和 lib 目录分别添加到附加包含目录和附加库目录,并将 cppunitd.lib 添加到附加依赖项。
  • 将 Calculator.h 所在目录和 Calculator.lib 所在目录分别添加到附加包含目录和附加库目录,并将 Calculator.lib 添加到附加依赖项。
  • 添加 main.cpp,代码如下:
#include "cppunit/TestCase.h"
#include "cppunit/TestRunner.h"
#include "cppunit/TestResult.h"
#include "cppunit/TestResultCollector.h"
#include "cppunit/TextOutputter.h"
#include "Calculator.h"

// 自定义的测试用例需要继承 CppUnit::TestCase
class CalculatorTest : public CppUnit::TestCase
{
public:
    // 重写 runTest() 方法
    virtual void runTest() override
    {
        Calculator calculator;

        // 使用 CPPUNIT_ASSERT 进行断言
        CPPUNIT_ASSERT(3 == calculator.add(1, 2));
        CPPUNIT_ASSERT(1 == calculator.sub(2, 1));
    }
};

int main()
{
    /* 创建 TestCase */
    // 不需要调用 delete 销毁 TestCase 对象,该对象由 CppUnit 框架自动销毁
    CalculatorTest *testCase = new CalculatorTest;

    /* 创建 TestRunner */
    CppUnit::TestRunner runner;

    /* 注册 TestCase 到 TestRunner */
    runner.addTest(testCase);

    /* 创建 TestResult */
    CppUnit::TestResult result;

    /* 创建 TestListener */
    // TestResultCollector 用来接收测试用例执行结果
    CppUnit::TestResultCollector resultCollector;

    /* 注册 TestListener 到 TestResult */
    result.addListener(&resultCollector);

    /* 执行测试用例 */
    runner.run(result);

    /* 处理结果 */
    // TextOutputter 用来以文本的形式打印执行结果
    // 第二个参数为输出设备,类型为 std::ostream,这里传入std::cout,将结果打印到标准输出
    CppUnit::TextOutputter outputter(&resultCollector, std::cout);
    outputter.write();

    return 0;
}

输出如下:

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


1) test:  (F) line: 25 g:\projects\cpp\calculator\calculatortest1\main.cpp
assertion failed
- Expression: 1 == calculator.sub(2, 1)

使用 TestFixture

  • 新建控制台项目,命名为 CalculatorTest2。
  • 将 CppUnit 的 include 目录和 lib 目录分别添加到附加包含目录和附加库目录,并将 cppunitd.lib 添加到附加依赖项。
  • 将 Calculator.h 所在目录和 Calculator.lib 所在目录分别添加到附加包含目录和附加库目录,并将 Calculator.lib 添加到附加依赖项。
  • 添加 main.cpp,代码如下:
/*
 * 本例说明如何使用 CppUnit::TestFixture
 * CppUnit::TestFixture 表示测试固件,用来建立测试基准或构建测试用例的先决条件。
 * 比如在执行测试用例之前连接数据库,准备初始数据等。
 * CppUnit::TestFixture 提供了两个函数 setUp() 和 tearDown() 用来
 * 在执行测试用例之前和之后进行初始化和清理操作。这两个函数将由 CppUnit 框架在
 * 执行测试用例之前和之后调用。
 */

#include "cppunit/TestFixture.h"
#include "cppunit/TestCaller.h"
#include "cppunit/TestResult.h"
#include "cppunit/TestResultCollector.h"
#include "cppunit/TextOutputter.h"
#include "Calculator.h"

// 自定义的 TestFixture 需要继承自 CppUnit::TestFixture
class CalculatorFixture : public CppUnit::TestFixture
{
    // CppUnit::TestFixture 
public:

    // 重写 setUp() 提供初始化操作
    virtual void setUp() override
    {
        // 在实际情况中,可能执行连接数据库,打开文件,加载资源等操作

        m_calculator = new Calculator();
    }

    // 重写 tearDown() 提供清理操作
    virtual void tearDown() override
    {
        // 在实际情况中,可能执行断开数据库连接,关闭文件,销毁资源等操作

        delete m_calculator; m_calculator = nullptr;
    }

    // 测试用例
public:

    void testAdd()
    {
        CPPUNIT_ASSERT(3 == m_calculator->add(1, 2));
    }

    void testSub()
    {
        CPPUNIT_ASSERT(1 == m_calculator->sub(2, 1));
    }

private:
    Calculator *m_calculator;
};

int main()
{
    /* 创建 TestCaller */
    // CppUnit::TestCaller 是一个以 CppUnit::TestFixture 作为模板参数的模板类,
    // 构造函数接收测试用例的名称和测试方法
    CppUnit::TestCaller<CalculatorFixture> 
        testCaller("testSub", &CalculatorFixture::testSub);

    /* 创建 TestResult */
    CppUnit::TestResult result;

    /* 创建 TestListener */
    CppUnit::TestResultCollector resultCollector;

    /* 注册 TestListener 到 TestResult */
    result.addListener(&resultCollector);

    /* 执行测试用例 */
    testCaller.run(&result);

    /* 处理结果 */
    CppUnit::TextOutputter outputter(&resultCollector, std::cout);
    outputter.write();

    return 0;
}

输出如下:

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


1) test: testSub (F) line: 49 g:\projects\cpp\calculator\calculatortest2\main.cpp
assertion failed
- Expression: 1 == m_calculator->sub(2, 1)

使用 TestSuite

在上例中可以看到使用 CppUnit::TestCaller 一次只能运行一个测试用例,而 TestSuite 则可以一次运行多个测试用例,如下所示(仅列出重要部分):

/* 创建 TestSuite */
CppUnit::TestSuite testSuite;

/* 注册测试用例 */
// 注意 new 出来的 TestCaller 会由 CppUnit 框架销毁
using Caller = CppUnit::TestCaller<CalculatorFixture>;
testSuite.addTest(new Caller("testAdd", &CalculatorFixture::testAdd));
testSuite.addTest(new Caller("testSub", &CalculatorFixture::testSub));

...

/* 执行测试用例 */
// 利用 TestSuite 执行测试用例
testSuite.run(&result);

使用 TestRunner

使用 TestRunner 可以一次执行多个 TestSuite。例如:

...
class CalculatorFixture : public CppUnit::TestFixture
{
public:
    // 提供一个静态的方法返回该 TestFixture 的 TestSuite,
    // 以便传给 TestRunner 使用
    static CppUnit::TestSuite *suite()
    {
        CppUnit::TestSuite *testSuite = new CppUnit::TestSuite("CalculatorTest");
        using Caller = CppUnit::TestCaller<CalculatorFixture>;
        testSuite->addTest(new Caller("testAdd", &CalculatorFixture::testAdd));
        testSuite->addTest(new Caller("testSub", &CalculatorFixture::testSub));
        return testSuite;
    }

    ...
};

int main()
{
    /* 创建 TestRunner */
    // 这里使用 TextTestRunner 将执行过程中的输出以文本形式打印出来
    CppUnit::TextTestRunner runner;

    /* 注册 TestSuite 到 TestRunner */
    // 实际测试环境当中,可能会注册多个 TestSuite 到 TestRunner
    runner.addTest(CalculatorFixture::suite());

    // 设置输出对象,这里传递一个 TextOutputter,将执行过程中的输出打印到标准输出
    runner.setOutputter(new CppUnit::TextOutputter(&runner.result(), std::cout));

    /* 执行测试用例 */
    runner.run();

    return 0;
}

编程工作的简化

CppUnit 提供了一系列宏来简化编程。这些宏都在头文件 #include "cppunit/extensions/HelperMacros.h"中。

简化获取 TestSuite 的方法

利用CPPUNIT_TEST_SUITE、CPPUNIT_TEST、CPPUNIT_TEST_SUITE_END宏来简化获取 TestSuite 的静态方法,例如上例中的 static CppUnit::TestSuite *suite() 方法可以简化为:

CPPUNIT_TEST_SUITE(CalculatorFixture);
CPPUNIT_TEST(testAdd);
CPPUNIT_TEST(testSub);
CPPUNIT_TEST_SUITE_END();

使用 TestFactoryRegistry 注册测试用例

使用 TestFactoryRegistry 有两个好处:
1. 避免忘记将 Fixture Suite 加入到 TestRunner 中。
2. 在执行测试用例的cpp文件中不用包含所有 TestFixture 的头文件。

使用方法:
利用 CPPUNIT_TEST_SUITE_REGISTRATION 将 TestFixture 注册到全局 TestFactoryRegistry 中。在执行测试用例时利用这个全局的 TestFactoryRegistry 获取所有注册在其中的测试用例。将这些测试用例传递给 TestRunner 执行。

// 在实现 TestFixute 的 cpp 文件中注册自定义的 TestFixture
// 到全局的 TestFactoryRegistry 中
CPPUNIT_TEST_SUITE_REGISTRATION(MyTestFixture);

// =================================================

// 在执行测试用力的 cpp 文件中通过全局的 TestFactoryRegistry 获取
// 所有注册在其中的 TestSuite
runner.addTest(CppUnit::TestFactoryRegistry::getRegistry().makeTest());

总结

  • CppUnit 是一个 C++ 的单元测试框架。
  • CppUnit 提供了丰富的类用以完成单元测试需求。
Logo

旨在为数千万中国开发者提供一个无缝且高效的云端环境,以支持学习、使用和贡献开源项目。

更多推荐