TDD从何开始-1

上一篇 / 下一篇  2012-06-26 09:25:22 / 个人分类:杂谈

 万事开头难。在TDD中,人们纠结最多的可能是这样一个问题:如何写第一个测试呢? 实际上要视不同的问题而定。如果问题本身是一个算法求解,或者是一个大系统中的小单元,那么可以从最简单、最直观的情况出发,这样有助于快速建立信心,形 成反馈周期。但是在实际的开发中,很多时候我们拿到的就是一个“应用级”的需求:一个网上订票系统,一个网上书店,愤怒的小鸟,诸如此类。此时,我们如何 TDD呢?一种很自然的想法是:51Testing软件测试网%l#gsio@g,I(^D

  先对系统做简单的功能分解,形成概念中的相互协作的小模块。然后再从其中的一个小模块开始(往往是最核 心的业务模块)TDD。我们把这种方式权且称为inside-out,也就是从部分到整体。这种方式可能存在的风险是:即使各个部分都通过TDD的方式驱 动出来,我们也不能保证它们一起协作就能是我们想要的那个整体。更糟糕的是,直到我们把各个部分完成之前,我们都不知道这种无法形成整体的风险有多大。因 此这对我们那个“概念中模块设计”提出了很高的要求,并且无论我们当前在实现哪个模块,都必须保证那个模块是要符合概念中的设计的。51Testing软件测试网F BmP9dvD@n.Z

f)H/dE(\,XjS7X5O'uE0  如果换一种思路呢?与其做概念中的设计,不如做真正的设计,通过写测试的方式驱动出系统的各个主要模块及其交互关系,当测试完成并通过,整个应用的“骨架”也就形成了。51Testing软件测试网dI#T]-o I5H0K3jn

51Testing软件测试网,AVMD!q5AO

   例如,现在假设我们拿到一个需求,要实现一个猜数字的游戏。游戏的规则很简单,游戏开始后随机产生4位不相同的数字(0-9),玩家在6次之内猜出这个 4位数就算赢,否则就算输。每次玩家猜一个4位数,游戏都会告诉玩家这个4位数与正确结果的匹配情况,以xAyB的形式输出,其中x表示数字在结果中出 现,并且出现的位置也正确,y表示数字在结果中出现但位置不正确。如果玩家猜出了正确的结果,游戏结束并输出“You win”,如果玩家输,游戏结束并输出“You lose”。

{BZ z {w:c/R.o051Testing软件测试网6P$sv&ci(W

  针对这样一个小游戏,有人觉得简单,有人觉得复杂,但无论如何我们都没有 办法一眼就看到整个问题的解决方案。因此我们需要理解需求,分析系统的功能:这里需要一个输入模块,那里需要一个随机数产生模块,停!既然已经在做分析 了,为什么不用测试来记录这一过程呢?当测试完成的时候,我们的分析过程也就完成了。51Testing软件测试网(\-xFbn+lb

W,H*Z_L{ G0  好吧,从何开始呢?TDD有一个很重要的原则-反馈周期,反馈周期不能太长,这样才能合理的控制整个TDD的节奏。因此我们不妨站在玩家的角度,从最简单的游戏过程开始吧。

G/a Vc9\Be051Testing软件测试网W}d ~2b_c

  最简单的游戏过程是什么呢?游戏产生4位数,玩家一把猜中,You win,游戏结束。51Testing软件测试网4rD8@ D&i&m2\

t2M6s;{C$PYs0  现在开始写这个测试吧。有一个游戏(Game),游戏开始(start):51Testing软件测试网|M~0C!l_WI

Game game =newGame();
game.start();
51Testing软件测试网$}'_| MzDRD|!H B

  等等,似乎少了什 么,是的,为了产生随机数,需要有一个AnswerGenerator;为了拿到用户输入,需要有一个InputCollector;为了对玩家的输入进 行判断,需要有一个Guesser;为了输出结果,需要有一个OutputPrinter。真的要一口气创建这么多类,并一一实现它们吗?还好有 mock,它可以帮助我们快速的创建一些假的对象。这里我们使用JMock2:

Ua I0m&H"wSZFjV0
Mockery context = new JUnit4Mockery() {                       
g;N}(S4L#E8y0    {                                              51Testing软件测试网1Wp ]V?m"RA@(K P
        setImposteriser(ClassImposteriser.INSTANCE);
!a` `D,H1S(O0    }                                              51Testing软件测试网U/G*Y$lze^,J5g)N
};                                                 
-Y.c~ ZjZ%cb*R0final AnswerGenerator answerGenerator = context.mock(AnswerGenerator.class);51Testing软件测试网 bQk8M8d8}
    
51Testing软件测试网%vz G,I3n

  然后我们测试里的Game就变成这个样子了:51Testing软件测试网D:k7|*c4xeN

Game game =newGame(answerGenerator, inputCollector, guesser, outputPrinter);
game.start();

Nv5||1S$b R(]x0J0  注意到这里为了通过编译,需要定义上面提到的几个类,我们不妨以最快的方式给出空实现吧:

1o$\:Tkp*u0vO0

u/wj5ps5l)Sk0public class AnswerGenerator {51Testing软件测试网ZQ$g#r u/@1zZ r.s5W
   51Testing软件测试网n7[Fl0d'QjL
}
51Testing软件测试网)tYOb}

51Testing软件测试网 rk7Dp { D S

public class InputCollector {
U5V {;Tm3f0   
*rn!a8dMR(y0}
51Testing软件测试网6X8@5J_ ^)J$U1LD

51Testing软件测试网,en C7k!@~9LV

public class Guesser {
Cg2de1g4l0   51Testing软件测试网~EdiNr7O
}

n;L1j6b5XN8O0|0

N4t4[ yTp*|g0public class OutputPrinter {51Testing软件测试网|H7I3a6F8Dr
   51Testing软件测试网YcVq[n o8OL9E
}
51Testing软件测试网J-cg7`0{IMSFw

51Testing软件测试网5ET Z"n$FN X

  以及为了通过编译而需要的Game的最简单版本:

b0oW^(g9UB/i1LX0public class Game {
vf a{`0    public Game(AnswerGenerator generator, InputCollector inputCollector, Guesser guesser, OutputPrinter outputPrinter) {
8fS&j B0S0       51Testing软件测试网 K6M8bnG%z%VWjY&yg
    }51Testing软件测试网d5XFO/a9d
   51Testing软件测试网#[R5?["Rv$l
    public void start() {
rlP)~0f0       51Testing软件测试网\ c%R b)C I/u2O
    }51Testing软件测试网n x ? ? k`6F
}
3w#o0IT1N ~*s0 好了,下面可以走我们的那个最简单的流程了。首先是由answerGenerator产生一个4位数,不妨假定是1234:
51Testing软件测试网%Y`.p&e|f6Q O

51Testing软件测试网 dA#jv k,ly2M!He7q

context.checking(newExpectations() {  
;x8XM2F;{0    {                                  
pNM#GTq,`J\.a0        one(answerGenerator).generate();51Testing软件测试网uV6jm&Hu6};?
        will(returnValue(
"1234"));     
)})nya^:F X]|;|1z0    }51Testing软件测试网Z&N)ko;WOz1d6O\e
});
51Testing软件测试网4v7e&G9Yx?.[5d#n

  这里需要我们的generator有一个generate方法,我们给一个最简单的空实现:

\}fT9RFy2v0

