以下内容来自:http://www.cnblogs.com/wishma/archive/2008/08/01/1258370.html
1. 简介
CppUnit 是个基于 LGPL 的开源项目,最初版本移植自 JUnit,是一个非常优秀的开源测试框架。CppUnit 和 JUnit 一样主要思想来源于极限编程(XProgramming)。主要功能就是对单元测试进行管理,并可进行自动化测试。这样描述可能没有让您体会到测试框架的 强大威力,那您在开发过程中遇到下列问题吗?如果答案是肯定的,就应该学习使用这种技术:
- 测试代码没有很好地维护而废弃,再次需要测试时还需要重写;
- 投入太多的精力,找 bug,而新的代码仍然会出现类似 bug;
- 写完代码,心里没底,是否有大量 bug 等待自己;
- 新修改的代码不知道是否影响其他部分代码;
- 由于牵扯太多,导致不敢进行修改代码;
2. 基本思想
对于上面的问题仅仅说明 CppUnit 的使用是没有效果的,下面先从测试的目的,测试原则等方面简要说明,然后介绍 CppUnit 的具体使用。
首先要明确我们写测试代码的目的,就是验证代码的正确性或者调试 bug。这样写测试代码时就有了针对性,对那些容易出错的,易变的编写测试代码;而不用对每个细节,每个功能编写测试代码,当然除非有过量精力或者可靠性要求。
编 码和测试的关系是密不可分的,推荐的开发过程并不要等编写完所有或者很多的代码后再进行测试,而是在完成一部分代码,比如一个函数, 之后立刻编写测试代码进行验证。然后再写一些代码,再写测试。每次测试对所有以前的测试都进行一遍。这样做的优点就是,写完代码,也基本测试完一遍,心里 对代码有信心。而且在写新代码时不断地测试老代码,对其他部分代码的影响能够迅速发现、定位。不断编码测试的过程也就是对测试代码维护的过程,以便测试代 码一直是有效的。有了各个部分测试代码的保证,有了自动测试的机制,更改以前的代码没有什么顾虑了。在极限编程(一种软件开发思想)中,甚至强调先写测试 代码,然后编写符合测试代码的代码,进而完成整个软件。
根据上面说的目的、思想,下面总结一下平时开发过程中单元测试的原则:
- 先写测试代码,然后编写符合测试的代码。至少做到完成部分代码后,完成对应的测试代码;
- 测试代码不需要覆盖所有的细节,但应该对所有主要的功能和可能出错的地方有相应的测试用例;
- 发现 bug,首先编写对应的测试用例,然后进行调试;
- 不断总结出现 bug 的原因,对其他代码编写相应测试用例;
- 每次编写完成代码,运行所有以前的测试用例,验证对以前代码影响,把这种影响尽早消除;
- 不断维护测试代码,保证代码变动后通过所有测试;
有上面的理论做指导,测试行为就可以有规可循。那么 CppUnit 如何实现这种测试框架,帮助我们管理测试代码,完成自动测试的?下面就看看 CppUnit 的原理。
3. cppunit的基本原理
CppUint进行测试的基本原理是:对于每一批测试,编写一个继承自TestFixture的测试类,其中setUp和tearDown 两个虚函数分别用于进行测试数据的初始化和释放操作;对于每一项测试,分别定义一个void返回类型的无参数方法,检测测试过程,可以用以下几个宏:
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时失败 |
其中,“失败”的含义就是程序运行出现异常,并且所有测试过程中止。
在完成一个或者多个测试类的编写后,需要用TestRunner类的实例来运行每个测试类的测试函数进行测试。TestRunner类型共有:TextUi::TestRunner,QtUi::TestRunner,MfcUi::TestRunner三个,都可以用来运行测试,其区别为:
TextUi::TestRunner | 默认将信息输出到控制台,也可以指定输出到std::ostringstream。 |
QtUi::TestRunner | 用QT的GUI输出信息 |
MfcUi::TestRunner | 用MFC的GUI输出信息 |
整个测试过程需要另外附加代码,所以通常情况下,测试用例代码和被测试对象因在不同的项目中。应该在另一个项目(最好在不同的目录)中编写测试类,然后把被测试的对象包含在测试项目中。根据测试工程建立的类型,Consol可以选择TextUi::TestRunner,将结果输出至控制台显示,MFC的GUI可以使用MfcUi::TestRunner,使用GUI来控制并查看测试结果。测试工程与被测试对象所在工程的类型是可以不同的。
在使用VC作为开发工工具时,建议使用TextUi::TestRunner,并更改其Outputter实例,使用附加的TraceOutputter,将信息输出到信息输出栏,双击信息输出栏中一行,即可定位到出错位置。
4. 建立cppunit测试实例的一般步骤
建立一个cppunit测试实例的一般步骤如下:
- 包含文件#include <cppunit/extensions/HelperMacros.h>
- 声明一个类,比如,MathTest,继承自CppUnit::TestFixture。
- 声明setUp和tearDown函数。
- 声明测试函数,比如testAdd
- 如果含有其他测试函数,继续添加。
- 在类声明部分添加如下宏,声明改类为测试类,并且声明那些函数为测试函数
CPPUNIT_TEST_SUITE( MathTest); //声明一个TestSuite CPPUNIT_TEST( testAdd ); //添加TestCase到TestSuite /* 定义的TestCase需要在此声明。如果有其他TestCase,可以继续添加到此处 */ CPPUNIT_TEST_SUITE_END(); //TestSuite声明完成
7. 最后在实现的CPP文件中,添加下面的宏,用于注册测试类到某个测试项目,比如以"alltest"命名的项目。
CPPUNIT_TEST_SUITE_NAMED_REGISTRATION( MathTest, "alltest" );
8. 实现setUp和tearDown,以及各个测试函数。
6. cppunit的测试代码实例
需要测试的类名为Math,待测试的函数的Math::testAdd().
//Math.h class Math{ public: Math(); ~Math(); int add(int, int); }; //Math.cc #include "Math.h" int Math::add(int x, int y){ return x+y; }
cppunit测试代码如下:
//MathTest.h #include "cppunit/TestFixture.h" #include "cppunit/extensions/HelperMacros.h" class MathTest:public CppUnit::TestFixture{ CPPUNIT_TEST_SUITE(MathTest); CPPUNIT_TEST(testAdd); CPPUNIT_TEST_SUITE_END(); protected: int m_value1, m_value2; public: MathTest(){} void setUp(); void tearDown(); void testAdd(); }; //MathTest.cc #include "MathTest.h" #include "Math.h" #include "cppunit/TestAssert.h" CPPUNIT_TEST_SUITE_NAMED_REGISTRATION(MathTest, "alltest"); void MathTest::setUp(){ m_value1 = ; m_value2 = ; } void MathTest::tearDown(){ } void MathTest::testAdd(){ int ret = m_value1 + m_value2; CPPUNIT_ASSERT(ret == ); }
测试的主代码如下:
#include "Math.h" #include "MathTest.h" #include <iostream> #include "cppunit/ui/text/TestRunner.h" #include "cppunit/extensions/TestFactoryRegistry.h" int main(void){ CppUnit::TextUi::TestRunner runner; CppUnit::TestFactoryRegistry ®istry = CppUnit::TestFactoryRegistry::getRegistry("alltest"); runner.addTest(registry.makeTest()); runner.run(); }
编译: g++ Math.cpp MathTest.cpp main.cpp -lcppunit -ldl -lm -lpthread -o mathTest
运行: ./mathTest
显示:
通过的情况:OK (1 tests)
失败的情况:会显示那个测试类中的哪行测试不通过
!!!FAILURES!!! Test Results: Run: 1 Failures: 1 Errors: 0 1) test: MathTest::testAdd (F) line: 15 MathTest.cpp assertion failed - Expression: ret==4