自动化工具 论自动化测试脚本的质量与效率

孙高飞 · 2016年06月12日 · 最后由 Suson 回复于 2021年12月07日 · 6694 次阅读

前言

从我第一次在咱们社区发帖子的时候我就说过我的测试思路跟很多人不一样,导致于其实我跟很多人都有过冲突。其中一点就表现在我对自动化测试脚本的质量的执着。那么今天,就让我来说一点我自己的想法吧。可能跟大多数人想的不一样,请轻喷。

自动化测试脚本的质量与效率

自动化测试脚本的质量有很多方面,我今天直说我觉得最重要的几个方面:

  1. 自动化测试脚本互不影响的,隔离的。
  2. 自动化测试中被测功能是互不影响的
  3. 自动化测试能够快速定位 bug 位置
  4. 自动化测试脚本是易于阅读的,能帮助我们理解产品的
  5. 自动化测试脚本是易于编写的,易于维护的以及易于扩展的
自动化测试脚本互不影响的,隔离的。

我们先说这一点吧,为什么我们的脚本要是互不影响的,隔离的呢。因为我们所有的脚本都是运行在同一个环境的,每个脚本可能都会在这个环境上造成痕迹,这些痕迹可能是数据库的,可能是文件系统的。而这些痕迹造成了 case 之间互相的影响。举个最常见的例子,本来我们运行的脚本们都很成功,没有什么异常。但是突然有有一天,测试删除数据的脚本由于产品的 bug 失败了,数据没有真正的被删除。于是乎间接的造成了之后的查询所有数据的脚本失败了。因为之前的脚本没有删除掉数据,现在预期值和返回值不一样了----多了一条数据。这种情况发生在很多场景,可能是 case 中设置了产品的全局变量,语言的信息等等。下面我们说解决方案。

  1. 我最不推崇的做法也是所有人都最喜欢的做法,因为这种做法最简单 ---- 严格控制测试数据的使用。例如规定所有脚本不准许更改系统变量;测试某种语言或场景的时候新创建一个项目,不用已有的;维护一个 user 列表,不准许脚本使用重复的 user 以免互相影响等等。case 量不多的情况很实用
  2. 我推崇的做法也是《google 软件测试之道》一书中的做法:任何脚本都不做任何持久化的操作,也就是说测试脚本执行前数据库和文件系统是什么样子,执行之后也是什么样子。环境始终是干净的,对每个测试脚本都是一样的。怎么做呢? 很简单,你再测试脚本运行中创建的什么数据。你再结束后就删除什么数据。

下面我们分别说说吧,我把第一点称作我最不推崇的,虽然它看起来貌似没什么实现成本。但是这种纯靠所有 QA 之间的约定和个人的自觉其实很不靠谱。即便我之前在外企的时候有严格的 code review 流程也很难保证 case 之间互不影响,更别说国内的测试我基本就没怎么见过有 code review 这个流程。case 数量变多的时候,就慢慢的发现以前埋的坑有多大,很典型的情况就是,测试用例在单独跑的时候怎么跑怎么过。放到 server 上所有 case 一起跑的时候怎么跑怎么不过。这时候你还不知道到底是哪个 case 造成的痕迹导致你的脚本跑不过去的。一出现这种情况一般没个半天一天的根本查不出来到底是以前哪个 case 造成的痕迹破坏了你的脚本。当然了,如果你说你们项目一共也就一百两百的 case 量,那就当我没说吧。这种方式很好。

第二种方式我比较推崇,很黄很暴力。绝对杜绝了任何互相影响的可能性。成本比较高,但是可以从框架的角度解决这个问题,这样成本就降低到了一个可以接受的程度了。具体实现可以参考我以前的两个帖子,如下:
框架中测试数据的管理策略
框架中测试数据的恢复策略
很多人可能会喷我的这个观点,仁者见仁智者见智吧。UI 自动化和接口自动化我都用这种方式。

自动化测试中被测功能是互不影响的,隔离的&& 自动化测试能够快速定位 bug 位置

