CppUnit 快速使用指南
上一篇 / 下一篇 2007-11-25 20:33:38 / 个人分类:自动化测试
e j|~h C0李群51Testing软件测试网x{/Q7f&H0hq
2003 年 8 月 03 日51Testing软件测试网"D9Wn{'_o%@2E
8c2landd)m$iq,P0本文从开发人员的角度,介绍 CppUnit 框架,希望能够使开发人员用最少的代价尽快掌握这种技术。下面从基本原理,CppUnit 原理,手动使用步骤,通常使用步骤,其他实际问题等方面进行讨论。以下讨论基于 CppUnit1.8.0。51Testing软件测试网[4~CMX/bTJ%C
背景
CppUnit 是个基于 LGPL 的开源项目,最初版本移植自 JUnit,是一个非常优秀的开源测试框架。CppUnit 和 JUnit 一样主要思想来源于极限编程(XProgramming)。主要功能就是对单元测试进行管理,并可进行自动化测试。这样描述可能没有让您体会到测试框架的强大威力,那您在开发过程中遇到下列问题吗?如果答案是肯定的,就应该学习使用这种技术:51Testing软件测试网 {d5? {,x N
51Testing软件测试网'p}n a8h%N. 测试代码没有很好地维护而废弃,再次需要测试时还需要重写;
M4b*r(NI$RLN0. 投入太多的精力,找 bug,而新的代码仍然会出现类似 bug;51Testing软件测试网"S'G*a9Ka7eu!L+Y,q
. 写完代码,心里没底,是否有大量 bug 等待自己;51Testing软件测试网2}\Z/Yw7Y;l%X:g0r4s
. 新修改的代码不知道是否影响其他部分代码;
?o5o5h%pX/G0. 由于牵扯太多,导致不敢进行修改代码;
4iWnJN0...
&PR,s t|E0
HrU|+Zi3F0这些问题下文都会涉及。这个功能强大的测试框架在国内的 C++ 语言开发人员中使用的不是很多。本文从开发人员的角度,介绍这个框架,希望能够使开发人员用最少的代价尽快掌握这种技术。下面从基本原理,CppUnit 原理,手动使用步骤,通常使用步骤,其他实际问题等方面进行讨论。以下讨论基于 CppUnit1.8.0。
p-tPs!| r&Zv!e@01. 基本原理51Testing软件测试网B7d3zTm
51Testing软件测试网X U6u4m;jQ n对于上面的问题仅仅说明 CppUnit 的使用是没有效果的,下面先从测试的目的,测试原则等方面简要说明,然后介绍 CppUnit 的具体使用。51Testing软件测试网Dv.S*?,Zs
Qc K5Bi0首先要明确我们写测试代码的目的,就是验证代码的正确性或者调试 bug。这样写测试代码时就有了针对性,对那些容易出错的,易变的编写测试代码;而不用对每个细节,每个功能编写测试代码,当然除非有过量精力或者可靠性要求。
Vm5\lH0#d r?(O@0t B%}0编码和测试的关系是密不可分的,推荐的开发过程并不要等编写完所有或者很多的代码后再进行测试,而是在完成一部分代码,比如一个函数,之后立刻编写测试代码进行验证。然后再写一些代码,再写测试。每次测试对所有以前的测试都进行一遍。这样做的优点就是,写完代码,也基本测试完一遍,心里对代码有信心。而且在写新代码时不断地测试老代码,对其他部分代码的影响能够迅速发现、定位。不断编码测试的过程也就是对测试代码维护的过程,以便测试代码一直是有效的。有了各个部分测试代码的保证,有了自动测试的机制,更改以前的代码没有什么顾虑了。在极限编程(一种软件开发思想)中,甚至强调先写测试代码,然后编写符合测试代码的代码,进而完成整个软件。51Testing软件测试网)d-o5{O(jhR D
Fb'R)uA'F5m2y]-I0根据上面说的目的、思想,下面总结一下平时开发过程中单元测试的原则:
0W*YW4U |08F*wx9kfN.lA0先写测试代码,然后编写符合测试的代码。至少做到完成部分代码后,完成对应的测试代码;
Y9D4zIR)WTU0测试代码不需要覆盖所有的细节,但应该对所有主要的功能和可能出错的地方有相应的测试用例;
?z/as;@E!Y,J2K0发现 bug,首先编写对应的测试用例,然后进行调试;
e@-@ jYoHER0不断总结出现 bug 的原因,对其他代码编写相应测试用例;51Testing软件测试网te g4m3Y
每次编写完成代码,运行所有以前的测试用例,验证对以前代码影响,把这种影响尽早消除;
^sl.~SO#b0不断维护测试代码,保证代码变动后通过所有测试;
p,U/W3@b8a
Z\)o0有上面的理论做指导,测试行为就可以有规可循。那么 CppUnit 如何实现这种测试框架,帮助我们管理测试代码,完成自动测试的?下面就看看 CppUnit 的原理。51Testing软件测试网rw
Z2}$K(hi#Av3vq
v
\RF5C|%`@6k^02. CppUnit 的原理
0yzAc9N&F_`+N4N3\051Testing软件测试网P!ngU(I:H在 CppUnit 中,一个或一组测试用例的测试对象被称为 Fixture(设施,下文为方便理解尽量使用英文名称)。Fixture 就是被测试的目标,可能是一个对象或者一组相关的对象,甚至一个函数。51Testing软件测试网r.\ QNm+F6M
51Testing软件测试网"Mb1u([ A D有了被测试的 fixture,就可以对这个 fixture 的某个功能、某个可能出错的流程编写测试代码,这样对某个方面完整的测试被称为TestCase(测试用例)。通常写一个 TestCase 的步骤包括:
AuIo&W|h:?051Testing软件测试网w3l Q`*f~0s对 fixture 进行初始化,及其他初始化操作,比如:生成一组被测试的对象,初始化值;
+gf*M4fa4lU0按照要测试的某个功能或者某个流程对 fixture 进行操作;
3GFE%M)EX"p[0验证结果是否正确;51Testing软件测试网)?,H)O VYa/X:Pb
对 fixture 的及其他的资源释放等清理工作。51Testing软件测试网+{b0Mr~"A+Y9W(}8t%j
对 fixture 的多个测试用例,通常(1)(4)部分代码都是相似的,CppUnit 在很多地方引入了 setUp 和 tearDown 虚函数。可以在 setUp 函数里完成(1)初始化代码,而在 tearDown 函数中完成(4)代码。具体测试用例函数中只需要完成(2)(3)部分代码即可,运行时 CppUnit 会自动为每个测试用例函数运行 setUp,之后运行 tearDown,这样测试用例之间就没有交叉影响。
对 fixture 的所有测试用例可以被封装在一个 CppUnit::TestFixture 的子类(命名惯例是[ClassName]Test)中。然后定义这个fixture 的 setUp 和 tearDown 函数,为每个测试用例定义一个测试函数(命名惯例是 testXXX)。下面是个简单的例子:51Testing软件测试网2f%gw3nl{0hd#G`
51Testing软件测试网biU%RA!P2p Q
b%qq;T0S7}0class MathTest : public CppUnit::TestFixture {51Testing软件测试网7~-EN6D\j u
protected:51Testing软件测试网1r |,{(FB"{_
int m_value1, m_value2;
z$RA#K5j W2n0Xe(O0public:51Testing软件测试网 aW_U\1x7Jg
MathTest() {}
H4y] |N0 // 初始化函数51Testing软件测试网G~SK2Q3x
void setUp () {51Testing软件测试网(_)Uvu4@?
m_value1 = 2;
u;^(R
Xf[M'e0 m_value2 = 3;
|a aUSB*zCJ/s0 }
J+nhI[S)Rk0 // 测试加法的测试函数51Testing软件测试网
?'BYE\9N2mF{vD
void testAdd () {51Testing软件测试网|4g;s jS,g%M*p;ye#E
// 步骤(2),对 fixture 进行操作51Testing软件测试网;TB x y7D e*\&`_(~s5Z!U
int result = m_value1 + m_value2;
YUzNb
Cn*x
y#{0 // 步骤(3),验证结果是否争取51Testing软件测试网!}
X9?yP
CPPUNIT_ASSERT( result == 5 );51Testing软件测试网qG8rd`+o^f'H
}
zRT$wrd\0 // 没有什么清理工作没有定义 tearDown.
3v!J9N#{3D
G0}
E"hJ%S)VWp*P0
J3J_
o*AH}0 51Testing软件测试网T4JQ8__"?R`,q
1IU!iB$a/jXzl+u[0在测试函数中对执行结果的验证成功或者失败直接反应这个测试用例的成功和失败。CppUnit 提供了多种验证成功失败的方式:51Testing软件测试网+g1d6j)dU,r$h-r
_9j
Um{4UF'kD&g3U0 CPPUNIT_ASSERT(condition) // 确信condition为真
t qg)I5~.S||0 CPPUNIT_ASSERT_MESSAGE(message, condition) // 当condition为假时失败, 并打印message51Testing软件测试网/ZgQ.ih-b$@
CPPUNIT_FAIL(message) // 当前测试失败, 并打印message
M:l+QP8~f0 CPPUNIT_ASSERT_EQUAL(expected, actual) // 确信两者相等
6^'b9Y+}@^0 CPPUNIT_ASSERT_EQUAL_MESSAGE(message, expected, actual) // 失败的同时打印message
H
T5kXm+SN0 CPPUNIT_ASSERT_DOUBLES_EQUAL(expected, actual, delta) // 当expected和actual之间差大于delta时失败51Testing软件测试网R!}N6XZo
51Testing软件测试网0N#S3T
L.t.AJx
/Lr-hX*BG'D!l&r0要把对 fixture 的一个测试函数转变成一个测试用例,需要生成一个 CppUnit::TestCaller 对象。而最终运行整个应用程序的测试代码的时候,可能需要同时运行对一个 fixture 的多个测试函数,甚至多个 fixture 的测试用例。CppUnit 中把这种同时运行的测试案例的集合称为 TestSuite。而 TestRunner 则运行测试用例或者 TestSuite,具体管理所有测试用例的生命周期。目前提供了 3 类TestRunner,包括:
0UN%M u-@d4Q0 CppUnit::TextUi::TestRunner // 文本方式的TestRunner
j!a,Rp&h.Sfm.@g0 CppUnit::QtUi::TestRunner // QT方式的TestRunner
v5DG E4qk_6{q0 CppUnit::MfcUi::TestRunner // MFC方式的TestRunner
$l7f6Z&w?:R0
Y]4z)[Y
K0
2UQKF'l0f
O0下面是个文本方式 TestRunner 的例子:51Testing软件测试网lm7Pv+JqU
51Testing软件测试网g HG
VH
CppUnit::TextUi::TestRunner runner;
|3R`){3]7B0 CppUnit::TestSuite *suite= new CppUnit::TestSuite();51Testing软件测试网Yu NI_m3xO
51Testing软件测试网e Z[C8ww({Pf
// 添加一个测试用例51Testing软件测试网
wqP,\*m6Z }8Wng
suite->addTest(new CppUnit::TestCaller<MathTest> (
"L#F8CeriJ
V"i0 "testAdd", testAdd));51Testing软件测试网qYJ s%hIDJ#L
d'm.QS?T.ky0 // 指定运行TestSuite51Testing软件测试网2_.j*K:Xbd#L-U;\Z
runner.addTest( suite );
8hJGgx(J9k4^
p7p3C0 // 开始运行, 自动显示测试进度和测试结果
B;uJNB]G3ly0 runner.run( "", true ); // Run all tests and wait51Testing软件测试网g xh3SQ6`9d\ k
51Testing软件测试网~|Y9F"Ksz E
51Testing软件测试网j GA$~`,W/o
对测试结果的管理、显示等功能涉及到另一类对象,主要用于内部对测试结果、进度的管理,以及进度和结果的显示。这里不做介绍。51Testing软件测试网D/z.n3PP0U(j+]U+HV
;w7ei#L} m]zG0下面我们整理一下思路,结合一个简单的例子,把上面说的思路串在一起。
/Si {qs-h?0f4k)O'C3Z%oR0
Y't4cyg Q!d1@ t051Testing软件测试网,SNb4z#bh3. 手动使用步骤
xF4mw4O!C*g p;n051Testing软件测试网B8~`X2X!V首先要明确测试的对象 fixture,然后根据其功能、流程,以及以前的经验,确定测试用例。这个步骤非常重要,直接关系到测试的最终效果。当然增加测试用例的过程是个阶段性的工作,开始完成代码后,先完成对功能的测试用例,保证其完成功能;然后对可能出错的部分,结合以前的经验(比如边界值测试、路径覆盖测试等)编写测试用例;最后在发现相关 bug 时,根据 bug 完成测试用例。51Testing软件测试网%_2SD5i Z9W!cI`
X-h*h{g x-eY}#{0比如对整数加法进行测试,首先定义一个新的 TestFixture 子类,MathTest,编写测试用例的测试代码。后期需要添加新的测试用例时只需要添加新的测试函数,根据需要修改 setUp 和 tearDown 即可。如果需要对新的 fixture 进行测试,定义新的 TestFixture 子类即可。注:下面代码仅用来表示原理,不能编译。51Testing软件测试网G{7v+^ Anl;~*R
51Testing软件测试网4f li$cG5}C
0}Il\W,_0/// MathTest.h
!Ad2Y Wku
Pe(Le0// A TestFixture subclass.51Testing软件测试网:g|WB*K
// Announce: use as your owner risk.51Testing软件测试网Ea7c n)~9O
// Author : liqun (liqun@nsfocus.com)
1u;w'q+jL4q)d;v;L4c0// Data : 2003-7-551Testing软件测试网P
k5vM)v4n0H
#include "cppunit/TestFixture.h"51Testing软件测试网w&agq,si2q{
class MathTest : public CppUnit::TestFixture {51Testing软件测试网PHi_
U-M-|
protected:51Testing软件测试网rZ
VlK
int m_value1, m_value2;51Testing软件测试网(c7P'\?-~
51Testing软件测试网,@.[E"Q Ecc"[:DD;j
public:51Testing软件测试网X4@Li5DV
MathTest() {}
.y&j,v'@8xa(F i/Y@"l0
f
FIn@E1\0 // 初始化函数51Testing软件测试网NYlc U
V$^
void setUp ();51Testing软件测试网Y!OJ l Q @(s
// 清理函数
.b
o0Q\/lF0 void tearDown();51Testing软件测试网 IL|:UP,Y*d:Y_
C
/b z!O/Wyi*pW0 // 测试加法的测试函数51Testing软件测试网:I;hh+V Y:j&M9wxd
void testAdd ();
-V7h0Q,T
Y2J]4Rqw/|0 // 可以添加新的测试函数
y"A?\Yc0};51Testing软件测试网A'st+]%m
/// MathTest.cpp51Testing软件测试网J
q-L0bt$\
// A TestFixture subclass.
X-c)C'L
mPS_0// Announce: use as your owner risk.
&t NO"^
^,g9~0// Author : liqun (liqun@nsfocus.com)
/fA8A8O:e7]@l2]0// Data : 2003-7-5
*YC
H-V%g0#include "MathTest.h"51Testing软件测试网)tH
r_5T
U2fRM"S(l3G,a
#include "cppunit/TestAssert.h"51Testing软件测试网)[$\y,[N]1tTI*I`
void MathTest::setUp()51Testing软件测试网-n'~2}?&r#U4gF:J
{
r!V(nuJ9F9q0 m_value1 = 2;51Testing软件测试网{*w/_:w D+g1w e
m_value2 = 3;51Testing软件测试网}4aX#I(t%r E
}
$xgj W:E3ZK9m.@ C4pO0void MathTest::tearDown()51Testing软件测试网;CU3Y-t:m7Gk~
{
EL&p d}bjWxR0}51Testing软件测试网"zbjx!h${;G1M~
void MathTest::testAdd()51Testing软件测试网`,tnuf
{51Testing软件测试网5w
kX^hBS1s
int result = m_value1 + m_value2;51Testing软件测试网Q$e8L1`@V
CPPUNIT_ASSERT( result == 5 );
BZ.k-t'QgA1S0}51Testing软件测试网?*T#ow*T
yr
51Testing软件测试网U^3Y#JU"i~#@
51Testing软件测试网6D{wu4nXL9{