本文是在读了《Working Effectively with legacy Code 》第九章,关于在无法将类放入测试用具中时遇到的四种最为常见的问题:51Testing软件测试网;X'zH;AQ:Vy (1)无法轻易创建该类的对象。51Testing软件测试网 _s3{8DR1z
51Testing软件测试网 af#U!d)l+DbX V (2)当该类位于测试用具中时,测试用具无法轻易通过编译构建。51Testing软件测试网a7K&D.rg
&UB?T.U%l0j0 (3)我们需要用到的构造函数具有副作用。51Testing软件测试网gdLIqg
51Testing软件测试网hA"c0t3G#Y
R)gN (4)构造函数中有一些要紧的工作,我们需要感知到它们。
JM1S`7JH7Fq051Testing软件测试网1R&O+w2Q"Q~6t 这四个问题在进行单元测试或
者接口测试的时候,会对测试工作造成很大的阻碍,这就是一个代码可测性的问题。当遇到这样的问题的时候,有两种方法,第一、强行构建一个类去完成测试,但
是这会造成测试的时候大部分工作都耗费在构建这样一个类的过程中;第二、重构代码,使代码具有可测性。本文将通过书中的列子来简单介绍一下如何提高代码的
可测性。
eF5R^DB;o~h051Testing软件测试网Z~iOJ0s 如在一个计费系统中,我们有一个未测试的Java类:CreditValidator。51Testing软件测试网|(h,P:lO
hg#R{
public class CreditValidator l@ z
ErJ#t6x0 {51Testing软件测试网8Zp'F["q public CreditValidator (RGHConnection connection, P*Je4e;{ J
X0 CreditMaster master, 9^%i*j"I-fV6De0 String validatorID) { m:vf@5H t0 }51Testing软件测试网)eG8?g&d[DzBb7zW Certificate validateCustomer(Customer customer) throws InvalidaCredit{ VQ%p~4v
_n&XD0 } 7G)rw6],{N:U!h5c8e0 public class RGHConnection51Testing软件测试网7{D$v'U)D
PlSkKT%X'Ip {51Testing软件测试网bU!~n[ public RGHConnection(int port, String Name, String passwd) throws IOException {51Testing软件测试网`F\E-j'bfD }51Testing软件测试网?@5?G8](B7B }51Testing软件测试网w;xC4QQ1^
~i } |
%T2DD#{!a;S&~Y2x|0IG'vn0
我们可以看到CreditValidator构造函数含有三个参数RGHConnection,CreditMaster,validatorID。其
中RGHConnection对象在构造时会连接到一个服务器,这个链接被用来从服务器上获取必要的信息,以检查客户的余额。
-y3_u1|(q8{C08K;w @g+]W4O0 宁一个类CreditMaster,则提供一些我们在检查余额的过程中会用到的策略信息。该类的构造函数会从一个文件中加载相关信息,并把这些信息保存在内存中以备后用。
I2Z I^;@(o,~051Testing软件测试网
n?o M2A#Y3` 如果按照我们开头讲的强制构造一个类来完成测试,如下所示:51Testing软件测试网0yt _jV
public void testCreate() throws Exception {51Testing软件测试网2~.@js;dx RGHConnection connection = new RGHConnection(DEFAULT_PORT,"admin","rii8ii9s");51Testing软件测试网'e%t3s)IJVC CreditMaster master = new CreditMaster ("crm2.mas",true); 5z)q'i*FZ.T6u&@0 CreditValidator validator = new CreditValidator(connection,master,"a"); 2jQ7K'e;]l0} |
#j}I6KLS!Iq0
虽然我们构建一个这个样的类,但是你能忍受这个测试的速度,根据《Working Effectively with legacy Code
》书中提到大于1秒的单元测试,都不叫单元测试。因此在测试中建立到服务器的连接并不是一个好的主意。首先其好事就比较长,况且服务器也并不总是处于服务
状态。可想而知RGHConnection是一个令人恼火的参数。我们的设想是:若能创建某种伪造的RGHConnection对象并使
CreditValidator相信它是一个真正的RGHConnection的话,就可以避开所有链接的问题了。51Testing软件测试网7rFf)M x
1A3Y+n2O$}bkO0 首先来看一下RGHConnection所拥有的方法:51Testing软件测试网-P5k1AvMt8m
RGHConnection @e2][o~-~l!kgA0+ RGHConnection(port,name,password) u'f7Fi5S ]2R;\#^0+ connect()51Testing软件测试网!xyj;B cF+rV + disconnect()51Testing软件测试网:h7f,l
B&PC^ N +RFDIReportFor(id:int):RFDIReport ;Vt l.b)A0+ACTIOReportFor(customerID:int) ACTIOReport `f0e(p9t E;b0+retry()51Testing软件测试网 qxI4}w3A;j/h +fromPacket():RFPacket |
51Testing软件测试网1b_;J)p'YO rp%q
看上去RGHConnection中有一些方法是用来处理与连接相关的任务的:如connect、disconnect以及retry。另外还有一些业
务方法。因此如果要伪造一个RGHConnection对象的话,那么这个伪造的对象也必须拥有这些方法也能提供一样的信息。
O8k,{2J.R*K0x.?6c(h]
mj0在这些条件下,伪造一个RGHConnection对象最好的方法是对RGHConnection类应用接口提取。如果你手头一个支持重构的工具,那么它很可能也会支持接口提取方法。我们来看一下接口提取后的情况:51Testing软件测试网4d-n$X:`W k;p M
UM$GH4L Q9E:gv051Testing软件测试网tB}-LZ;s+_
<interface>51Testing软件测试网2PQ$r/}3ee IRGHConnection "[P$w Z#dB0+connect() 0`7_])l^9@ P o0+ disconnect()51Testing软件测试网y|P$Ee&p7u'o `z +RFDIReportFor(id:int):RFDIReport51Testing软件测试网J'v
Xf'q +ACTIOReportFor(customerID:int) ACTIOReport |
D1R-zy Qw0o0 由于retry()和fromPacket()不属于业务相关方法因此只需要在实现类中增加这两个方法,至此我们可以轻松的构建出一个FackeConnection类,并使它能够提供我们所需要的反馈信息,然后将这个伪造的对象用在测试中:
uP,N1M2|+m9A!E0or0nAIn8kf8rGx051Testing软件测试网,x%q\(a(J#|;k
public class FakeConnection implements IRGHConnection51Testing软件测试网BnNH+N"V#? { 4L\1I3o'i7p3W8}}+QN0 public RFDIReport report; ojd4Wo/T-x%n-N0 public void connect() {} 0i*O3mZL0 public void disconnnect(){}51Testing软件测试网Xc"a4D'l9g public RFDIReport RFDReportFor(int id) {return report;}51Testing软件测试网m#LHT]_jC}q public ACTIOReport ACTIOReportFor(int customerID) {return null;}51Testing软件测试网XqEQYr } |
51Testing软件测试网 m QgA}G 下面我们来写测试
:f3kp2^n-J~0n;Zz4E ~2M._0
K_x$bO-oR2y b0void testNoSuccess()throws Exception{51Testing软件测试网a2P2P#r
m-K2ky CreditMaster master = new CreditMaster("crm2.mas",true);51Testing软件测试网`[0_o2]#wI/I*ca IRGHConnection connection = new FakeConnection(); Iw.u5@-u4vr
R0 CreditValidator validator = new CreditValidator(connection,master,"a");51Testing软件测试网"i k;v t7gz:n6G
L connection.report = new RFDReport(....);51Testing软件测试网YHY
G I Certificate result = validator.validatorCustomer(new Customer(...));51Testing软件测试网*zT
R5cJX$}n"` assertEquals(Certificate.VALID,result.getStatus());51Testing软件测试网6GwVF Ed~ } |
Mj&HN9@[+B Wu/xF([0 虽然FakeConnection类看起来有点奇怪:它的方法要么是空的要么就简单的返回null。这种情形并不常见。更糟的是,它有一个任何
人都可以看到的并随意设置的公共变量。这样一个类似似乎违反了所有的良好准则。但你要看到,实际上并非如此。对于一个用来使得测试可行的类,规则是所不同
的。FakeConnection中的代码并非产品代码。它永远也不属于最终投入运行的应用,而只是为了测试工具和测试而诞生。
,y'N4h'QF*i;Qv0Unq4M{*\j/N0 有了这个fake类我们接下去便可以做更多的相关测试。这就是提高代码可测试性和遇到教难构造的的类的时候所采取的一种方法。若代码设计阶段就将RGHConnection设计为接口,那么在后面的测试中会是测试更加方便,使代码在后期的重构也会更加方便。51Testing软件测试网U$Gg(kWAd)E
d o