我把这两点写在一块了,因为解决这两点的方案是一样的。我们以前一定碰见过很多次这样的情况,由于下单这个功能 bug 了,导致所有依赖下单这个功能的脚本全都失败了。为什么呢?因为用例都是流程性的测试,都是先下单,再查询订单,再更新订单,再评论订单等等。我们把相似的功能都放在数个脚本里测试了。这样的结果有两个,第一是可能因为一个 bug 导致 N 个脚本的 fail。你需要挨个排查很影响效率。第二是以 QA 的能力很难定位 bug 到底出现在哪。举例说明一下,假如脚本在查询订单的时候失败了,你能确定一定是查询订单这个 feature 出 bug 了么?不一定吧,可能下单的时候就没入库正确。当然了我只是举个最简单的例子,下单和查询其实还是很容易定位的,但实际情况很复杂。很可能入库的时候的错误在脚本最后的某个功能上才体现出来。例如运行某些 task,需要在入库时的一些元数据等等。

解决方案----就是减少流程性的测试。把被测的产品功能打碎了,切成一块一块的个体。一个脚本中只测试一个 feature。杜绝了互相影响的同时也简化的 bug 定位的成本,因为你脚本里只测试了这个 feature,失败了就肯定是这个 feature 的问题了。只要你代码能力还行,直接去看产品源码就能找到 bug 到底出现在哪一行代码中了,我用这种方式 fix 过很多个 bug。有些同学一定会问,这不可能实现,因为测试当前功能一定要其他功能创建数据啊?那到底怎么做呢? 方案也很简单--直接在数据库,文件系统,索引等等中插入数据,不使用产品本身的 feature 创建数据,我们仔细想想,除了单元测试之外,我们的这些功能之间真的有依赖么? 答案是没有,功能依赖的是数据,是数据库中的数据。你再 UI 上走一遍流程也就是在数据库中创建了一系列的数据而已。so,如果有同学疑虑说没有流程性测试其实是不完整的测试的话。那你大可不必担心。因为这跟流程性测试根本没区别,有什么流程性测试的数据是你用 sql 模仿不了的?所以有区别的只是造数据的方式而已。这种方式的脚本成本一般也比较高,因为要清楚产品数据库的结构,要写 sql。但是我们同样可以在框架上解决这个问题,同样看我上一点发的两个链接。里面有提及。

自动化测试脚本是易于阅读的,能帮助我们理解产品的

这个比较容易懂,就是你得能让人很简单的就看明白你的脚本都在干什么,验证什么。《xunit test parttern》一书中推崇测试脚本可以当成产品文档一样阅读。开发没时间写文档?那么代码就是文档,测试脚本就是文档。举个例子吧

Cookie cookie= this.login("user","password");
//String temp = 
given()
    .cookie(cookie)
    .contentType("application/json")
    .body(project)
.expect()
    .statusCode(200)
    .body("status", equalTo(0))
    .body("msg", equalTo("ok"))
.when()
    .post("/projects/380").asString();

Request request = new Request(dataSource,"select * from project where id = 380");
assertThat(request).row()
    .value("name").isEqualTo(project.getName())
    .value("description").isEqualTo(project.getDescription());

上面的脚本我们很容易看出来我们访问了/projects/380 这个 API,传递了一个 project 对象当做参数,反正了各种返回值后并到数据库中验证了 project 的 name 和 description 的属性。这个依赖于 QA 良好的编码规范以及测试框架的支持。有兴趣的看我之前发的关于可毒性,可维护性,可扩展性的那篇帖子吧。

自动化测试脚本是易于编写的,易于维护的以及易于扩展的

这一点我也得好好的说明一下,因为现在关键字驱动录制回放式框架的盛行。让人很容易陷入一种幻觉,那就是不会写代码的 QA 用这种工具就可能完全代替写代码了。这种测试框架也变成了一种高大上的证明。完成了这种框架十分有利于你的 KPI,装逼首选方案(实际上我也这么干过)。之前迫于无奈,我上一家公司实在没几个 QA 会写代码,我只能推行关键字驱动了。领导觉得很牛逼,很高大上,各种到处吹嘘,最后吹的我自己都不好意思了。但实际上关键字驱动的效率,我心里清楚的很。极其的不灵活,极其的不利于扩展,你想加什么功能,除了实现以外你还得想着怎么设计关键字能让不会写代码的 QA 能接受。到了最后为了实现各种各样丰富的功能以满足测试需要后。发现特么的关键字的语法都很复杂了。这特么的比他们学写脚本好不了多少。而且没有 debug 能力的 QA 搞自动化本身就不靠谱。会写代码的 QA 也极其反感这种框架,写时间长了直接就是离职的节奏。录制回放式的更不用说了,直接无法满足我上面说的几个提升脚本质量的点,再一个录制回访式的框架录下来的脚本。。。我就不说了。。。坑爹 +1. 所以我一直觉得测试这行有个误区。那就是以不让其他 QA 写代码为荣耀,却忽略了脚本本身的个质量。我觉得不能无限制的为了降低学习成本而牺牲质量和效率,还是想想办法怎么提升团队的技术水平吧

