编写综合的单元测试-2
上一篇 / 下一篇 2012-07-04 11:59:49 / 个人分类:杂谈
测试也有代码味道51Testing软件测试网S C-t-uxj
^P,R/QK v0 回看第一个测试用例的8个测试,它们都有同样的设置和运算。唯一的不同是我们写的断言。在业界这个被称为一个代码味道。事实上,根据维基百科所列的这里应该有两个代码味道:
~w5N i"HzB!f;n+[I%k0FW1]'SrH2w-~0 ● Duplicated code51Testing软件测试网4UBd ?4r.g;cU
J"S+B} ^M]"S9^a0 ● 重复的代码51Testing软件测试网(C uTp h6u`
YMl&FwW0 ● Excessively long identifiers
(Ce&| c'fN[051Testing软件测试网(La(hq9yT● 过长的标识符
6I8VV bmt x/a051Testing软件测试网O%@(^"z-v我们可以通过将断言合并到一个测试来轻松地消除这两个代码味道:
%q'K5},b7Y0!Y#N7cq2N/v0
.G%gGZPQ0s0[TestMethod]51Testing软件测试网-X$?H:O-leA public voidPerson_FirstName_Set() m ds:wK+OA0{ /n CAW"P-a)|#_q_ f0 varperson =newPerson("Adam","Smith"); V#H3_f&s7iZ0 vareventAssert =newPropertyChangedEventAssert(person);51Testing软件测试网vR(^ C'Gr1X varerrorsChangedAssert =newErrorsChangedEventAssert(person);51Testing软件测试网6tf/dug varchangeAssert =newChangeAssert(person);51Testing软件测试网 TrW^Z#Wr person.FirstName ="Bob"; F'F7A+Sl'o"E/R0 Assert.AreEqual("Bob", person.FirstName,"FirstName setter failed");51Testing软件测试网n EsG6m ` Assert.AreEqual("Bob Smith", person.FullName,"FullName not updated with FirstName changed"); f8Po!TK"E0 Assert.IsTrue(person.IsChanged,"IsChanged flag was not set when FirstName changed");51Testing软件测试网3m5B%yy6[o'BVe$^ eventAssert.Expect("IsChanged");51Testing软件测试网D$A(fWJJ eventAssert.Expect("FirstName");51Testing软件测试网Qxu(v[Z$n{K2B eventAssert.Expect("FullName"); |1U9QUIy'G0 errorsChangedAssert.ExpectNothing("Expected no ErrorsChanged events");51Testing软件测试网[n%h:R q:l4o changeAssert.AssertOnlyChangesAre("FirstName", "FullName", "IsChanged");51Testing软件测试网)T ?:u$k_&Oyp } |
m^'jO3a{ N.w0 知道什么导致测试失败很重要,因此我们在断言里添加失败的信息提示。51Testing软件测试网!@cz4@cb];h RP
YO%TRZx0 单元测试和代码重用
#r'NTW+Q#X o0g1y4P O5b9Bi)I0 回看那27个测试用例,我们可以断定设置FirstName为null或者空串应该也需求同样的测试。因此我们可以扩展成:51Testing软件测试网C)e} Qs0[/wg&o_z
51Testing软件测试网@-Bb$_(d*V O*T51Testing软件测试网 i.[!q S;d$|\)r
[TestMethod]51Testing软件测试网@9Q,V&RF\public voidPerson_FirstName_Set_Empty()
Q A+B-p@N0{51Testing软件测试网BX&\'z*E
Person_FirstName_Set_Invalid(String.Empty);51Testing软件测试网 s(ZWy*k5`
}
Y d/H-bE(x`wy0[TestMethod]51Testing软件测试网|5F;kH(JKitIZ
public voidPerson_FirstName_Set_Null()
e9DR\AC/p#q0{
"p Se$s"RC:t[%L"f0 Person_FirstName_Set_Invalid(null);51Testing软件测试网:N&{;K(C4X%|
}51Testing软件测试网-B'r3e5b|li
public voidPerson_FirstName_Set_Invalid(stringfirstName)
5{'Z9X4z ^^ n0{
T,hGGc-ij)c0 varperson =newPerson("Adam","Smith");51Testing软件测试网_p%t9t h#_8W
vareventAssert =newPropertyChangedEventAssert(person);
&Jr"X:M3P*H*VJ P0 varerrorsChangedAssert =newErrorsChangedEventAssert(person);
1zizQFGU0 varchangeAssert =newChangeAssert(person);
3LJ/bA0n*gM4I0 Assert.IsFalse(person.IsChanged,"Test setup failed, IsChanged is not false");51Testing软件测试网tg"n~ IAW[ w"]
Assert.AreEqual("Adam", person.FirstName,"Test setup failed, FirstName is not Adam");51Testing软件测试网n|/bD xo.? {
Assert.AreEqual("Smith", person.LastName,"Test setup failed, LastName is not Smith");
0R%v;o&_(Vaz,V0 person.FirstName = firstName;
(NF'D mGU S0 Assert.AreEqual(firstName , person.FirstName,"FirstName setter failed");51Testing软件测试网,X\`5u[a
Assert.AreEqual("Smith", person.FullName,"FullName not updated with FirstName changed");
&y6l!ou%p Hb6J"d;Q0 Assert.IsTrue(person.IsChanged,"IsChanged flag was not set when FirstName changed");
?"p0`'iEK EB0 eventAssert.Expect("IsChanged");51Testing软件测试网%^ t4?2S3oR"I
eventAssert.Expect("FirstName");
x)v_,Qozec0 eventAssert.Expect("FullName");51Testing软件测试网*`N_{"xCcQ6F
Assert.IsTrue(person.HasErrors,"HasErrors should have remained false");51Testing软件测试网u,Cd;e+Npa
errorsChangedAssert.ExpectCountEquals(1,"Expected an ErrorsChanged event");51Testing软件测试网 uc_T"{IB
changeAssert.AssertOnlyChangesAre("FirstName","FullName","IsChanged","HasErrors");51Testing软件测试网#[&EoI2[_Zf
}
!kp3U?2Oo051Testing软件测试网.dA F b#]$Iz:J
可以发现Person_FirstName_Set和Person_FirstName_Set_Invalid的差异很小,我们可以进一步试着通用化:
)E#S/f(a$c!i-P.ez ed)^051Testing软件测试网8r.FA7L*Vx?Y(R9y-c[TestMethod]51Testing软件测试网#Bu~6jn;e)A(~'iI0F+]
51Testing软件测试网;FGtc}ibP51Testing软件测试网aH'x1K#{+Yo%f"q
public voidPerson_FirstName_Set_Valid() 2_?h~ IJ#d'r q0{51Testing软件测试网E+~1I @8iXCPN!U5T Person_FirstName_Set("Bob",false); 1g}3Q2RB;NteZ$I0} Wd0V5M+dvu@ Z0[TestMethod] R7xd$}"w^;?0public voidPerson_FirstName_Set_Empty()51Testing软件测试网P3B(R ?-On { U&}5Ie5K1M0 Person_FirstName_Set(String.Empty,true); Y5w PYI1\0}51Testing软件测试网~7s$p+`b [TestMethod]51Testing软件测试网#VOtW*u)l(aqbDv public voidPerson_FirstName_Set_Null() KK%Of(dw\@0{ qo#L;F/vsJ0 Person_FirstName_Set(null,true); %J-Mt%P;G x \\-Y0} b@?0E]#h)L0public voidPerson_FirstName_Set(stringfirstName,boolshouldHaveErrors)51Testing软件测试网JvYUNtT {51Testing软件测试网7s-h{D)bS varperson =newPerson("Adam","Smith");51Testing软件测试网%s5qWVZ)K vareventAssert =newPropertyChangedEventAssert(person); 'X ma1Zu wAE0 varerrorsChangedAssert =newErrorsChangedEventAssert(person); Ly.o+V.|;x@0 varchangeAssert =newChangeAssert(person); &BW }[@0 Assert.IsFalse(person.IsChanged,"Test setup failed, IsChanged is not false");51Testing软件测试网9aUl4n;U Assert.AreEqual("Adam", person.FirstName,"Test setup failed, FirstName is not Adam");51Testing软件测试网jd3a8^c2U g1|J Assert.AreEqual("Smith", person.LastName,"Test setup failed, LastName is not Smith");51Testing软件测试网DLG WPjG person.FirstName = firstName; `\K*vj^0 Assert.AreEqual(firstName, person.FirstName,"FirstName setter failed");51Testing软件测试网]$oky@E5p ~ Assert.AreEqual((firstName +" Smith").Trim(), person.FullName,"FullName not updated with FirstName changed");51Testing软件测试网gun|jHH d Assert.AreEqual(true, person.IsChanged,"IsChanged flag was not set when FirstName changed");51Testing软件测试网f5v }y} eventAssert.Expect("IsChanged");51Testing软件测试网x x[Fq.L'\(KmYQ eventAssert.Expect("FirstName");51Testing软件测试网"Vh9m$GdR eventAssert.Expect("FullName");51Testing软件测试网*[*C7PKy q6_K[c if(shouldHaveErrors) t3F;k!@iW.P0 {51Testing软件测试网L Jh;b9r6q7U n'_'^YO Assert.IsTrue(person.HasErrors,"HasErrors should have remained false");51Testing软件测试网E)t|6Q#I:JP,D errorsChangedAssert.ExpectCountEquals(1,"Expected an ErrorsChanged event");51Testing软件测试网(}7xvx3c changeAssert.AssertOnlyChangesAre("FirstName","FullName","IsChanged","HasErrors");51Testing软件测试网Qq!qZ!Q9ac }51Testing软件测试网{OtY.|y"bNL]%w x else zP0_%L~3bR0 { bC+IJi P |/W5S0 errorsChangedAssert.ExpectNothing("Expected no ErrorsChanged events");51Testing软件测试网O3D7mU aqm changeAssert.AssertOnlyChangesAre("FirstName","FullName","IsChanged");51Testing软件测试网Pze7n:o:t^^ }51Testing软件测试网+Wk-g#L0v{ } |
在测试代码变得令人迷惑之前,我们可以把它通用化什么程度,这里绝对有个限制。但是一个有意义的测试名称,并给每个断言配一个好的描述可以让你的测试更加容易让人理解。
g;l#Pba0zTqp1Kq0 控制变量
G7R7@$i1d;FR9Ja]0!}m;rrPy0 目前所有的断言都只考虑到了测试用例的输出。他们假设每个Person对象初始状态已知,然后从此出发进行其他操作。但是如果我们想让测试更具科学性,必须确保我们能控制变量。或者换句话说,我们需要保证,一切在掌握之中。
9U)VRBT1T051Testing软件测试网 gS2~ C,s[ J请看下面一组断言:51Testing软件测试网;SrIf}
51Testing软件测试网jZ-nE4\Ve@4S8qV,[0Assert.IsFalse(person.HasErrors,"Test setup failed, HasErrors is not false");51Testing软件测试网"y~"Z$_*n*b Assert.IsFalse(person.IsChanged,"Test setup failed, IsChanged is not false");51Testing软件测试网e%R8_R-kx+D7r Assert.AreEqual("Adam", person.FirstName,"Test setup failed, FirstName is not Adam"); Oa,z,Z+|?0Assert.AreEqual("Smith", person.LastName,"Test setup failed, LastName is not Smith"); |
51Testing软件测试网zR$x'O.F_
由于我们不想在每个测试的开始重复这些断言,我们可以选择把他们移到一个工厂方法中,这样我们可以保证总是拿到一个干净的对象。这个同样适用于重用这些设置去测试其他属性的测试用例。51Testing软件测试网/D/G ^:NU7C\,x#R
51Testing软件测试网Z'I,wD:sA[TestMethod]51Testing软件测试网%a,nv+{*U0{bw(B3q Aa
0G7ne4Q K2nA|8F051Testing软件测试网*j&nb\&j
public voidPerson_FirstName_Set() -j aqppZ{0{51Testing软件测试网AD9p#}tTl2B varperson = GetAdamSmith();51Testing软件测试网)Ae R/jdYB~ ... |
表格式的测试
|$y8L y`wO@{b*C(Q}z051Testing软件测试网2@T#Zz0H;cj9v~t wG之所以走到这一步,是因为“测试方法”的数量跟测试的完善程度没有关系。它们只是组织和执行测试用例一种比较方便的方式。51Testing软件测试网(J.X.ekX(j|i5A|
'XF S!]/w!iU M3~R0 另一个组织大量测试用例的方法是表格驱动测试法。不能执行单个测试,但是仅用一行代码就可以增加新的测试用例。表格式测试里的表格可以来源于 XML的文件,数据库表,写死在数组里或者只是使用同一个函数用不同的值反复调用。一些框架如MBTest甚至可以让你用属性给出测试用例,但是为了让例 子轻便,我们还是坚持保持最低的共同部分。
C3K4[|[cE&G0"@afRV051Testing软件测试网@?O6YeF}z
[TestMethod] 3{u2ma0CT#A/E8V-N0public voidPerson_FullName_Tests() !~JGoWiI)g0{ 51Testing软件测试网(h'q7E.Yy5np Person_FullName_Test("Bob","Jones","Bob Jones"); |