9个从零开始的B端产品自动化测试实践
作者:Agilean
9个从零开始的B端产品自动化测试实践

从自动化测试实践开始,我们将陆续推出「研发工程实践」系列文章,包括需求质量、用户故事拆分、分支模型、CR方案、工程效能等主题。结合我们多年为企业进行研发管理相关的咨询经验,以及团队本身的软件工程总结,为大家进行工程实践提供参考。
经过多年发展,自动化测试已成为敏捷测试中持续测试的重要手段。作为一款提供企业数字化协同服务的产品,知微产品研发团队在接口自动化测试上摸爬滚打多年,总结出了一套适合B端企业级软件小型团队的自动化测试实践,包括如下9点:

本文将全面复盘知微团队这些年做自动化测试的历程,从初次建设自动化开始,分析我们遇到的问题,改进思路和实践总结。
时间回到刚开始建设自动化时,我们做自动化测试的初衷,是由于版本节奏快、系统功能多、人力资源有限,需要在有限人力前提下,尽可能的保障系统质量——只有做自动化测试才能达到目标。因此,我们决定抽出测试人员来专门做自动化建设。
建设初期,我们发现了几个问题:
功能测试人员进行测试案例描述,自动化测试人员把测试案例描述转化成自动化案例。这个过程存在两次理解转化,知识的传播链路长,沟通成本较高,很容易出现目标偏差,即最终的自动化案例没有守护到功能。
自动化人员专岗,不参与到功能测试中,甚至于不参与到项目整个研发流程中,无法真正的理解系统的功能、运行逻辑,在编写自动化测试案例时,无法判断案例设计是否合理、是否能够覆盖到真实的功能,无法从最优的角度合理设计案例。
由于项目迭代节奏快、系统功能多,而自动化测试人员少,自动化案例转化受限于人力资源,一直落后于功能研发速度,长期持续就会积累很多自动化负债,自动化测试无法达到预期的回归守护功能的要求。
上述可以总结为自动化测试专岗的问题。为了改善这一问题,我们动员了测试全员进行自动化建设。这并不是说全部测试都变成只做自动化测试,而是全部测试都参与到研发过程,参与功能测试、自动化测试。
这里需要先介绍我们使用的自动化框架:Gauge。这是一个开源的BDD自动化框架,简单灵活,基于MarkDown格式,跨平台、跨语言、插件化的框架。我们底层使用Python语言进行关键字的开发,用关键字组合成一个个场景案例:

.spec 是一个测试集合,文件里至少要包含一个测试场景xx 开头的为描述此测试文件的用途,即标题# xx 开头的为描述此测试场景的用户,即小标题* xx 开头的为测试步骤,即为关键字

addTeamCard 为python方法,通过使用@step 装饰器绑定到.spec文件中的*测试步骤上Python语言对于无编程基础的人员来说入门相对简单,Guage这种关键字形式的自动化框架,是一种前期积累后期收益框架,只要编写好关键字,各类场景只需要通过关键字组合就能达成目标,是一种高效的自动化形式。通过对其他测试成员的培训,测试全员都参与到了自动化建设中。
业务知识的挑战
写自动化测试案例的时候,先从不需要依赖其他模块的简单的功能开始写起,或者可以从目前已有依赖的案例写起;在比较熟悉了之后,就可以结合当前正在进行功能测试的用户故事编写自动化测试案例了。
关于Python语言
Python是一个入门门槛较低的代码语言,加之之前有过代码基础,以及对于测试人员来说,Python的使用程度不需要太深,就可以先直接开始写关键字方法。快速上手,可以先照猫画虎,复刻他人的框架后,再根据实际情况进行修改。
Gauge框架和Markdown写法
刚开始接受培训时,确实是云里雾里的,但是当接触Markdown之后,发现这是一个极其容易上手的东西,只要关键字写好后,写自动化案例也变成了一件很简单的事情。
随着测试全员自动化、案例数量增加,每天执行测试耗时显著增加,检查测试结果时,我们发现测试案例稳定性变差了,每次都要花费大量精力排查问题。主要表现为以下几点:

