1. 背景
先理一下自动化测试的概念,从广义上来讲,一切经过工具(程序)的方式来代替或者辅助手工测试的行为均可以成为自动化。从狭义上来讲,经过编写脚本的方式,模拟手工测试的过程,从而替代人工对系统的功能进行验证。
有赞是一家互联网行业的创业公司,测试起步较晚,发布很是频繁,就算每次只回归核心功能,对人数极少的几个测试人员来讲工做量巨大,且基本是重复劳动,极其枯燥,持续时间长了也容易出错。
因此初期咱们测试自动化切入的思路很是简单:从实际用户的角度出发,模拟真实的操做,替代现有的手工测试用例的执行。这样一来,每次重复的工做就能够用自动化来替代,测试人员只须要关注每次发布的增量需求便可。
随着脚本数量的增长,这种自动化覆盖的方式的弊端也逐渐暴露:
- 执行效率低下
- 构建成功率低(误报率高)。
- 受前端样式变动影响大
- 外部依赖较多,不是全部用例都能自动化
- 覆盖能力有限
虽然咱们在测试框架和工具层面经过结合selenium-grid实现了脚本并发执行和失败用例重试机制以提升执行效率和下降误报率,可是这种方式只能缓解问题,并不能从根本解决覆盖不全的问题。
正好遇上公司的SOA服务化进程,测试这边也开始配合的作自动化方面的转变,从原来的黑盒系统级自动化测试向分层自动化测试转变。
2. 分层自动化测试
在谈分层测试以前,先回顾几个概念:
- 单元测试:对软件中的最小可测试单元进行检查和验证。具体的说就是开发者编写的一小段代码,用于检验被测代码的一个很小的、很明确的功能是否正确。一般而言,一个单元测试是用于判断某个特定条件(或者场景)下某个特定函数的行为。
- 集成测试:集成测试是在单元测试的基础上,测试在将全部的软件单元按照概要设计规格说明的要求组装成模块、子系统或系统的过程当中各部分工做是否达到或实现相应技术指标及要求的活动。也就是说,在集成测试以前,单元测试应该已经完成。这一点很重要,由于若是不通过单元测试,那么集成测试的效果将会受到很大影响,而且会大幅增长软件单元代码纠错的代价。
- 系统测试:将需测试的软件,做为整个基于计算机系统的一个元素,与计算机硬件、外设、某些支持软件、数据和人员等其余系统元素及环境结合在一块儿测试。系统测试的目的在于经过与系统的需求定义做比较,发现软件与系统定义不符合或与之矛盾的地方。
接下来咱们谈谈有赞是如何随着系统拆分SOA服务化推动分层自动化测试的。先来看看经典的测试金字塔:
其中Unit表明单元测试,Service表明服务集成测试,UI表明页面级的系统测试。分层的自动化测试倡导产品的不一样层次都须要自动化测试,这个金字塔也正表示不一样层次须要投入的精力和工做量。下面我会逐层介绍有赞的分层自动化实践。
2.1 Unit-单元测试
在系统拆分以前,有赞只有一个庞大的巨无霸系统,单元测试极度缺失。在系统逐渐SOA服务化的过程当中,咱们逐渐提出了对单元测试覆盖率的要求。
咱们的单元测试会分别作DAO层和服务层的测试。DAO层的单元测试主要保障SQL脚本的正确性,在作服务层的单元测试时就能够以DAO层是正确的前提进行用例编写了。
为了作细粒度的测试,须要解决单元测试的外部依赖。系统和模块之间的依赖能够经过Mock框架(Mockito/EasyMock)解耦,同时能够结合h2database解决对数据库的依赖,使得测试用例尽量作到能够随时随地运行。
这一层发现并解决问题付出的成本相对来讲最低,自动化用例的维护成本也不高,总的来讲自动化测试的投入产出比最高。
单元测试的责任主体通常来讲是开发人员,写单元测试也是开发人员对本身的代码进行检查的一个过程。
2.2 Service-服务集成测试
咱们在服务层的测试首要考虑的是各系统(子系统)的集成测试。由于在通过单元测试这一层的保障以后,在服务层咱们更关注的是某个系统的输入输出功能是否正确,以及若干个系统间的交互是否和业务场景的要求一致。
先来看看咱们系统拆分以后的SOA系统应用架构图:
- 展示层:老的Iron应用,代码为php。拆分以后iron只剩下和前端交互的展示层逻辑,以及调用核心业务的API层
- 核心业务:Iron系统拆分出来的核心业务
这一层的被测对象是抽离了展示层的代码(前端以及部分后端展示层逻辑)。
鉴于有赞的测试起步较晚(应该不少创业公司都有相似状况),测试资源紧缺,代码覆盖率低得可怜。因此咱们的初期自动化用例覆盖策略是这样的:
- 从老的Iron应用的API接口做为业务场景覆盖的切入点
- 优先覆盖核心业务的场景
- 配合系统拆分,优先覆盖拆分出去的系统
- 已拆分出去的系统,作好系统服务层的测试覆盖(全面覆盖该服务的接口)
- 测试依赖的数据准备优先选择调用系统接口的方式(为了增长业务覆盖面)
- 测试方式逐渐从黑盒向灰盒/白盒转变
这样作的好处是,能够快速增长业务场景的覆盖面,同时事先准备好的API接口用例,能够做为系统拆分后的冒烟测试用例,起到核心老功能的回归做用(只是作系统拆分,业务逻辑以及对展示层暴露的接口行为不变)。毕竟在自动化测试的过程当中,最怕的就是变化,会带来更多的脚本维护工做,而以这种方式覆盖的用例,目前来看维护成本很低。
再介绍一下这一层的初期咱们用例的基本形态:
- 专一于业务场景,和UI脚本一致,只是脚本从操做页面变成了调用接口。相对于UI自动化,服务层的接口测试更加稳定,测试用例也更容易维护。服务层接口测试能够更关注与系统总体的逻辑(业务)验证,而UI自动化则会转变为页面展现逻辑及界面前端与服务的集成验证(这个在UI层会介绍)。
- 暂时不作系统间的Mock。更多的考虑系统之间的耦合和依赖。
- 搭建MockServer解决系统的外部依赖,主要是相似于支付等第三方系统(关于咱们的MockServer会有专门的文章介绍)。
结合咱们的交易系统举个例子:好比交易系统会依赖于商品和营销活动,那咱们的下单场景的用例会依次调用商品和营销这几个系统的API构造数据做为用例的前置条件,而后按照下单的业务场景调用交易系统的下单接口,校验返回值以及写入DB的数据,最后作好数据清理的工做。
咱们在这一层的测试框架选择是基于公司通用的服务框架(Nova)基础之上搭建的,架构图以下:
BIT :服务接口集成测试(Business Interface&Integration Test)
SUT:被测系统(System Under Test)
- Validator:各个业务的数据库结果验证封装
- Mock:用例前置条件依赖的数据Mock服务
- HttpClient:根据IRON系统的接口封装了返回通用的RPCResult对象
- Util:经常使用工具类封装
- Biz:在此封装了全部被测系统对外暴露的接口,供测试用例直接调用
- TestCase:咱们服务接口测试又分为SDV(System design Verify-系统设计验证)和SIT(System Integration Test-系统集成测试)。按照上面提到的用例覆盖策略,咱们是在系统拆分以前,先根据该系统的业务场景和REST接口补充核心的接口集成测试用例,后续能够做为系统拆分以后的冒烟用例。在系统拆分以后,详细补充该系统的测试用例,粒度更细。
- Facade:结合了Nova框架,对外发布各个业务的数据构造的REST接口,同时能够做为测试数据构造系统,辅助测试人员的手工测试
这一层的测试覆盖主要是由测试人员进行,是测试人员大展身手的地方。
咱们不须要很是详细的了解代码的实现,可是咱们的用例里充分体现了咱们对系统的结构,模块之间关系等的充分的理解。
后续咱们对于Service层自动化测试的推动策略是:
- 逐渐丰富SDV层的测试用例,而且在必定程度上进行用例依赖的系统的解耦,好比数据构造从调用接口向直接往数据库写入数据转变。
- 逐渐细化拆分业务场景,作好用例的解耦。
- 优先作场景覆盖,以后再考虑代码覆盖。
2.3 UI-展示测试
先提一个问题,既然在文章开篇提到了UI自动化测试有这么多弊端,这么劳民伤财,那么是否还有必要进行UI层的自动化呢?答案是确定的,由于UI层是咱们的产品最终呈现给用户的东西。因此在作好上面两层的测试覆盖以后,测试人员能够投入更多的精力到UI层的测试上。正是由于测试人员会在UI层投入较大精力,咱们仍是有必要经过自动化来帮助咱们解放部分重复劳动力。
根据咱们的UI层自动化实践,提一下咱们的UI层自动化覆盖的原则:
- 能在底层作自动化覆盖,就尽可能不在UI层作自动化覆盖
- 只作最核心的功能的自动化覆盖,脚本可维护性尽量提升
咱们提升UI脚本可维护性的方法是遵循Page Object设计模式。
Page Object
Page Object模式是为了不在测试代码中直接操做HTML元素,对Web页面的抽象。好处有:
- 减小测试代码的冗余
- 提升测试代码的可读性和稳定性
- 提升测试代码的可维护性
一个简单的例子
以有赞首页的登陆操做为例(Ruby):
class LoginPage include HeaderNav def login(account, password) text_account.wait_until_present.set(account) text_password.set(password) button_login.wait_until_present.click return MainPage.new(@browser) end private def text_account @browser.text_field(:name => 'account') end def text_password @browser.text_field(:name => 'password') end def button_login @browser.button(:class => 'login-btn') end end
- public方法对外暴露页面的服务,对于登陆页面来讲就是登陆行为
- 页面的UI细节设为private方法对外隐藏
- 跳转到新的页面后在此方法中return以后的页面的对象,好比登陆以后跳转到首页(MainPage)。甚至同一页面也能够返回self作链式操做。
- 各个页面的公共部分,如页面顶部导航,能够封装成Module供各个页面对象直接include
下面咱们来看看测试用例:
class TestLogin < Test::Unit::TestCase def testLogin @browser = Browser.new @browser.goto 'youzan.com' main_page = @browser.login_page.login('xx', '123') #断言 end end
这样最终的测试脚本呈现的就是单纯的页面操做逻辑,更贴近文本测试用例。
下面来看一下咱们的测试框架:
- Base:这一层和大多数UI测试框架大同小异,使用的是selenium和watir,用例管理方面并无使用Ruby领域煊赫一时的BDD框架cucumber,而是最基本的单元测试框架MiniTest。同时还引入了ruby的多线程包,配合UI脚本的并发执行。
- Actir:咱们本身封装的测试框架
- Initializer:自动按照约定的工程结构加载全部的ruby文件,并根据Page的类名和反射自动生成了全部页面类的对象实例。
- UA:封装好测试须要的浏览器User-Agent。
- Executor:用例执行器。基于ruby的多线程包以及selenium-grid,实现了全部用例的调度及分布式执行,能够必定程度上大大提高UI脚本的执行效率。执行器还包括了失败用例重试机制。
- Util:工具类,包括了配置文件读写、数据驱动等。
- Report:根据UI测试脚本执行的最终结果(失败重试的用例以最终的结果为准)自动生成HTML格式的测试报告。
- Cli:根据Actir框架的上述功能,封装出的命令行工具,方便持续集成。
- Pages:基于PageObject模式包装出的页面的对象
- Components:各个页面的公用的部分或者插件,如图片上传、地址选择等。包装成Module供各个页面对象须要时直接include。
- Item:根据系统业务抽象出的对象,如订单、优惠券、商品等
- User:根据系统业务抽象出的角色及其Action,如买家的购买行为、买家的退款、发货等。
随着服务层自动化覆盖率愈来愈高,UI层的自动化覆盖会逐渐转变为页面展现逻辑及界面前端与服务的展示层交互的集成验证。咱们后续对于UI层自动化的演进规划是这样的:
- 依赖环境的Mock,解除UI脚本的外部依赖
- 完善的数据准备,能够经过后端服务接口的mock使UI自动化更关注页面业务逻辑的自动校验。
- 页面截图比对校验UI样式。
UI层的自动化测试也是由测试人员负责,在覆盖了核心业务核心场景以后,不该该在这层的自动化覆盖上投入太多的精力和资源。就算咱们在必定程度上提升了脚本的可维护性,但是毕竟自动化测试最怕的就是变化,而UI界面是变化频率最高的一层,因此仍是得投入必定的精力维护,不是么?
3. 持续集成
有了上述各层的自动化测试脚本,下面咱们须要创建起持续集成体系。 持续集成的目的:
- 流程自动化,提升工做效率
- 最大化自动化测试脚本的价值
咱们的持续集成是基于Jenkins搭建的,主要的动做以下:
- 代码提交自动执行单元测试
- 单元测试经过后自动部署总体的环境
- 自动执行集成自动化测试(Service/UI)
- 自动生成构建的详细测试报告,同时自动通知相关人员
持续集成所需的支撑有:
- Java应用 基于JaCoCo+Jenkins插件的方式
- php应用 通Xdebug+phpunit的方式
对于持续集成咱们后续的演进规划是朝着持续交付和持续部署的方向努力,在持续集成的基础之上,自动将代码部署到测试环境上方便测试人员进行手工测试。
4. 总结
本文主要从总体上介绍了在有赞SOA化的进程中,测试推行的分层自动化实践,以及后续的发展方向,同时简单介绍了相关的测试框架结构。下面再从总体回顾一下咱们的分层自动化的要点:
- 对测试来讲优先级最高
- 从业务场景的角度切入
- 系统外部接口100%覆盖
- 关注系统间的依赖和调用
- 测试实施
- 优先级相对最低
- 只保证核心功能的自动化覆盖,只关注UI层的问题
- 经过数据mock的方式减小对后台数据的依赖。
- 测试实施
至于各层投入的具体比重,仍是须要根据项目的需求来实际规划。在《google 测试之道》一书,对于google产品,70%的投入为单元测试,20%为集成、接口测试,10% 为UI层的自动化测试。
最后再提一些观点吧:
- 越底层的自动化,收益越高
- 质量不是测试人员一我的的事情
- 自动化测试的目的不是为了减小手工测试,而是为了测试人员作更多更有意义的手工测试