CppUnit 入门
简介
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 流程如下:
下面通过实例来介绍 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 提供了丰富的类用以完成单元测试需求。
更多推荐
所有评论(0)