一、自动化测试没那么简单
简而言之,自动化测试就是利用脚本来完成重复、机械、繁重的手工测试。从使用功能的角度而言,自动化测试脚本既是一个工具,也是一款软件。因此,一款合理的、实用的自动化测试框架是完成高效自动化测试的前提。
非常不幸的是,我所负责的项目由于几易其手,项目的资产沉淀非常混乱甚至没有,项目的自动化代码极其臃肿庞杂。与其说是自动化测试框架,还不如说是代码块随意的堆积,缺乏必要的设计。旧框架的特点如下:
(1)接口分层过于粗糙,导致代码重复率高。
我负责的这个项目是属于平台类产品,暴露给调用方的是大量的接口。而我们的测试重点之一,就是要覆盖接口种种功能,比如接口字段的截断、补数、入库、下抛等。每个接口之间既存在一定的区别,又存在一定的共性,某些公共类操作可以为其他接口所复用,但也有大量接口所独有的逻辑。而旧框架为了省事,直接以接口作为管理单元,每个接口下都有一份自己的组件和关键字。大体结构如下所示:
图1 项目层级结构示意图
如此分层看起来很清晰,但实际上忽略了接口之间的内部联系。由于接口A和接口B彼此隔绝,导致一些公共操作在A中实现了,但是不能在B中用,B还需要再实现一遍。比较偷懒的办法就是直接将A中内容拷贝至B中,虽然能解决问题,但是违背了“一次编码,永久运行”得到原则。在导致代码重复率升高的同时,还会导致维护成本剧增。一旦基础类信息发生改变,则需要挨个接口去修改。
(2)案例的关键字逻辑复杂,不利于维护、拓展。
由于种种历史原因,导致旧框架使用了RobotFramework作为案例管理层,python作为基础逻辑实现层。使用rf进行案例管理并没有问题,问题是旧框架将90%的处理逻辑封装成rf关键字,而将python当成了“函数集合”。于是,编写案例就变成了在rf中进行“搭积木式”的各种跳转、组合,而python灵活、便捷的优势确不能得到充分发挥。每次编写案例都需要先花大量的时间来捋关键字的跳转顺序,再来确定需要新增、修改哪些关键字。其逻辑如图2所示。
(3)框架缺少设计,导致难以扩展。
旧框架的py文件并没有进行合理的类抽象,函数之间也没有明确的功能划分,经常是A函数的部分功能,在B函数中也会实现,这导致函数之间、类之间、py文件之间是互相耦合混乱的。且代码没有注释。
由上述三个原因,导致编写自动化案例的时间成本极高,维护自动化脚本的成本极高,新人的学习成本极高。在项目中,宁可手工测试,也不愿意写自动化案例。而没有自动化案例的沉淀,导致回归场景的测试成本极高。由此进入恶性循环,效率降低。
所以,我们项目组需要重新设计一款自动化框架。
二、如何开始设计呢?
我作为项目负责人最先接到这个任务,领导希望我能重构旧框架,在革除旧问题的基础上能够将全组的测试场景都涵盖进去,全组……涵盖进去……进去。
对我而言,这是一个极具挑战性的任务。为此我按照自己的思路开始了如下设计。
1、version1--将RF和python解耦
既然自动化测试脚本也是一款软件,那就需要先明确使用方的需求是什么,我简单罗列了一下我的诉求:
(1)降低代码的重复率;
(2)尽量减少或者不用RF处理复杂逻辑;
(3)节省编写案例的时间。
为此,我开始了第一个版本的设计。我仔细回忆了我在使用旧框架过程中的障碍,我认为想要达到上述诉求的第一步就是彻底将复杂业务处理逻辑放到python中,只将RF作为案例的“组装器”。同时为了解决接口隔离导致的重复率过高的问题,我单独分离出了一个公共层,用于存放公共类操作。最后,为了解决RF关键字跳转复杂的问题,我在RF抽象出了接口层和模型层。即大部分案例的测试过程是相似的,只是传参不一样,将这些步骤固定下来形成一个模板即所谓模型。为这些模型提供服务的即所谓接口。最后我得到了如图3所示的框架:
图3 version1示意图
version1解决了代码重复率过高的问题,解决了RF处理逻辑复杂的问题,也在一定程度上提高了案例编写效率,至少解决了目前存在的问题。
于是我去找领导检视代码,领导提了很多修改建议,归结起来有如下几点:
(1)RF中的跳转虽然少了,但是传参依旧麻烦,可否进一步解耦?如果公司不用RF了你如何应对?
(2)如何应对拓展场景呢?拓展场景如果只是轻微的区别是修改模型层还是修改公共层呢?
(3)如何将全组的测试场景装进去?
(4)当协同编写案例时,如何保证框架不走样,如何分级维护呢?
……
2、version2--参数对象化
领导的修改建议听得似懂非懂,不过修改意见的第(1)点醒了我。既然我能想到将相似得到流程抽象成模型层、将案例的区别体现在传参上,那么我可不可以将案例变成一组参数,通过统一的入口反射传参至python的模型类中。这样就能在最大程度上将RF和python解耦,还能利用案例参数化的方式,将参数对象完整地传给模型类、接口类等,既简化了案例层的传参,又简化了类之间的繁琐传参
客观来讲,我修改后的版本能解决我项目中的问题,也具备在组内推广的必要条件,但是领导更希望我能更进一步,达到他心中理想的版本。此时领导建议我先做建模,并旁敲侧击问我,一个测试过程究竟应该分成几步?
3、version3
在和领导讨论之后。他认为所有的测试场景(无论是单接口测试还是长流程的测试,甚至是数仓脚本导数等场景)都可以切分成准备数据、执行操作、对比结果三个步骤。测试场景不同、操作对象的变化等都可以看成是对上述三个步骤的具体实现。沿着他的思路,得到如下版本。
即创建一个基础控制器对象,并将上述三个步骤定义成抽象方法,并定义了三个步骤的调用顺序。为实现控制器的功能,又定义了为控制器提供服务的基础服务层。同样在基础服务层中也只定义准备数据、执行操作、对比结果所需要的抽象方法,并定义不同抽象方法的组装顺序。至此,整个框架的骨架就搭建完毕了。
回过头来看我之前编写的框架中所谓的模型层、接口层、服务层、公共层的分类,不过是填充到控制器层、服务层的具体实现。
4、两种版本的比较
仔细研究version3能发现,其包含的设计思想很简洁,且符合设计模式六大原则中的开闭原则、历史替换原则、依赖倒置原则和最小知道原则。而使用的设计模式主要是模板模式。其通过建模的方式将所有的测试场景进行了归纳,而不同的测试场景则通过子类重写父类或从头实现模板的抽象方法来实现。
version2虽然也解决了旧框架存在的问题,但是缺乏高度统一的归纳。而且不同层级的划分不够清晰,没有进一步抓住不同流程之间内部联系,导致可拓展性大打折扣。
从可维护性而言,version3可以实现0代码运行案例,即初级测试人员不用关系每个场景的实现逻辑是啥,只需要照葫芦画瓢填入参数,就可以完成案例的编写。这样就可以在一个项目组中,由初级测试人员快速地编写案例,中级或高级测试人员负责新场景的拓展和底层逻辑的维护。
四、总结
1、自动化测试脚本本质也是一款软件,设计之前一定要先进行抽象和建模;
2、采用常用的设计模式,如模板模式、工厂模式,并遵循设计模式六大原则,可以使代码变得简洁和高效;
3、设计一个工具之前,先考虑如何快速地解决大部分的问题,不要贪多求全。否则会被小部分的问题阻挡进度,回报太低;
4、框架的重构应当贯穿在整个生命周期,不能偷懒和凑合。如果不适时地重构将会积重难返;
5、参数对象化,随存随取,既可以保证参数的完整性也可以保证不发生传漏、传错的情况。