G.q4u*jy?0

bGyX*F0
publicclassAnswerGenerator {publicString generate() {returnnull;
    } 
}

/] z#Vn$o;vv { Z o_0{0  然后玩家猜数字,第一次猜了1234:

$T4Z.Q1O'r5BB)rR6tC0

L+{a&s-V$ca?051Testing软件测试网9YW vU4ek5~

context.checking(new Expectations() {                  
&uf6bvR6]'Qk \_8|0                                                   
T)vF$O6x$I7i-\`z0    // ...51Testing软件测试网:m_ hMJ!c/t:I
                                                   
A c*c~meJ'R_,[0    {                                                  
/{mfGI j B0        one(inputCollector).guess();                   
7z-?)[M'j%{0        will(returnValue("1234"));                     51Testing软件测试网G.h z @ Y'P$^6q,r
    }                                                  51Testing软件测试网bs-TG(K!CA)N%n4`
51Testing软件测试网QB3z~r^}%Y n

  为了使编译通过我们给inputCollector加上一个空的guess方法:51Testing软件测试网T^&`^1W

51Testing软件测试网u%j GcN

Rl2m c/ML,uz+?0
publicclassInputCollector {publicString guess() {returnnull;
    }
}

S)Dg1CA M S@5F0  然后guesser判断结果,由于完全猜对,因此返回4A0B:

XYq-GPQ p0

m^J:R["sLr H051Testing软件测试网C6P r!W?

d M%_vq-c"WZ0context.checking(new Expectations() {                  51Testing软件测试网 j8w4P(Q%`%W3y

51Testing软件测试网w }bp%V N~1N.q5_d'O

    // ...                                              
