编写综合的单元测试-2

上一篇 / 下一篇  2012-07-04 11:59:49 / 个人分类:杂谈

51Testing软件测试网HQ9xt)mJ\X

  测试也有代码味道51Testing软件测试网S C-t-uxj

^P,R/QK v0  回看第一个测试用例的8个测试,它们都有同样的设置和运算。唯一的不同是我们写的断言。在业界这个被称为一个代码味道。事实上,根据维基百科所列的这里应该有两个代码味道:

~w5N i"HzB!f;n+[I%k0

FW1]'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软件测试网(L a(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 d s:wK+OA0{
/nCAW"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软件测试网Q xu(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 R P

YO%TRZx0  单元测试和代码重用

#r'NTW+Q#X o0

g1y4PO5b9Bi)I0  回看那27个测试用例,我们可以断定设置FirstName为null或者空串应该也需求同样的测试。因此我们可以扩展成:51Testing软件测试网C)e } Qs0[/wg&o_z

51Testing软件测试网@-Bb$_(d*V O*T

51Testing软件测试网 i.[!q S;d$|\)r

[TestMethod]51Testing软件测试网@9Q,V&RF\
public voidPerson_FirstName_Set_Empty()
QA+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;k H(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%t9th#_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&_(Va z,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`'i EK 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软件测试网 u c_T"{IB
     changeAssert.AssertOnlyChangesAre(
"FirstName","FullName","IsChanged","HasErrors");51Testing软件测试网#[&EoI2[_Zf
}
!kp3U?2Oo0
51Testing软件测试网.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}ibP

51Testing软件测试网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+dv u@ Z0[
TestMethod]
R7xd$}"w^;?0
public voidPerson_FirstName_Set_Empty()51Testing软件测试网P3B(R ?-On
{
U&}5Ie5K1M0     Person_FirstName_Set(
String.Empty,true);
Y5w PY I1\0}51Testing软件测试网~7s$p+`b
[
TestMethod]51Testing软件测试网#VO tW*u)l(aqbDv
public voidPerson_FirstName_Set_Null()
KK%O f(dw\@0{
qo#L;F/vsJ0     Person_FirstName_Set(
null,true);
%J-Mt%P;G x \\-Y0}
b @?0E ]#h)L0
public voidPerson_FirstName_Set(stringfirstName,boolshouldHaveErrors)51Testing软件测试网Jv YUNtT
{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软件测试网DLGWPjG
     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软件测试网*[*C7PKyq6_K[c
     
if(shouldHaveErrors)
t3F;k!@iW.P0     {51Testing软件测试网LJh;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软件测试网(}7xv x3c
          changeAssert.AssertOnlyChangesAre(
"FirstName","FullName","IsChanged","HasErrors");51Testing软件测试网Qq!qZ!Q9ac
     }51Testing软件测试网{OtY.| y"bNL]%w x
     
else
z P0_%L~3bR0     {
bC+IJi P|/W5S0          errorsChangedAssert.ExpectNothing(
"Expected no ErrorsChanged events");51Testing软件测试网O3D7mUaqm
          changeAssert.AssertOnlyChangesAre(
"FirstName","FullName","IsChanged");51Testing软件测试网Pze7n:o:t^^
     }51Testing软件测试网+Wk-g#L0v{
}
51Testing软件测试网4|\'Muh%i

  在测试代码变得令人迷惑之前,我们可以把它通用化什么程度,这里绝对有个限制。但是一个有意义的测试名称,并给每个断言配一个好的描述可以让你的测试更加容易让人理解。

g;l#Pba0

zTqp1Kq0  控制变量

G7R7@$i1d;FR9Ja]0

!}m;rrPy0  目前所有的断言都只考虑到了测试用例的输出。他们假设每个Person对象初始状态已知,然后从此出发进行其他操作。但是如果我们想让测试更具科学性,必须确保我们能控制变量。或者换句话说,我们需要保证,一切在掌握之中。

9U)VRB T1T051Testing软件测试网 gS2~ C,s[ J

  请看下面一组断言:51Testing软件测试网;SrIf}

51Testing软件测试网jZ-nE4\V

e@4S8qV,[0
Assert.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+|?0
Assert.AreEqual("Smith", person.LastName,"Test setup failed, LastName is not Smith");
51Testing软件测试网,EH]@*R"i
51Testing软件测试网zR$x'O.F _

  由于我们不想在每个测试的开始重复这些断言,我们可以选择把他们移到一个工厂方法中,这样我们可以保证总是拿到一个干净的对象。这个同样适用于重用这些设置去测试其他属性的测试用例。51Testing软件测试网/D/G ^:NU7C\,x#R

51Testing软件测试网Z'I,wD:sA

  [TestMethod]51Testing软件测试网%a,nv+{*U0{bw(B3qAa

0G7ne4Q K2n A|8F051Testing软件测试网*j&nb\&j

public voidPerson_FirstName_Set()
-j aq ppZ{0{51Testing软件测试网AD9p#}tTl2B
     
varperson = GetAdamSmith();51Testing软件测试网)Ae R/jdYB~
     ...
51Testing软件测试网+M$O"y?/W/OOd|

  表格式的测试

|$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-N0
public voidPerson_FullName_Tests()
!~JGoWiI)g0{     51Testing软件测试网(h'q7E.Yy5np
     Person_FullName_Test(
"Bob","Jones","Bob Jones");
T&O7T*}t~2|8ku0     Person_FullName_Test(
"Bob ","Jones","Bob Jones");
b5K$[^ CY+?0     Person_FullName_Test(
" Bob","Jones","Bob Jones");
l{xhwJ!b C+R0     Person_FullName_Test(
"Bob"," Jones","Bob Jones");51Testing软件测试网6d,?g5Kf;Da L
     Person_FullName_Test(
"Bob","Jones ","Bob Jones");51Testing软件测试网2|G4t+Q:tFr
     Person_FullName_Test(
null,"Jones","Jones");
#H X_2@P0     Person_FullName_Test(
string.Empty,"Jones","Jones");
q;R3UhM/{;{p3{0     Person_FullName_Test(
"      ","Jones","Jones");
U f(KNw0     Person_FullName_Test(
"Bob","","Bob");
we%j0xz'{:BX0     Person_FullName_Test(
"Bob",null,"Bob");51Testing软件测试网wD"jfv?[
     Person_FullName_Test(
"Bob",string.Empty,"Bob");51Testing软件测试网1a*R k,A,AG9Kp
     Person_FullName_Test(
"Bob","      ","Bob");51Testing软件测试网)Qf%X3^*{
}
\'YXi6j0
private voidPerson_FullName_Test(stringfirstName,stringlastName,stringexpectedFullName)
3E}h{)k!h?*H'o{D0{51Testing软件测试网3{8uO?e3I4m
     
varperson = GetAdamSmith();
9aI$wHDdM7f {8W0     person.FirstName = firstName;
"o FR_7`%s1FO5mb0     person.LastName = lastName;
6]o$Hovs0V5^0     
Assert.AreEqual(expectedFullName, person.FullName,
j%l@'}0iw2z0          
string.Format("Incorrect full name when first name is '{0}' and last name is '{1}'"51Testing软件测试网tN;`(Ytz1K
          firstName ??
"", lastName ??""));
R^L/kc*s3b4e WZ0}

z2R-Y7j&c2]0  在运用这个技巧时,要使用带参数的错误信息,这很重要。如果不加,你会发现在定位哪些参数组合不对时,还得一步一步调试代码。

+h d W2^}&v y051Testing软件测试网l9y5C%}0Qg

  结论

\+q{N;A)i$S9y }@;V!O051Testing软件测试网|8Dc!W4[a b!|

  在为任何变量编写单元测试时,最好尝试最大化以下几个因素:

2eZuu&ps\ u051Testing软件测试网dU2e&cU

  ● 有意义的单位工作量测试覆盖率

)ZbQ:]/_*_eb&d051Testing软件测试网e9t%`r:Y

  ● 面对变动的代码基线时,保证可维护性51Testing软件测试网hg}M.v0f'kk

51Testing软件测试网e2H)V&]#y&B

  ● 测试套件的性能

3VGv:h'eUq(i0

:h:A_b6k9Z5}0  ● 明确说明测试什么以及为什么51Testing软件测试网M%s b`#sMF-U

;Hf2[4?cg\3d0  鉴于这些因素往往会冲突,谨慎地运用单个用例多重断言可提升上述四个方面,具体做法是: + 减少需要编写的样板代码量 + 减少因API更改而需要更新的样板代码量 + 减少每个断言需要执行的样板代码数量 + 将某一操作的所有断言,用文档记录在同一个地方。51Testing软件测试网cPa:?0Z]6RyW


:ju1t+@xl0

TAG:

 

评分:0

我来说两句

Open Toolbar