我们对这些问题逐个进行了分析。
针对问题1和2,起初,统一的策略就是在不稳定环节增加等待关键字,例如新建完卡片后,等待X秒再验证卡片是否创建成功。但多次实践后,我们发现这样依然无法保证稳定性。因此,我们引入了案例重试机制:
增加了等待xx相关的关键字,主动等待相关缓存数据,案例里会受缓存影响相关步骤,增加此等待步骤:

引入了python里retry相关依赖,增加步骤重试机制,步骤内python代码如果遇到异常,会重新执行:

针对问题3-5,我们引入了造数概念,对案例的数据准备进行了改造:
将案例中数据准备过程剥离,放到造数文件(JSON文件),案例只保留测试主体过程
按功能模块划分案例,聚合案例中的数据,以减少重复造数,提高数据利用
specs案例优化,保证案例不依赖其他specs案例产生的数据
由于知微是一个高度配置化的产品,测试一个场景前,需要先把整个测试场景配置出来,所以才出现测试准备过长的现象,但这些测试准备过程,并不是案例本身关注的主体,案例主体只关注要测试的主要目标。
例如,在一张看板上新建卡片,验证卡片数据是否正常。这个场景只关心在看板上的新建卡操作,并不关心看板是怎么来的。因此,我们将测试案例中测试准备过程剥离出案例,简化成一个编写业务数据文件(JSON文件)的过程,业务数据文件里,只需要编写知微上的用户界面输入内容。然后,我们编写了一套将JSON文件转化为系统数据的造数代码,这样简化了测试数据的准备。这一过程我们称之为造数阶段,至此,我们将测试分为造数执行阶段、测试执行阶段2个过程。
此外,我们还按照功能模块划分了案例文件,整合测试数据到数据文件内,提高数据的可重用性,减少同类案例里的测试数据准备阶段,以减少测试案例的步骤、减少执行时长,提高案例执行的稳定性。
通过以上方式,我们解决了测试执行耗时过长、稳定性差的问题,让自动化测试提供了一个可信任的测试结果,使之能够持续的发挥其应有的责任。

经历了前两个阶段的积累,自动化测试逐渐覆盖了测试人员的大部分回归场景,将测试人员从繁重的手工回归测试中释放出来。
但随着团队规模变大、开发人员增多,测试人员在自动化投入上又开始变得不足,赶不上版本节奏,自动化负债开始上升。在测试人员不增加的情况下,我们引入开发人员来编写自动化,在这个过程中主要发现了两个问题:
编写的关键字,存在不良设计,不利于推广使用
开发对于业务端到端流程不熟悉,对造数工具、案例编写都需要培训
为此我们展开了自动化代码的又一轮重构,关键字重构。
关键字的设计尤为重要,其设计是否合理会影响案例的可读性、关键字是否易懂、易使用。什么是良好的关键字设计——
良好的关键字应该用纯业务语言描述;
#不合理的关键字,关键字耦合接口信息,且表达形式上没有完全用业务含义文字描述* 移动故事价值单元|from|to|optDate||----|--|-------||0 |1 |20170207090101|#合理的关键字* 从 "需求澄清" 移动故事价值单元到 "研发中"
良好的关键字不应该使用隐形的数据,即数据被隐含在代码层,没有在关键字层体现,出现问题时候定位需要查看代码实现才能了解具体问题;
#不合理的案例* 列表快捷编辑-修改状态* 检查卡片状态#合理的案例* 列表快捷编辑-修改状态 "故事-1" 修改状态为价值流 "价值流-故事" 的状态 "设计中"* 检查卡片 "故事-1" 所在状态为 "设计中"
关键字重构后,测试案例更易懂了。我们还整理了相关使用手册、说明文档,对开发人员进行了一系列相关的培训,包括:
Gauge 框架,Specs案例编写的培训
Python语言基础使用培训,整套自动化测试代码规范,案例编写流程
业务端到端流程介绍,造数流程介绍及工具使用
关键字的查找、编写、组合
经过了一段时间的开发、测试协作,开发人员也都具备了编写自动化测试案例的能力。随后,我们将功能开发完成进行了重新定义:开发人员完成功能开发、且编写了HappyPath的自动化测试案例后,才算完成开发,并以此作为测试准入的标准,在桌面检查的时候进行核实。
Gauge框架是否友好,容易使用?
使用起来很简单,之前没有BDD相关经验也可以快速上手。
写接口自动化案例是否对代码质量有帮助?
对于技术债务,有自动化接口测试的情况下,可以放手重构。
开发写接口自动化作为测试准入,是否对质量有帮助?
明显的作用是,减少了进入测试阶段后,低级错误的发生,也减少了之前正常的功能出现缺陷的情况。
同时,我们也开始收集后端各服务自动化覆盖率信息,以覆盖率趋势评估各微服务的测试情况,并和开发定期评审服务的覆盖率信息,整理哪些场景未覆盖,以进一步提高代码覆盖率和业务覆盖场景。

