BB技术|测试遗留代码的一种低风险方法--抽取和覆盖

论坛 期权论坛 期权     
A梦的生活BB   2019-7-28 23:26   2076   0


点击蓝字关注我们




对老系统的遗留代码编写单元测试脚本一直是自动化测试的难点,为了测试而重写老代码的风险极大。本文提出的“抽取和重写”的思路可以有效解决这一问题,在不改变老代码结构的前提上,测试其功能。


对老系统的遗留代码进行单元测试一直是自动化测试的难点。


遗留代码的内部相互依赖多,很难针对某个单独功能进行测试。而且遗留代码年代久远,结构臃肿,对其进行测试需要尽可能不改变代码结构,否则稍有不慎,系统结构可能会崩溃。

在这个背景下,Roy Osherove在他的《单元测试的艺术》一书中提出了一种“抽取和覆盖”的思路,能够简单方便的解决这一问题。但作者是用C++实现的,与java的语法有差异,本文用java来实现这一思路,以将这种实用有效的方法介绍给大家。


假设有这样一段老的代码:
  1. public class LogAnalyzerUsingFactoryMethodOld{    public String IsValidLogFileName(StringfileName){    //下面就是对外部类的依赖    ExtensionManager  myManager=new  ExtensionManager();    //如果isValid的机制变化,则对IsValidLogFileName的测试可能失败    //这就是依赖    return myManager.isValid(fileName);    }}
复制代码
这个类中的方法 IsValidLogFileName,用来判断给定的文件名是否有效。而文件名是否有效,则依赖于ExtensionManager这个外部类。


对于不同的于ExtensionManager处理机制,是否有效的返回值可能会不同。比如如果ExtensionManager是word,则.doc的文件是有效的,但当它有一天变为Excel时,.doc文件就会失效。


这样,如果仍然是对IsValidLogFileName 方法进行测试,当ExtensionManager这个外部类的判断机制发生了变化,可能导致原先定义的测试规则失效,从而造成对IsValidLogFileName的测试失败。


若要有效进行测试,必须解除对ExtensionManager的依赖。使用“抽取和覆盖”这一方法,可以在不改变原来代码逻辑的基础上,达到这个效果。


首先我们对依赖的部位做一个抽取,这个抽取是用接口来替代原来的ExtensionManager类调用。利用接口的通用性和替代性,可以解除依赖的目的。代码如下。
  1. public  class LogAnalyzerUsingFactoryMethod {        public String isValidLogFileName(String fileName){            //通过GetManager返回接口,来调用接口中的方法            //实现解耦            return GetManager().isValid(fileName);        }        protected  IExtensionManager GetManager(){            //实际所调用的外部类,增加实现了IExtensionManager接口                        return new FileExtensionManager();        }    }
复制代码
很显然,我们的接口定义就为如下代码。
  1.     public interface IExtensionManager {        String isValid(String filename);
复制代码
  1. }
复制代码
这里注意,我们的被测试类(LogAnalyzerUsingFactoryMethod)中,new FileExtensionManager()调用的仍然是原先所依赖的外部类,这里的原代码逻辑是没有发生改变的。当然,FileExtensionManager类也要实现 IExtensionManager 接口,其代码如下。
  1. public class FileExtensionManager implements IExtensionManager {         public String isValid(String fileName){            return "File Extension Manager";        }    }
复制代码
如果在测试阶段,这个依赖是要替换的,那么就可以在此做出下一步操作,覆盖。


接下来,我们对依赖部分做出替换,在不改变被测方法逻辑的基础上,用新的被测试类,替换掉依赖项。这个替换是用自定义子类来实现的。
  1.     public class TestableLogAnalyzer extends LogAnalyzerUsingFactoryMethod{        public IExtensionManager Manager;        //新的可测试子类,覆盖了父类的GetManager方法        //从而可以按照自己的需要插入新方法,解除依赖        @Override        protected IExtensionManager GetManager(){            return Manager;        }    }
复制代码
新建的TestableLogAnalyzer 子类继承自 LogAnalyzerUsingFactoryMethod被测试类,增加了一个成员接口变量 Manager,并重写了之前产生依赖的GetManager()方法,用来返回我们自己定义的 Manager 接口实现类。这样就完成了依赖的解除,并可以按照我们自己的意愿传入测试 isValidLogFileName 方法所需的外部类了。

自定义的测试所需的外部类代码如下。
  1.     public class StubExtensionManager implements IExtensionManager{        public String ShouldExtensionBevalid ;        public String isValid(String filename) {            return ShouldExtensionBevalid;        }
复制代码
  1. }
复制代码
这个外部类可以依据测试的需要来返回任意结果,同样实现了IExtensionManager 接口。

接口的定义也很简单,如下代码。
  1.     public interface IExtensionManager {        String isValid(String filename);    }
复制代码



最后我们看看测试的代码。代码是用groovy实现的,使用了Spock测试框架。
  1.     class LogAnalyzerTestsOverride extends Specification{        def 'test override'(){            //定义了测试所需的自己的外部类            StubExtensionManager stub = new StubExtensionManager()            stub.ShouldExtensionBevalid =  "Stub Extension Manager"            //实例化一个被测试类,并将外部类传入            TestableLogAnalyzer logan = new TestableLogAnalyzer()            logan.Manager = stub            given:            def result = logan.isValidLogFileName("file.ext")            expect:            //测试结果判定为在外部类中设定的结果            result == "Stub Extension Manager"        }    }
复制代码

至此,执行测试后,可以验证测试结果符合预期,在被测类的方法中得到了正确的输出,实现了按照测试要求进行的解耦。


抽取和覆盖”方法,在《单元测试的艺术》一书中是极为推荐的,它可以在不影响原代码逻辑的基础上,解除代码间的依赖,精确的对被测试类中的方法开展单元测试,对老代码逻辑变动的风险极小。


这个方法的精髓在于通过接口来解耦,通过接口返回自定义的依赖项,就是抽取;然后通过建立被测试类的子类,将接口返回的依赖项替换为上述抽取的内容,实现覆盖和解耦。

这种“抽取和覆盖”的处理方法原理简单,适用面广,威力无穷,值得仔细体会,熟练掌握。








分享到 :
0 人收藏
您需要登录后才可以回帖 登录 | 立即注册

本版积分规则

积分:
帖子:
精华:
期权论坛 期权论坛
发布
内容

下载期权论坛手机APP