大话重构连载12:你不能没有保险索
通过前面的描述你已经对重构中“小步快跑”的开发模式有了一个清楚的认识。学会和习惯小步快跑的开发模式,对于重构工作极其重要,因为它让这种大范围、大幅度修改代码的重构工作变得不再像以往那样让人胆战心惊。究其原因,虽然从结果上是在大范围、大幅度调整,但每一步却是小范围、小福度调整,并且能保证每一步都是正确的。
然而,这里有一个非常重要的假设条件,那就是“保证每一步都是正确的”,这是怎么保证的呢?就这个问题,我们需要展开来认真讨论讨论。
毫无疑问,系统重构就其结果来看,是在对软件系统进行大范围、大幅度的改动,而这种改动过去我们是无法想象的,因为它实在是改动太大了,一不小心就会产生一个毁灭性的结果。这就是许多人不敢轻易尝试系统重构的重要原因。
是的,这就是真相,如果你不能正确地验证每一步系统重构是否正确,或者你的验证方式存在缺陷,即使小步快跑也不能解决重构带来的风险。所以我们说,系统重构,你不能没有保险索。这个保险索就是每次重构后正确的测试方法。
什么是程序代码正确的测试方法,其在不同的场景中标准是不一样的。但与其它测试不同,系统重构在测试代码正确性方面却有自己独特的方法。系统重构,从自身的定义上就把其代码正确性的验证方式描述得十分明白,那就是“不改变软件外部行为”。
“不改变软件外部行为”,我们可以从多个层面上看待这个问题。首先从功能层面上看,重构前系统提供给用户什么样的功能,重构后也应当提供同样的功能。说得更加具体一些,重构前,用户从界面上输入什么内容,发出什么请求,得到什么结果,那么重构后用户应当得到同样的结果。这种结果可能体现在前端界面所展现的结果中,也可能体现在此处操作后存入数据库的数据中,即重构前后系统存入数据库的数据也应当是一致的。存入数据库的数据同样是测试中系统输出的一种。
从功能层面再往下钻就到了代码层面。打开我们的软件系统,有各个层次、各项接口和各种函数。我们进行系统重构往往是在某个层次、某项接口或某个函数上进行的重构。比如,我们对某项接口进行重构,那么我们可能会修改这个接口的实现类、方法函数、底层调用类,但不论怎么修改,这个接口必须保持不变。也就是说,我们代码层面的重构,“不改变软件外部行为”是针对的这个接口。你可以重构接口内部,但必须保证接口外部是不变的。
所以,系统重构的测试可以从两个层面来进行:系统测试与单元测试。系统测试往往是一种手工测试,当然也可以使用QTP等工具进行一些简单的录制。重构前我们首先通过需求文档确认系统现有功能,根据现有功能设计测试用例。然后进入系统,确保每个测试通过,并录制成QTP脚本。这样,我们对重构的准备工作就完成了。在整个测试过程中,我们每完成一次重构就去手工执行这些测试,或者执行QTP脚本,保证每个测试通过。如果测试不通过,则要么寻找问题原因并解决,要么就恢复到上次测试的版本,此次重构宣布失败。
反复地手工进行同样的测试是一件比较烦人的事,因此你可以有两个选择:录制QTP脚本,以及在接口层面建立自动化测试程序。这里所说的自动化测试程序是指那些基于JUnit编写的自动化测试程序,它实际上是在代码层面进行的单元测试。建立自动化测试的关键在于我们在哪个层面建立测试。代码有许多层次,有函数级别、有类级别、有接口或抽象类级别,从系统分层结构来说有底层、DAO层、BUS层、Web层。每次重构都是站在某个层次进行重构,因此自动化测试代码也应当在这个层次上写。比如最初的重构是在函数层次上进行的,就应当对函数写测试代码;后来在对象层次上进行重构,就对对象写测试代码;再到接口层次……
与手工测试一样,在系统重构前,我们首先保证原有代码自动化测试通过。然后执行重构,保证每次重构都能够测试通过,如此往复。但重构中的自动化测试有一个问题就是,测试接口不太稳定。起初是在函数级别进行的重构,我们写了针对函数的测试代码。但随着重构的深入,我们开始在对象层面进行重构了,我们可能要调整原有的函数。这时将意味着原有的针对函数编写的测试代码将失效而必须要遗弃,这不得不说是一种浪费。因此我给大家的建议是,不要过早编写自动化测试代码,它不一定能节省我们的时间,甚至可能花费更多。最初的重构可以采用手工测试或QTP测试的方式进行。
大话重构连载首页:http://www.cnblogs.com/mooodo/p/talkAboutRefactoringHome.html
特别说明:希望网友们在转载本文时,应当注明作者或出处,以示对作者的尊重,谢谢!