图 / 知微各服务的自动化覆盖情况
从有自动化案例开始,我们就将自动化案例集成到持续集成流水线上,由原来的30分钟,执行耗时一直随案例数的增加而增加,所以,我们一直都是每天只在晚上执行1次。为了能让自动化提供更快速的反馈,我们希望每次持续集成都能全量执行测试,以提供全面的质量反馈。
但是,如果持续集成流水线执行耗时太长,会导致流水线执行频率低、无法提早反馈问题。而且,执行频率低,每次执行所包含的代码提交会比较多,也不利于在出现问题时定位相关代码。
只有做到尽可能的小批量提交编译、测试、反馈,才能更好的保障代码质量。为此,我们重点优化了流水线各节点的耗时,也从整体流程上优化了整体流水线的执行耗时。
单点优化:我们重点优化了编译、执行测试的执行时长
编译:我们通过使用mvn提供的多线程编译,将整个项目(10+个微服务)的编译耗时从之前的10分钟,降低到2分钟;
测试执行:我们通过按功能拆分了测试案例,然后通过增加jenkins的slave节点和可执行的Job线程数,来增加可并行执行的测试数量,将所有测试的执行耗时从之前的30分钟以上,降低到了10分钟;
流水线整体优化:

调整前(1条流水线-耗时90分钟)
每次集成流水线:
编译、替换基础数据库、发布、测试(造数+测试执行)、发送测试报告、执行错误重试、sonar覆盖率收集等各个阶段。
调整后(分2条流水线)
每日凌晨定时造数流水线:
替换基础数据库、服务启动、造数、保存每日测试数据库、执行测试、发送测试报告、执行错误重试、发送重试报告、sonar覆盖率收集;
每次集成流水线(耗时30分钟):
编译、检测是否需要造数(有造数文件变更)、替换基础数据库、服务启动、造数、保存每日测试数据库、执行测试、发送测试报告、执行错误重试、发送重试报告;
编译、检测是否需要造数(无造数文件变更)、替换每日测试数据库、服务启动、执行测试、发送测试报告、执行错误重试、发送重试报告;
通过以上优化调整,持续集成流水线的耗时有了巨大的改善,执行单次耗时从原先90分钟变为30分钟,加快了持续集成的质量反馈速度。
但执行耗时却并非越短越好。在经过一段时间实践后,我们发现30分钟的间隔,开发人员修复上一次失败都还未验证提交完成,就又有下一次的失败。因此,我们逐渐调整为1小时执行一次持续集成流水线。
由于每次持续集成流水线都执行了全量自动化案例,我们也将案例分配相关开发负责人,这样每次报告中的失败案例由对应案例负责人查看,并作为日常优先任务。避免了之前由统一人员查看带来的时间浪费。
自动化实践是一个积累的过程,只有当自动化案例积累到一定程度,才慢慢体现其守护质量的作用,在过去的一年里,不管是增强原有功能还是重构旧代码,自动化测试守护都起着至关重要的作用,在项目代码的不断迭代过程中,自动检测出这过程中引入的缺陷,解决了开发人员重构代码的后顾之忧,减少了测试人员的工作量,大大提升知微产品的稳定性。
新的一年里,我们希望能够继续完善自动化体系,以代码覆盖率为指导,完善测试分层体系,补充各服务的单元测试。同时持续监控测试案例的MTTR,保证自动化案例的及时修复,避免破窗效应。坚持使用自动化来守护知微,为知微产品保驾护航。
为了让大家更好的理解自动化测试的相关实践,本周四(2月24日)晚8点,我们还将组织一场自动化测试实践主题的直播,感兴趣的同学,可以点击以下视频号直播预约,会在开播前自动提醒。
本文作者周小宁,Agilean知微产品团队测试负责人