Google Test 测试架构探究
上一篇 / 下一篇 2012-08-09 10:01:18 / 个人分类:杂谈
-fB/q)s2Mc0 得益于和萱哥关于单元测试的聊天,让我开始想要了解Google的单元测试框架GoogleTest,(虽然以前也听勇哥提到过这个词,但是一直没有往心里去);以前基于C#反射特性Demo过一个C#的单元测试框架(http://www.51testing.com/?action-viewnews-itemid-818965),所以更想明白Google Test测试框架的实现机制;这期间搞过一段Python,看Python文档时也看到相关的单元测试框架PyUnit,但没有深入研究,有时间深入研究一下。
9`Z Wh OG0d1MP+p\@)j0 自动化测试框架,主要目的就是自动化调用执行某些测试用例,将执行结果与目标结果进行比较,用以达到测试特定目标的目的。而测试用例的针对目标可能是函数接口,功能模块,UI等等。51Testing软件测试网&j\2K-sxRf
5Kx)@WW6e0 自动化测试框架从功能上可以分为技术框架和执行框架。所谓技术框架一般针对于特定的测试目标,为了达到自动化测试而引入的技术或者方法,比如微软UI测 试中的MSAA,UI Automation框架以及后续建立在UIAutomation上的POM/LFM(详见References中微软UI自动化测试技术演进相关链 接)。而执行框架则侧重于对测试用例自动化执行的控制。这篇文章主要侧重于讨论执行框架,而自动化执行框架的设计中有以下几方面是必须考虑的:51Testing软件测试网2G}!J.E G8DK
m8}F/fj B.Rxl#r$S]'O0 1、测试用例选择控制。
yA1uKW(v_0e QOq*~(UP&^-H a0 2、自动化传递参数或者配置条件。51Testing软件测试网 A.V}O0V5kj-m:U
51Testing软件测试网p)?2s(U,|9O+Q(L/F*j3、测试用例执行结果比对。
K"tV\{hM)W051Testing软件测试网0H BfC5yg,g'pn4、测试结果记录与分析。51Testing软件测试网c7f v#hB3m
51Testing软件测试网T)g.|(g+Z/OJ测试用例选择控制
-Y;@3qdTD0k$O L}SOZ OZ0 测试用例由多人编写,且针对模块不同,再加上历史原因,诸如此类限制情况要选择特定测试用例执行。C#中常见做法是利用反射机制建立特性标签,选择带有特定标签的测试用例执行,而C++常见做法是给测试用例加字符串参数,执行时通过字符串参数进行选择。51Testing软件测试网rWZ3I]X
51Testing软件测试网N@~V:Wq^f'tS自动化传递参数或者配置条件51Testing软件测试网Z5W!v{8WM f
H)Si#@gOrv%t0 给被测试测试目标传递参数或配置文件是自动化测试框架必须有的特性,最可取的做法就是利用XML文档动态传参,好处是实现代码复用(针对同一个测试方法 传递不同测试参数),以及可以动态自适应测试参数的变化(代码不改动的情况下调整参数或者配置信息适应测试条件的改变)。51Testing软件测试网qJ*s6l;A,_,}"wRb
"Ou2HZUzz0 测试用例执行结果比对51Testing软件测试网,@p$C%N~ Qy
51Testing软件测试网IY~ t v~p0@:A,z测试比对一般代码中实现测试结果比对,但这涉及到目标结果也就是期望结果的取得的问题,其实和上述传参和配置条件属于同一个范畴。
MP\+T,gos4m051Testing软件测试网x^r+T5|&C测试结果记录与分析51Testing软件测试网:\ f5r M.g
51Testing软件测试网'O2]lR5c结果记录一般会将结果可视化输出到CMD窗口或者桌面窗口,XML文件,HTML文件,Excel文件等。不同测试框架实现有所不同,取决于需求。51Testing软件测试网 L ?eZ:L Z
51Testing软件测试网1P#O#u6d U(~3\Google Test51Testing软件测试网:duV%qr}1{
51Testing软件测试网#{#UEa c8e*}*h本来想Demo以下Google Test的实现,但是发现网上已经有人做过类似的事情,就把代码copy过来了,尊重作者意愿,将连接放至refereces。
zj4U*?:f051Testing软件测试网4me| R'D4cSGoolge Test架构主题设计很简单,将每一个测试用例封装到一个类TestCase的子类里。一个测试单元类UnitTest中将这些TestCase存入一个 Vector的数据结构中,使用RunTestCases()方法(原著中使用Run这个方法名起的不是很好容易与TestCase中Run()方法相 混,降低代码可读性)控制取出Vector中的测试用例来控制执行过程,结果比对等。51Testing软件测试网&VJq V]]#AF
51Testing软件测试网+`(@4J0@T@ Sy9s~ [这个架构最出彩的地方是使用宏定义掩盖了繁杂的测试用例封装过程,可谓是神来之笔,详尽代码请参考连接,于作者博客下载。
?#Z!JcOJ%P.K U0C S\!ji@h0class TestCase
T!o2H}6T4y6s0{
3P&Z|)k%iF1H8]0public:51Testing软件测试网|%S
I
gyn3v
TestCase(const char* case_name) : testcase_name(case_name){}
/FCYXc0`0 // 执行测试案例的方法
4i(GZlO-q[)h%m.AD)b`0 virtual void Run() = 0;
int nTestResult; // 测试案例的执行结果
z VDq"y2pB0 const char* testcase_name; // 测试案例名称
LK;Lp"B5S*FC0};
class UnitTest
'@;F;W)Kukc0{
)E/]*WF*Pv2o&[0public:51Testing软件测试网%S
Prd$Gg^;gQ;R
// 获取单例51Testing软件测试网2v*i6P%f1]tQG
static UnitTest* GetInstance();51Testing软件测试网1u%u.XQ/W,nI
0j#A E,y,o?g0 // 注册测试案例
mL;f)d$NcQ0 TestCase* RegisterTestCase(TestCase* testcase);
9P7C'i"OL%[aG0 51Testing软件测试网^&Xh ju,a
s
// 执行单元测试
S*]"W$S:{O wBG0 int RunTestCases();
A'T{!E#sa0 TestCase* CurrentTestCase; // 记录当前执行的测试案例51Testing软件测试网Zo%{/jM-hthZ'NE
int nTestResult; // 总的执行结果51Testing软件测试网
\
pP0D;y;Pf#n5[
int nPassed; // 通过案例数
/g"q*|"LD&Z.s@5q$I0 int nFailed; // 失败案例数51Testing软件测试网0_-Sj
m-DVH dV jN+u
protected:51Testing软件测试网7Hm&g8Z.@;l*@
std::vector<TestCase*> testcases_; // 案例集合51Testing软件测试网k1h,@a p/QA)w P
};
_!kD:xUM0// 以下这段宏定义掩盖了繁杂的测试用例封装过程51Testing软件测试网;E0Hxf$U:M5IG7s
#define TESTCASE_NAME(testcase_name) \51Testing软件测试网X ~&vleq7ytuU
testcase_name##_TEST //##的作用在于(token-pasting)符号连接操作符,即将宏定义的多个形参成一个实际参数名,在这里testcase_name_TEST
Oi.{QK%Ry(T0#define NANCY_TEST_(testcase_name) \51Testing软件测试网{N8r7J9MNM
class TESTCASE_NAME(testcase_name) : public TestCase \51Testing软件测试网^+[ChL7^6g
{ \
;e&d9Q._m:}9y(`0public: \51Testing软件测试网
R+O8Y(h'^
TESTCASE_NAME(testcase_name)(constchar* case_name) : TestCase(case_name){}; \51Testing软件测试网9N
t#C aT!s
virtualvoid Run(); \51Testing软件测试网NWaj,Y1t(BX8TEXR
private: \51Testing软件测试网'F}Y1GX_\4e
static TestCase*const testcase_; \51Testing软件测试网z"s5p2vF.vz Y:p5x2S
}; \
;BLR4P'^!{en0\51Testing软件测试网iy
io"O2Z
TestCase*const TESTCASE_NAME(testcase_name) \
x-r7m4K[?0 ::testcase_ = UnitTest::GetInstance()->RegisterTestCase( \
q]@V%h^ SU0 new TESTCASE_NAME(testcase_name)(#testcase_name)); \ 51Testing软件测试网y&r9Hx:j.F~
//#号的作用是(stringizing)字符串化操作符。其作用是:将宏定义中的传入参数名转换成用一对双引号括起来参数名字符串。("testcase_name").
`a(S%K1fs l6I0void TESTCASE_NAME(testcase_name)::Run()
Z3p*K
X/p2C/V0/*注意Run()后边没有{},之所以这么做是宏定义将测试用例放入到Run的方法主体里。例如51Testing软件测试网zKsc@ `wR
NTEST(FooTest_PassDemo)51Testing软件测试网ABgZ2M
{51Testing软件测试网 u~R w'Uch
EXPECT_EQ(3, Foo(1, 2));
_ _)qF8K#z3_&~o1c6z0 EXPECT_EQ(2, Foo(1, 1));51Testing软件测试网.ka1hjN?3Ny
}51Testing软件测试网8V~o~\sM
上述代码中EXPECT_EQ(2, Foo(1, 1));代码放入到Run的方法主体里。51Testing软件测试网%i0IjE kTz,v
*/ 51Testing软件测试网8b*C*\H2H*vw9G7h[ K
8^lRnRln2S0
W@0L*hC%J0n0#define NTEST(testcase_name) \51Testing软件测试网/Woy0vh,S:_
NANCY_TEST_(testcase_name)
DQ)H-b#Y
z&CB I1b)W0#define RUN_ALL_TESTS() \
4k6j4O0}!CE0 UnitTest::GetInstance()->RunCases();
3c#T.mf;a0#define EXPECT_EQ(m, n) \51Testing软件测试网
MFS8T
t \
if (m != n) \
Jz}O ?R%sd0 { \51Testing软件测试网!fR8W,x2u2iIV'hk@t
UnitTest::GetInstance()->CurrentTestCase->nTestResult = 0; \51Testing软件测试网2erx^q2Q;SB}
std::cout << red << "Failed" << std::endl; \51Testing软件测试网1U@*v `#xk
std::cout << red << "Expect:" << m << std::endl; \51Testing软件测试网 Y W CO-X7?
std::cout << red << "Actual:" << n << std::endl; \51Testing软件测试网.Q^[6@&lZX,t
}
例如以下测试Foo方法,NTEST(FooTest_PassDemo)就是创建一个名为FooTest_PassDemo_Test的子类,将宏定义的断言EXPECT_EQ()等放入Run()方法主题中。展开代码如下。51Testing软件测试网#^0{jhKH D q
51Testing软件测试网f0h2?3YYM51Testing软件测试网S*Fu.PE
/G+M9y2q"ooF*w0int Foo(int a, int b)51Testing软件测试网
`0l^-A^C 'MU/Lg,Xr4O0NTEST(FooTest_PassDemo)51Testing软件测试网RP)@U*DH+c
i // 将以上宏定义展开等价于以下代码。 6y\3i H'Kj*v051Testing软件测试网Rx_@bQ!Z9D*L\class FooTest_PassDemo_TEST : public TestCase (a$kZ_Ky0TestCase* const FooTest_PassDemo_TEST::testcase_ =
UnitTest::GetInstance()->RegisterTestCase(new
FooTest_PassDemo_TEST("FooTest_PassDemo"));51Testing软件测试网.U^ek![
SMg[ |