OK,说了这么多吐槽的话,我一直没说怎么做?我也不想说了,因为太多了。我的整个《测试开发之路》系列一直在说这个问题。有兴趣的就去看看我以前的帖子吧。

尾声

其实脚本质量还有其他方面,我在这就不说了,要不又该长篇大论了。我就跟个老头似的,啰嗦起来没完没了。最后说点感慨吧。工作快 5 年多了。从以前安安静静的写自动化脚本,到为了那种关键字驱动而废寝忘食,再到追求各种能装逼的架构和平台,最后才到现在又回归到平平淡淡的写脚本,我领悟到了一件事:不要为了各种花哨的东西丢失了最基本的东西。还是那句话:前路走好,勿忘初心

如果觉得我的文章对您有用,请随意打赏。您的支持将鼓励我继续创作!
共收到 30 条回复 时间 点赞

从我第一次在咱们社区发帖子的时候我就说过我的测试思路跟很多人不一样,导致于其实我跟很多人都有过冲突。

为什么看起来一点都不觉得冲突呢~哈哈,我很赞同。

写得好,赞~然而我们做 UI 自动化测试,业务不太一样,写着写着很容易 “变味”,变成了脚本迁就软件质量的情况,而且这个泥潭越陷越深,每次版本迭代都要花很大力气去 review 脚本实现 ‘pass’,有种本末倒置的感觉,然而关键字驱动的框架并不能很好的改善这个情况

所以我一直觉得测试这行有个误区。那就是以不让其他 QA 写代码为荣耀,却忽略了脚本本身的个质量。我觉得不能无限制的为了降低学习成本而牺牲质量和效率,还是想想办法怎么提升团队的技术水平吧。

+1
我也认为做技术类测试,还是要懂一点代码的,至少要了解自动化整个过程中处理逻辑。就算不写,也要知道系统中做了什么。

第一点其实会增加很多冗余

你的思路很正常,应当如此。

—— 来自 TesterHome 官方 安卓客户端

#4 楼 @pacerron 冗余难免,但是脚本不独立很麻烦

所以我一直觉得测试这行有个误区。那就是以不让其他 QA 写代码为荣耀,却忽略了脚本本身的个质量。我觉得不能无限制的为了降低学习成本而牺牲质量和效率,还是想想办法怎么提升团队的技术水平吧

不能同意更多,感觉一半的代码都在解决怎么让不懂代码的人写 case 以及其带来的问题,还很难扩展

所以我一直觉得测试这行有个误区。那就是以不让其他 QA 写代码为荣耀,却忽略了脚本本身的个质量。我觉得不能无限制的为了降低学习成本而牺牲质量和效率,还是想想办法怎么提升团队的技术水平吧。

非常同意,测试不会代码的时代已结束了。

#5 楼 @frankliu 以前在社区发过类似的帖子。当时被喷了。我以前的同事也不太赞成,所以我总觉得我跟其他人的思路不一样

不能无限制的为了降低学习成本而牺牲质量和效率,还是想想办法怎么提升团队的技术水平吧。

深有同感啊,公司新招聘了 4 个实习生,两个功能测试内转的。java 单行注释,方法注释都没有整明白。。。。。代码质量真心有待提高。。。。。。

#10 楼 @success 额,实习生么。。理解一下吧。。

#11 楼 @ycwdaaaa 理解啊。可是公司严重缺人啊。

任何脚本都不做任何持久化的操作,也就是说测试脚本执行前数据库和文件系统是什么样子,执行之后也是什么样子。环境始终是干净的,对每个测试脚本都是一样的。怎么做呢? 很简单,你再测试脚本运行中创建的什么数据。你再结束后就删除什么数据。

