如何在VS2010中编译CppUnit:http://www.cnblogs.com/justin_s/archive/2012/05/25/2517722.html http://blog.csdn.net/x_iya/article/details/8433716 http://www.cnblogs.com/zhcncn/archive/2012/12/25/2832162.html
CppUnit SVN地址:https://svn.code.sf.net/p/cppunit/code/
使用指南(IBM developer works):http://www.ibm.com/developerworks/cn/linux/l-cppunit/
学习主线:
CppUnit工作原理(e.g. setup()、teardown()、testcase()的设计思想) --------> 使用方法(工程搭建、配置、可调用的函数方法) --------> CppUnit的输出(报告、结果、记录..)
学习背景
- 测试代码没有很好地维护而废弃,再次需要测试时还需要重写;
- 投入太多的精力,找 bug,而新的代码仍然会出现类似 bug;
- 写完代码,心里没底,是否有大量 bug 等待自己;
- 新修改的代码不知道是否影响其他部分代码;
- 由于牵扯太多,导致不敢进行修改代码;
一、CppUnit 的原理
在 CppUnit 中,一个或一组测试用例的测试对象被称为 Fixture(设施,下文为方便理解尽量使用英文名称)。Fixture 就是被测试的目标,可能是一个对象或者一组相关的对象,甚至一个函数。
有了被测试的 fixture,就可以对这个 fixture 的某个功能、某个可能出错的流程编写测试代码,这样对某个方面完整的测试被称为TestCase(测试用例)。通常写一个 TestCase 的步骤包括:
- 对 fixture 进行初始化,及其他初始化操作,比如:生成一组被测试的对象,初始化值;
- 按照要测试的某个功能或者某个流程对 fixture 进行操作;
- 验证结果是否正确;
- 对 fixture 的及其他的资源释放等清理工作。
对 fixture 的多个测试用例,通常(1)(4)部分代码都是相似的,CppUnit 在很多地方引入了 setUp 和 tearDown 虚函数。可以在 setUp 函数里完成(1)初始化代码,而在 tearDown 函数中完成(4)代码。具体测试用例函数中只需要完成(2)(3)部分代码即可,运行时 CppUnit 会自动为每个测试用例函数运行 setUp,之后运行 tearDown,这样测试用例之间就没有交叉影响。
对 fixture 的所有测试用例可以被封装在一个 CppUnit::TestFixture 的子类(命名惯例是[ClassName]Test)中。然后定义这个fixture 的 setUp 和 tearDown 函数,为每个测试用例定义一个测试函数(命名惯例是 testXXX)。下面是个简单的例子:
1 class MathTest : public CppUnit::TestFixture { 2 protected: 3 int m_value1, m_value2; 4 public: 5 MathTest() {} 6 // 初始化函数 7 void setUp () { 8 m_value1 = 2; 9 m_value2 = 3; 10 } 11 // 测试加法的测试函数 12 void testAdd () { 13 // 步骤(2),对 fixture 进行操作 14 int result = m_value1 + m_value2; 15 // 步骤(3),验证结果是否正确 16 CPPUNIT_ASSERT( result == 5 ); 17 } 18 // 没有什么清理工作没有定义 tearDown. 19 }
在测试函数中对执行结果的验证成功或者失败直接反应这个测试用例的成功和失败。CppUnit 提供了多种验证成功失败的方式:
1 CPPUNIT_ASSERT(condition) // 确信condition为真 2 CPPUNIT_ASSERT_MESSAGE(message, condition) 3 // 当condition为假时失败, 并打印message 4 CPPUNIT_FAIL(message) 5 // 当前测试失败, 并打印message 6 CPPUNIT_ASSERT_EQUAL(expected, actual) 7 // 确信两者相等 8 CPPUNIT_ASSERT_EQUAL_MESSAGE(message, expected, actual) 9 // 失败的同时打印message 10 CPPUNIT_ASSERT_DOUBLES_EQUAL(expected, actual, delta) 11 // 当expected和actual之间差大于delta时失败
要把对 fixture 的一个测试函数转变成一个测试用例,需要生成一个 CppUnit::TestCaller 对象。而最终运行整个应用程序的测试代码的时候,可能需要同时运行对一个 fixture 的多个测试函数,甚至多个 fixture 的测试用例。CppUnit 中把这种同时运行的测试案例的集合称为 TestSuite。而 TestRunner 则运行测试用例或者 TestSuite,具体管理所有测试用例的生命周期。目前提供了 3 类TestRunner,包括:
CppUnit::TextUi::TestRunner // 文本方式的TestRunner CppUnit::QtUi::TestRunner // QT方式的TestRunner CppUnit::MfcUi::TestRunner // MFC方式的TestRunner
下面是个文本方式 TestRunner 的例子:
1 CppUnit::TextUi::TestRunner runner; 2 CppUnit::TestSuite *suite= new CppUnit::TestSuite(); 3 4 // 添加一个测试用例 5 suite->addTest(new CppUnit::TestCaller<MathTest> ( 6 "testAdd", &MathTest::testAdd)); 7 8 // 指定运行TestSuite 9 runner.addTest( suite ); 10 // 开始运行, 自动显示测试进度和测试结果 11 runner.run( "", true ); // Run all tests and wait
对 测试结果的管理、显示等功能 涉及到另一类对象,主要用于内部对测试结果、进度的管理,以及进度和结果的显示。这里不做介绍。
下面我们整理一下思路,结合一个简单的例子,把上面说的思路串在一起。
二、手动使用步骤(使用方法)
首先要明确测试的对象 fixture,然后根据其功能、流程,以及以前的经验,确定测试用例。这个步骤非常重要,直接关系到测试的最终效果。当然 增加测试用例的过程是个阶段性的工作 ,开始完成代码后,先完成对功能的测试用例,保证其完成功能 ;然后对可能出错的部分,结合以前的经验(比如 边界值测试、路径覆盖测试 等)编写测试用例;最后在发现相关 bug 时,根据 bug 完成测试用例。
比如对整数加法进行测试,首先定义一个新的 TestFixture 子类,MathTest,编写测试用例的测试代码。后期需要添加新的测试用例时只需要添加新的测试函数,根据需要修改 setUp 和 tearDown 即可。如果需要对新的 fixture 进行测试,定义新的 TestFixture 子类即可。注:下面代码仅用来表示原理,不能编译。
1 /// MathTest.h 2 // A TestFixture subclass. 3 // Announce: use as your owner risk. 4 // Author : liqun (liqun@nsfocus.com) 5 // Data : 2003-7-5 6 #include "cppunit/TestFixture.h" 7 class MathTest : public CppUnit::TestFixture { 8 protected: 9 int m_value1, m_value2; 10 11 public: 12 MathTest() {} 13 14 // 初始化函数 15 void setUp (); 16 // 清理函数 17 void tearDown(); 18 19 // 测试加法的测试函数 20 void testAdd (); 21 // 可以添加新的测试函数 22 };
23 /// MathTest.cpp 24 // A TestFixture subclass. 25 // Announce: use as your owner risk. 26 // Author : liqun (liqun@nsfocus.com) 27 // Data : 2003-7-5 28 #include "MathTest.h" 29 #include "cppunit/TestAssert.h" 30 void MathTest::setUp() 31 { 32 m_value1 = 2; 33 m_value2 = 3; 34 } 35 void MathTest::tearDown() 36 { 37 } 38 void MathTest::testAdd() 39 { 40 int result = m_value1 + m_value2; 41 CPPUNIT_ASSERT( result == 5 ); 42 }
然后编写 main 函数,把需要测试的测试用例组织到 TestSuite 中,然后通过 TestRuner 运行。这部分代码后期添加新的测试用例时需要改动的不多。只需要把新的测试用例添加到 TestSuite 中即可。
1 /// main.cpp 2 // Main file for cppunit test. 3 // Announce: use as your owner risk. 4 // Author : liqun (liqun@nsfocus.com) 5 // Data : 2003-7-5 6 // Note : Cannot compile, only for study. 7 #include "MathTest.h" 8 #include "cppunit/ui/text/TestRunner.h" 9 #include "cppunit/TestCaller.h" 10 #include "cppunit/TestSuite.h" 11 int main() 12 { 13 CppUnit::TextUi::TestRunner runner; 14 CppUnit::TestSuite *suite= new CppUnit::TestSuite(); 15 16 // 添加一个测试用例 17 suite->addTest(new CppUnit::TestCaller<MathTest> ( 18 "testAdd", testAdd)); 19 20 // 指定运行TestSuite 21 runner.addTest( suite ); 22 // 开始运行, 自动显示测试进度和测试结果 23 runner.run( "", true ); // Run all tests and wait 24 }
三、常用 使用方式
按照上面的方式,如果要添加新的测试用例,需要把每个测试用例添加到 TestSuite 中,而且添加新的 TestFixture 需要把所有头文件添加到 main.cpp 中,比较麻烦。为此 CppUnit 提供了 CppUnit::TestSuiteBuilder,CppUnit::TestFactoryRegistry 和一堆 宏,用来方便地把 TestFixture 和测试用例注册到 TestSuite 中。下面就是通常的使用方式:
1 /// MathTest.h 2 // A TestFixture subclass. 3 // Announce: use as your owner risk. 4 // Author : liqun (liqun@nsfocus.com) 5 // Data : 2003-7-5 6 7 #include "cppunit/extensions/HelperMacros.h" 8 class MathTest : public CppUnit::TestFixture { 9 // 声明一个TestSuite 10 CPPUNIT_TEST_SUITE( MathTest ); 11 // 添加测试用例到TestSuite, 定义新的测试用例需要在这儿声明一下 12 CPPUNIT_TEST( testAdd ); 13 // TestSuite声明完成 14 CPPUNIT_TEST_SUITE_END(); 15 // 其余不变 16 protected: 17 int m_value1, m_value2; 18 19 public: 20 MathTest() {} 21 22 // 初始化函数 23 void setUp (); 24 // 清理函数 25 void tearDown(); 26 27 // 测试加法的测试函数 28 void testAdd (); 29 // 可以添加新的测试函数 30 }; 31
32 /// MathTest.cpp 33 // A TestFixture subclass. 34 // Announce: use as your owner risk. 35 // Author : liqun (liqun@nsfocus.com) 36 // Data : 2003-7-5 37 #include "MathTest.h" 38 // 把这个TestSuite注册到名字为"alltest"的TestSuite中, 如果没有定义会自动定义 39 // 也可以CPPUNIT_TEST_SUITE_REGISTRATION( MathTest );注册到全局的一个未命名的TestSuite中. 40 CPPUNIT_TEST_SUITE_NAMED_REGISTRATION( MathTest, "alltest" ); 41 // 下面不变 42 void MathTest::setUp() 43 { 44 m_value1 = 2; 45 m_value2 = 3; 46 } 47 void MathTest::tearDown() 48 { 49 } 50 void MathTest::testAdd() 51 { 52 int result = m_value1 + m_value2; 53 CPPUNIT_ASSERT( result == 5 ); 54 } 55
56 /// main.cpp 57 // Main file for cppunit test. 58 // Announce: use as your owner risk. 59 // Compile : g++ -lcppunit MathTest.cpp main.cpp 60 // Run : ./a.out 61 // Test : RedHat 8.0 CppUnit1.8.0 62 // Author : liqun ( a litthle modification. liqun@nsfocus.com) 63 // Data : 2003-7-5 64 // 不用再包含所有TestFixture子类的头文件 65 66 #include <cppunit/extensions/TestFactoryRegistry.h> 67 #include <cppunit/ui/text/TestRunner.h> 68 // 如果不更改TestSuite, 本文件后期不需要更改. 69 int main() 70 { 71 CppUnit::TextUi::TestRunner runner; 72 73 // 从注册的TestSuite中获取特定的TestSuite, 没有参数获取未命名的TestSuite. 74 CppUnit::TestFactoryRegistry ®istry = 75 CppUnit::TestFactoryRegistry::getRegistry("alltest"); 76 // 添加这个TestSuite到TestRunner中 77 runner.addTest( registry.makeTest() ); 78 // 运行测试 79 runner.run(); 80 }
这样添加新的测试用例只需要在类定义的开始声明一下即可。
其他实际问题(输出)
通常包含测试用例代码和被测试对象是在不同的项目中。应该在另一个项目(最好在不同的目录)中编写 TestFixture,然后把被测试的对象包含在测试项目中。
对某个类或者某个函数进行测试的时候,这个 TestFixture 可能引用了别的类或者别的函数,为了隔离其他部分代码的影响,应该在源文件中临时定义一些 桩程序 ,模拟这些类或者函数。这些代码可以通过宏定义在测试项目中有效,而在被测试的项目中无效。