Fyn+u,Q6D+dG0                                                       51Testing软件测试网[ vP w6eh#V
    {                                                  
3@n7Gn,|]0        oneOf(guesser).verify(with(equal("1234")), with(equal("1234")));                   51Testing软件测试网m\jI6@sPP@
        will(returnValue("4A0B"));                     
[8i4e"?A0    }                                                 51Testing软件测试网-n+y4B$Whb-z4KS4H
}

(tEY[gWV&{0nQ:J0

$\#Lu2w JY-a&?? o0  同理我们可以推出guesser的一个最简实现:

w6i3tSou/]D0

5Fz ed+_k051Testing软件测试网0l {r'g^:^xM

publicclassGuesser {publicString verify(String input, String answer) {returnnull;
    }
}

'ed8H8}0uG0  最后玩家赢,游戏输出“You win”,game over:51Testing软件测试网4R%^N~W"ouN%t

51Testing软件测试网2d\a\6xT-f1At Q

#_XP['i s0

2\mGnT+T^Llg0context.checking(new Expectations() {  

N:F_3H;? j'MKx051Testing软件测试网(T*zE^L Y/D]

    // ...

m"]#S_{4t051Testing软件测试网5V8D+}r_D~ \

    {                                  
j:s7^ff}3aP0IJ0        oneOf(outputPrinter).print(with(equal("You win")));    
z#q.DFF;{%y UO0    }                                  
%@mV-J w1n L*[0
51Testing软件测试网|N)IM;Vf c+|3d.]

51Testing软件测试网#xY!Da;uV bW^ \2l\

  对应的outputPrinter可以做如下的微调:

X2j2OE#]2_051Testing软件测试网*k Eg^A+DR

51Testing软件测试网 d2^gj(N3|0T

publicclassOutputPrinter {publicvoidprint(String result) {

    }
}

_qor)ZlZ0  最后别忘了启动Expectation验证:51Testing软件测试网p#\sM#]Sc

51Testing软件测试网,Y.H*V"i&B:? H A

51Testing软件测试网7N~C}M(L)Sc8\:l

context.assertIsSatisfied();

#oR^%bE` f0  整个测试方法现在看起来应该是这样的:

&[6P)P2@)os$K3V3liz051Testing软件测试网'\p} R&yo

51Testing软件测试网,y6C!F k&a

@Test                                                                            51Testing软件测试网,BC.p0F3XR*v't)n/y'|
 public void should_play_game_and_win() {                                         51Testing软件测试网!cm!R \"XY't
     Mockery context = new JUnit4Mockery() {                                      
\L y {7c(F o&a0         {                                                                        
^YT@@7G(X0             setImposteriser(ClassImposteriser.INSTANCE);                         51Testing软件测试网&@g,Q z8_-XI
         }                                                                        
+l M `s$O7eN^0     };                                                                           51Testing软件测试网~:~;Xbf_*A V
     final AnswerGenerator answerGenerator = context.mock(AnswerGenerator.class); 
Ryf5}u0     final InputCollector inputCollector = context.mock(InputCollector.class);    
bK5c6Mq1P4[?0     final Guesser guesser = context.mock(Guesser.class);                         
b"c P6d5F0     final OutputPrinter utputPrinter = context.mock(OutputPrinter.class);       51Testing软件测试网*R]e4Un,U#aY&tA3c
                                                                                  
i.]6R:z;o:T*j'h.x6_5~0     context.checking(new Expectations() {                                        51Testing软件测试网m:f:vlfj
         {                                                                        51Testing软件测试网i:{ Tr1D$A3w
             one(answerGenerator).generate();                                     
!sfFy(G8r0             will(returnValue("1234"));                                           51Testing软件测试网bV-b$W4~$m(aW
         }                                                                        
I7}7T.ue;HE/\.k-L|x0                                                                                  51Testing软件测试网Lz/Sp,z${
         {                                                                        
#fX$\5{3{ U0             one(inputCollector).guess();                                         51Testing软件测试网;x uS+w7Thu\;y5x
             will(returnValue("1234"));                                           
s4F We T^4t+?;k s%p2j*K c0         }                                                                        51Testing软件测试网H f^9w"n
                                                                                  
+KMX5]_QJgo*d/h0         {                                                                        
i1U} P`6I(s`8f0             oneOf(guesser).verify(with(equal("1234")), with(equal("1234")));     
j WxwSL%f;i7~0             will(returnValue("4A0B"));                                           
+Fy8lg?5o~0         }                                                                        
r2gom&]6vYx0                                                                                  51Testing软件测试网t~_5K MS+{9u"wp
         {                                                                        
Ob/?2h,yTf/G0             oneOf(outputPrinter).print(with(equal("You win")));                  51Testing软件测试网m$q P"N9}5Fv
         }                                                                        
d zK4[b5\X{ x\3W0     });                                                                          
yw:I@ fo*H5B0                                                                                  51Testing软件测试网6v$gMh_$t7V
     Game game = new Game(answerGenerator, inputCollector, guesser, outputPrinter);
.E.Q!supF0     game.start();                                                                51Testing软件测试网A-f`:R[|P2L eO
                                                                                  
O"EORO F0     context.assertIsSatisfied();                                                 51Testing软件测试网r:Z!\J7Q'R
 }
,H7j5At%t&R0

TAG:

 

评分:0

我来说两句

Open Toolbar