学习了 太赞同

经验之谈,感谢分享。其实所谓的自动化就是找最合适自己的测试方法。

保持自动化测试用例的有效性,基本就是一个投入大人力的过程。另外你觉得好的设计并不一定是别人眼中的好设计。业务快速增长之后,你的架构能否无缝的包容这些增长。

你所说的几点都不错。如果能把这些揉入到整个自动化测试架构中去,让写用例的人无感知的遵守你的约定,那就更好了。

#15 楼 @lihuazhang 这也是我追求的。

#16 楼 @ycwdaaaa 我尝试用你的理论去和老大讨论,被反驳的重点也是执行效率问题,太费时间了。我们做 UI 自动化,每条测试用例结束后数据都恢复初始化,无形中也会降低给予设备的 “压力”。这一点不知道你是如何考虑的,可能业务不同,出发点也不同吧。总之会谈不欢而散,谁也说服不了谁,继续深陷在维护用例的泥潭里😤

#17 楼 @jamesparagon 额,我没太看懂,你们老大反驳的点是?

#18 楼 @ycwdaaaa 他又要保证回归测试的正确性,又要测试稳定性,二者兼顾。几千条 case,要都分割独立化的话要执行很久

#19 楼 @jamesparagon 嗯,如果你用第一种方式的话。其实性能上没什么影响。 用第二种,也就是测试用例运行结束后删除数据。是会慢一些。因为有很多的数据库操作。 针对这个问题,我是这么做的。不是每执行一个脚本都删除数据的。可以给用户留下策略让他决定运行一批 case 后再删除数据。这样就可以快一点。我是从框架角度做的。框架监控数据库, 根据用户设计的删除数据的策略来做。

@DataManage(baseData="testData/project/defaultProject.xls",recoveryStrategy=RecoveryStrategy.METHOD)
public class TestDeleteProject extends BaseCase {

就像上面这样,recoveryStrategy 决定了恢复数据的策略。 可以 method 级别,可以 class 级别。class 级别就是所有在这个类中的用例都结束后再恢复数据

#20 楼 @ycwdaaaa 嗯,这个方法是挺好的,只不过我们的关键字驱动框架改起来很麻烦,一条一条的,从脚本设计之初就要考虑到要不要保留数据,牵一发动全身很痛苦

#21 楼 @jamesparagon 我明白了,你们的测试用例是链式结构。前一个脚本创造的数据留给下一个脚本使用。 这确实是很不好的方式。之后也很难改

很多人费时费力地写框架,看起来似乎是不用写代码了,然而实际上是在 EXCEL 里面写代码,还是自造的语言还没有文档,稍微格式一不对它就呵呵了

仁者见仁智者见智

孙高飞 [该话题已被删除] 中提及了此贴 06月28日 18:51

观点都很赞同,尤其是关键字驱动框架那块,这种框架纯粹是个 toy

孙高飞 测试开发之路 ---- 代码覆盖率 (EMMA) 中提及了此贴 06月01日 15:11

好的场景测试用例确实很难写

请教一下大佬,初级自动化工程师刚开始入行是从自动化测试脚本开始的吗?不涉及框架维护。自动化测试脚本本身技术含量有多深?要写好也是不容易的吧。还是说要结合相应的框架才能有技术深度呢

飞哥,你上面说的比如数据库和文件系统测试前是什么样,测试后也是什么样,我之前在项目中也是这样,在测试后会把本次测试的数据全部进行处理,但是就因为这样导致数据库数据是没有了,但是系统更底层却有这个数据,但是当时我是不知道的,直到开发发现问题,查了很久才明白问题的由来,于是乎项目负责人就不让我去动数据库了,所以现在跑脚本的数据都是动态生成的,跑完也不会去删除,因为删除会影响,这种情况怎么做更好呢?

用 sql 的形式,线上环境没有权限咋整呢。。

Guo Dalu 回复

所以这思路只是看场景和业务,测试环境可以这么跑。生产环境还是用回接口吧,不然,这样有可能导致冗余,导致线上系统缓慢

ABEE ycwdaaaa (孙高飞) 在 TesterHome 的发帖整理 中提及了此贴 01月12日 13:47
需要 登录 后方可回复, 如果你还没有账号请点击这里 注册