内部版:https://wiki.sankuai.com/x/PQcBOQ
1. 概述
我做自动化测试也好几年了,从 Robot 到 QTP,从 UFT 到 Selenium,从WebDriver 到 Appium ,从UI自动化到接口自动化,基本都玩了一遍,大大小小的文章也写了一些。
简单的说,自动化测试大致就是这个流程:
在测试圈里,看到很多同学对自动化也有很多的误区,比如说到自动化,谈的基本都是自动化测试工具,很少谈如何针对现有公司项目进行自动化,也很少谈及在公司最后落地的情况。
1.1 自动化测试银弹
“自动化测试银弹”这个词在前两年也是高频的出现。
当然,自动化测试不是“银弹”,这是肯定的,哪有什么一种工具就能解决所有测试问题的呢?
自动化测试不过也是一种工具,是工具就有它的局限性和使用场景。
而我们作为测试人员,要做好的就是“平衡”。
一味地码代码来做自动化,会发现很有可能投入的时间和人力,还不如手工点两下来的收益高,得不偿失,这块在UI自动化方面更为明显。
但也不是否定UI自动化的价值,有选择地进行UI自动化是能释放人力,提高效率的。
1.2 好的自动化
一个好自动化需要考虑的一方面是ROI(投资回报率)。
想要提高ROI,就得从两个方面入手,一个是减少投入成本,另一个就是增加自动化使用率。
在减少投入成本上,做到:
- 减少工具开发的成本
- 减少自动化测试用例的录入成本
- 减少自动化测试用例的维护成本
增加自动化使用率的话,尽量做到:
- 真正的自动化,尽量做到持续集成
- 健壮的自动化测试,支持较多场景
- 支持各种场景的测试
- 手工测试也能支持
所以在Lego的搭建上,使用的工具都是公司已有的,或是行业内使用广泛、已成熟的方案,如使用TestNG执行测试,测试用例使用@DataProvider来执行;如使用Jenkins来做每日构建,都是各处都能见到的解决方案,这些都是尽量减少前期的工具开发成本和学习成本。
另一个需要考虑的便是可靠性
那这里首先要说是关于“执行错误”。当我们自动化测试执行出错之后。
一般分为两种,一种是“被测系统错误”,另一种是“测试工具错误”。
我们希望看到的是“被测系统错误”,最不希望看到的是“测试工具错误”。
测试工具毕竟也是代码开发的,所以也会有自身的BUG,遇到“测试平台、框架错误”,应及时的修复。提供给使用者提交BUG和新需求的地方,来收集用户的反馈,提高测试工具本身的可靠性。
为了尽量避免“测试数据的错误”,我们需要对测试数据的有效性方案做一定的考虑和设计,保证使用的测试数据能实时有效。
在最初做自动化测试选型时,也参考了一些公司其他团队在用的接口自动化测试框架,有一些也使用TestNG进行测试执行,会将一条测试用例写以为一个java文件里,本身这没什么问题,但是在维护用例的时候,需要拉去最新代码->编码->push至Code平台等一系列代码操作,同时,不同的同学代码风格和能力都不同,很多同学会在测试用例中,留下很多硬代码参数,导致修改参数很麻烦;当多次迭代之后,变更参数,变更平台等操作几乎很难完成;当有新同学来接手自动化的时候,有的同学宁可重新写一版也不愿意维护原有自动化。
这些也都是问题,会将原本因专注于自动化测试用例设计的精力和时间分散至代码开发。
2. Lego上的设计
针对上面这些问题,我在Lego上尝试一些我的想法:
2.1 针对测试数据
首先应该想到是将测试数据与测试脚本分离,所以测试数据放在了DB中单独管理,这个在上一篇也已经说了。
将测试数据提出的好处有很多。
- 方便数据维护与修改,不需要为了改一个参数而更改代码,推送代码,或造成不必要的错误。
- 可以单独开发工具对数据库中的数据进行维护。
- 数据可统计,如共有case数据,新增case趋势,某服务下有多少条用例等。
- 便于批量修改。
- 如平台迁移,可快速迁移。只需做数据上的迁移,而不需要对每一条测试用例进行重新编码和重新录入,增加可移植性。
当然,如果需要使用数据库进行用例数据管理,则需要使用统一的数据库字段。设计的数据库字段应尽量满足各个团队的业务测试需要。
所以就想设计通用的测试用例模板。
2.2 针对校验点
在做自动化的时候,都会想尽量满足各种测试情况的需要。但当校验点多的时候,又会带来更多不确定的因素造成测试用例执行失败。
尽量充足的检查点,可以检查出被测服务更多的缺陷;
尽量少的误报情况,可以减少很多的人工检查和维护的成本。
这两者不应该矛盾,不能为了高成功率而减少校验的数量。
所以在设计检查点的时候,应支持下面一些校验:
- 支持获取返回结果中指定内容进行校验;
- 支持对返回结果中List类型数据个数进行校验;
- 支持对返回结果中List类型中每个内容进行统一校验;
- 支持获取数据库中实时的结果进行校验;
- 支持使用执行前数据库中的结果与执行后数据库中的结果进行校验。
测试数据要灵活,至少得支持上面这些情况,才能满足基本的校验情况。
另外统一检查点的使用方法,这样不管什么类型的接口,都可以快速接入和快速投入用例的编写。
2.3 针对健壮性
“动不动就调用接口失败”的情况不是我想要的。
“写死的测试数据又被删掉了导致测试失败”的情况也不是我想要的。
所以:
- 需要支持参数替换,随时替换成最新的数据;
- 支持参数部分数据批量修改;
- 支持一些token等数据根据UserID实时生成,作为测试用例参数使用;
- 支持在测试执行之前先创建一些数据,之后再销毁一些数据;
- 支持执行前获取一些数据,作为测试用例参数使用;
- 支持执行另一条测试用例,得到结果作为本条测试用例的参数使用。
从而提高测试用例的健壮性,让一条自动化测试用例在各个场景都能很好的完成本身的测试任务,真正发挥出一条测试用例的价值。
2.4 针对易用性
想上面说的,每种类型用例的“检查点”、“前后置动作”、“参数化操作”的操作都是一样的。
所以如果这时候又有一种全新的接口类型接入,应该也能完美地使用现有的设计进行测试用例的编写。
同时减少代码的操作,让测试人员开发自动化测试用例的时候,注意力能更多的放在用例设计本身。
增加易用性,哪怕不会编写用例的非RD人员,也应该能进行接口的执行和回归操作。
3. 一条测试用例
那么我设计的一条Lego平台接口自动化测试用例该是什么样子呢?
下面这张图就是我设计的一条测试用例该有的过程:
下面我会针对图中的各个部分,做一些详细的介绍。
3.1 测试发起
测试发起基本还是使用的Jenkins,稳定、成熟、简单、公司工具组支持。
也支持从Lego的web页面进行执行操作。
3.2 测试数据准备
使用 @DataProvider 的方式,从DB数据库中读取测试用例,逐一执行进行测试。详细的说明见上一篇(Lego测试平台(2)-脚本篇)。
3.3 参数替换
在正式执行测试用例之前,会先进行一波参数替换的动作。
简单的理解就是:
通过一些操作,将一个“值”替换掉测试用例里的一个“替代字符”。
为什么要有这部分操作呢?主要是解决下面几个场景的问题:
多个测试用例使用同一个参数进行测试。
● 无参数化时:
如50条测试用例都使用同一个id作为参数进行测试,我们需要修改id,需要修改50次,即每条测试用例中的id都得进行修改,而且可能会有遗漏。
● 有参数化时:
50条测试用例中,使用id部分用 ${myID} 替代,需要修改的话,修改“参数化维护”页面中维护 myID=00001 这条数据就可以,改动一条,下次执行所有的用例会使用新的 ${myID} 值进行接口测试的执行。
测试数据过期导致测试用例执行失败
● 无参数化时:
如参数需要使用Token,数据过期,导致测试用例执行失败,只能重新修改用例数据,并且重新执行测试用例。
或是编写id转Token的方法,方法可能会重复编写,多个团队之间可能实现方式也不同。
● 有参数化时:
使用Lego统一的方法,实时转换数据(如Token),保证执行测试时,得到的数据有效。
数据库数据获取以及有效测试数据获取
● 无参数化时:
如参数中传入dealID作为参数,如果这个dealID失效,那这条测试用例就会执行失败,而在测试环境中,一个订单时常会因为测试需要被修改数据,导致单号失效。
在代码中编写读取数据库的方法获取某些内容。
● 有参数化时:
通过一条SQL语句,实时查询出符合条件的一条dealID,替换掉测试用例中的${myDealID},保证测试用例使用的订单号总是有效。
不用在用例中编写数据库操作相关代码,减少开发和维护成本。
举个例子:
上面的这个用例中,在参数中有个传参,写了 ${stopdealid} ,这里就用到了参数替换。
从执行的结果中可以看到,在正式执行接口请求前,对接口中的 ${stopdealid} 部分,进行了参数的替换动作。
当然还有比较神奇的用法:
比如我们需要有个参数,内容是今天的日期,这时候也可以使用SQL类型来实现。
这样的话,在测试用例里使用 ${当天日期} 就能替换掉测试用例里的日期参数。
所以,通过参数化的使用,可以有效的提高测试用例的健壮性。
3.4 前后置动作
“前后置动作”的概念就比较好理解了:
在接口请求之前(或之后),执行一些操作。
目前前后置动作支持5种类型:数据库操作、已有测试用例、MQ消息、Http请求和Java方法。
主要也是解决下面几个场景的问题:
数据库数据准备
有时候在执行接口请求前,为了保证数据可用,可能需要在数据库中插入或删除一条信息,这时候就可以使用前后置动作里的“执行SQL语句”类型,在编写在接口请求前(后)的 Insert 和 Delete 语句。
试数据批量获取
之前参数化中的数据是通过SQL语句,得到返回结果中的某一个数据,要如果要想得到查询出的一整条信息中多个值进行替换,就可以使用“执行SQL语句”类型,编写 Select 语句,在得到的批量数据中,可以使用 ${pre.param} 或 ${pre.param[2]} 进行参数替换。
测试前HTTP请求
可以请求一些http请求动作,如清缓存、请求http接口、请求Pigeon4080接口等动作。
依赖另一条用例的结果
比如当前测试用例的请求参数,需要使用另一条测试用例的返回结果,这时候就可以使用“执行测试用例”类型,写上Lego上某条测试用例的ID编号,就可以在当前用例接口请求前(后)执行这条测试用例。
前后置动作中测试用例的返回结果可以用于当前用例的参数,对测试用例返回结果内容的获取上,也支持JsonPath和正则表达式两种方式。
发送MQ消息
在接口请求前(后)发送Swallow或Mafka消息。
其他方法执行
可以在Lego-Kit项目中,编写自己需要的java方法,选择“执行Java方法”,通过反射实现自定义java方法的执行。
Q : 那如果同样是获取三个参数,使用3个“参数化的Select操作”和使用1个“前置动作的Select操作”又有什么不同呢?
A : 不同在于执行时间上。
比如我们查询最新的有效团单的“单号”“下单人”和“手机号”三个字段。
使用3个“参数化的Select操作”:可能当执行${单号}的时候得到的订单号是“10001”,但是当执行到${下单人}的时候,可能有谁又下了一单,可能取到的下单人变成了“10002”的“李四”而不是“10001”的“张三”了,最后可能“单号”“下单人”和“手机号”三个字段去的数据并非同一行的数据。
而使用“前置动作的Select操作”:就可以避免上面的问题,因为所有字段的数据是一次性查询出来的,就不会出现错位的情况。
Q : 那“参数化的Select操作”和“前置动作的Select操作”这样不同的取值时机又有什么好用之处呢?
A : 由于“前置动作”一定是接口请求前执行,“参数化”一定是用到的时候才执行这样的特性。
所以在检查点中,如果要验证一个数据库字段在经过接口调用后发生了变更,那使用“前置动作”和“参数化”同时去查询这个字段,然后进行比较,不一致就说明发生了变化。
所以根据使用场景,选择合适的参数化方式,很重要,选择对了,能大大提升测试用例的测试数据健壮性。
3.5 接口请求
这部分就没什么好说的了,就是通过接口请求的参数,请求对应的接口,拿到返回结果。
这里Lego为了统一各种类型的接口结果,方便处理,都将返回结果保存为String类型,也方便了有新的接口类型的接入
3.6 检查点校验
检查点部分是一条自动化测试用例的精髓,一条自动化测试用例是否能真正的发挥它的测试功能,就是看QA对这条测试用例的检查点的校验是否做了良好设计。
在Lego平台上,目前我拥有的检查点有6种不同的类型。
● 异常检查点
○ 当返回结果为异常时,则会报错。
○ 但是有时候为了做异常测试,可以将这个检查点关掉。
● 不为空检查点
○ 字面意思,当出现””、”[]”、”{}”、null 这样的的结果,都会报错。也可以根据自己用例的实际情况关闭。
● 包含检查点
● 不包含检查点
○ “包含”和“不包含”检查点是将接口的返回结果作为一个String类型来看,检查所有返回内容中是否“包含”或“不包含”指定的内容。
● 数据库参数检查点
○ 字面意思,不做过多的解释了。
● JsonPath检查点
这是我在Lego上设计的最具有特色的一种检查点类型。
JsonPath的基本写法是:{JsonPath语法}==value
主要是针对返回结果为Json数据的结果,进行检查。具体的JsonPath语法可以参考: https://github.com/json-path/JsonPath。
刚刚说的”JsonPath的语法”,现在说一下”JsonPath检查点的语法”,”JsonPath检查点的语法”是我自
己想的,主要针对以下几种数据类型进行校验:
(1) 字符串类型结果检验:
● 等于 : ==
● 不等于 : !==
● 包含 : =
● 不包含 : !=
例如:
1. {$.[1].name}==aa : 检查返回的json中第2个Json的name字段是否等于aa。
2. {$..type}==’14’ : 检查返回的json中每一个Json的name字段是否等于aa。
3. {$.[1].type}==14 && {$.[1].orderId}==106712 : 一条用例中多个检查用&&连接。
4. {$..orderId}!==12 : 检查返回的json中每个Json的orderId字段是否不等于12。
5. {$..type}=1 : 检查返回的json中每个Json的type字段是否包含1。
6. {$.[1].type}!=chenyongda : 检查返回的json中第2个Json的type字段是否不包含chenyongda。
(2) 数值校验:
● 等于 : =
● 大于 : >
● 大于等于 : >=
● 小于 : <
● 小于等于 : <=
例如:
1. {$.[0].value}4 : 检查返回的json中第2个Json的value字段的列表是否大于4。
(3) List类型结果检验:
● list长度 : .length
● list包含 : .contains(param)
● list成员 : .get(index)
例如:
1. {$..value}.length=3 : 检查返回的json中每个Json的value字段的列表是否等于3。
2. {$.[0].value}.length4 : 检查返回的json中第2个Json的value字段的列表是否大于4。
4. {$..value}.contains(‘222′) : 检查返回的json中每个Json的value字段的列表是否包含222字符串。
5. {$.[0].value}.contains(1426867200000) : 检查返回的json中第1个Json的value字段的列表是否包含1426867200000。
6. {$.[0].value}.get(0)==’222′ : 检查返回的json中第1个Json的value字段的列表中第1个内容是否等于222。
7. {$..value}.get(2)=’22’ : 检查返回的json中每个Json的value字段的列表中第3个内容是否包含22。
(4) 时间类型处理:
● 时间戳转日期时间字符串 : .todate
例如:
1. {$..beginDate}.todate==2015-12-31 23:59:59 : 检查返回的json中beginDate这个时间戳转换成日期后是否等于2015-12-31 23:59:59。
PS: 数据库select时间时的样式设置:
SELECT DATE_FORMAT(r.BeginDate,’%Y-%m-%d %H:%i:%s’) as BeginDate,DATE_FORMAT(r.EndDate,’%Y-%m-%d %H:%i:%s’) as EndDate FROM TGDeal.TG_DealReceiptInfo r where r.DealGroupId=
Q:为什么要做JsonPath检查点?
A:使用JsonPath检查点可以对json返回结果中的某个指定的KEY对应的VALUE进行校验,甚至可以对批量规律的KEY的结果进行校验。
除了上面的的这些,我在JsonPath检查点的设计上,还考虑了多个数据的情况,
比如:
[‘good’,’good’,’bad’,’good’] == ‘good’:
如果等号左边的JsonPath得到的结果是一个列表的话,会拿列表里的每个对象逐一和等号右边的对象进行对比,每一次对比都是一个独立的检查点。
所以会变成四个检查点:列表第0个元素good是否等于good;列表第1个元素good是否等于good;列表第2个元素bad是否等于good;列表第3个元素good是否等于good。
[‘good’,’good’,’bad’,’good’]==[‘good’]:
这样写的话,就只会做一个检查点,会将等号左右两边的做一个列表的全量比较。
[[‘a’,’b’],[‘a’,’b’],[‘a’,’b’,’c’]] == [‘a’,’b’]
当然也会有这样的情况,道理和上面的一样,会做三个检查点的校验:列表第0个元素[‘a’,’b’]是否等于[‘a’,’b’];列表第1个元素[‘a’,’b’]是否等于[‘a’,’b’];列表第2个元素[‘a’,’b’,’c’]是否等于[‘a’,’b’];
除此之外,还有非常多的花样玩法,JsonPath中的检查支持“参数化”和“前后置动作”,所以会看到很多如{$.param}=’${param}’ && {$.param}==${pre.param}这样的检查点。
以上所有类型的检查点,都支持多个检查点检查,使用 && 连接符进行连接。
3.7 测试结果
这部分就比较简单了,使用ReportNG可以打印出很漂亮的报告。
我的报告会自定义一些高亮等展示方式,只需要在ReportNG使用前加上:
System.setProperty("org.uncommons.reportng.escape-output", "false");
就可以支持“输出逃逸”,可使用html标签自定义输出样式。
3.8 报表
当使用Jenkins执行后,有一些测试结果,通过Jenkins Api 定时获取数据,数据落数据库,可以统计生成图表。
根据公司统一的Jacoco代码覆盖率配置,同样能捕获到覆盖率的数据,落地数据库。并生成图表展示。
便于做数据分析。
关于用例篇的内容,就写到这里了。
更多的关于Lego网站上方便接口自动化操作的功能和设计,请期待下面的“网站篇”。
请问一下,关于JSONPath检查点,能否简单的说明一下具体是怎样实现的嘛?看到这么多判断表达式,相当于自己写了个规则啊
可以这么理解,其实是笨办法,就是字符处理和解析。我找个机会公开一下吧,这部分应该可以抽出来一下的
对于前后置动作中的参数化,占位符都是只带pre吗?如果前/后置分别使用不同的动作,但是返回的param是重名的,不会导致冲突吗?
不会,是自定义的,比如 ${pre.dealID}、 ${pre.userName}、 ${pre.PhoneNum},pre后面的参数都是配置动作的时候自定义的。
然后如果是sql的select,那就是表的列名,如果是多行数据,可以是${pre.dealID[0]}、${pre.dealID[1]},而且如果还是有重名,sql的列名是可以用as来改名的。
在执行用例的时候,常常有这样一个场景:
比如需要使用不同组的数据(包括入参和断言),分别执行一条用例,分别生成测试报告。
这种场景是如何实现的呢?
那就多个用例啊
还有个疑问,如果前后置的动作能够做参数替换了,那么参数化这样一个独立功能的价值在哪里呢?
在于时间点,前后置动作是固定位置的时机进行的参数获取,但是参数化是遇到的时候进行参数获取。
简单的说,比如${pre.x} x是前置动作里获得的值=0,那后面所有的${pre.x} 都=0,
但是如果参数化${y},这个在测试过程中会变动, 那可能${y}=1,用例中第二次用${y}=2,第三次${y}=3
明白了,非常感谢!
请问在参数化中创建的参数会指定某个用例使用吗?还有所有用例在执行前都会按照参数化的参数做一遍替换啊?
参数化不会指定某个用例使用,只要调用,都可以使用。
参数化是遇到替换字符,就进行参数获取。
您好想请问可否详细阐述下您的平台如何实现 上一条或前面用例的结果是下一个用例的参数?数据是如何传递的?非常感谢
是否可以用session?
我会在这条测试用例执行的过程中,有一个公共的map,用例执行前new起来,执行完成后干掉。当前后置动作中,生成的参数,会放入这个map,后面用参数${param}的时候,会用param这个key在这个map中找对应的value
楼主挺厉害,很想跟着楼主一起干活呢,,哎,能力不够,估计也不会招我。。。
努力吧少年~下个窗口期的时候,可以来试试
逹先生,最近都没有见到更新的文章。。。。。
什么时候会更新呢。。。
啊~~这两天~最近在忙着写公司的总结,然后开始写自己的总结
测试用例执行过程中会对数据库中的数据进行修改,这样下次执行测试用例的环境就发生了变化。
请问这个问题有没有比较好的处理方法?
我的设计里面,最小的“原子”是一条用例,一条用例包含了指定的环境、指定的参数,不会出现你说的问题。
如果一条用例对应多个场景,才会有你的问题
能聊聊么。 感觉你做的这一套很强大。 我也在做接口自动化测试。 不过用的robot framework+python.
测试数据以文件形式存储。 不如DB来的方便。
用例的组织在robot进行。 不如平台化的方便。
也很赞同创建的用例可以快速执行某些功能, 帮助QA,又或快速让PM验收功能。
嗯嗯
你好,不知道有没有对【windows,andriod交互测试】有没有好的方法能分享? 一个case里面包含了windows和android的操作
你是要做UI自动化么?加我微信聊吧
陈老师,
请问在Lego上如何进行测试用例的创建和测试数据的输入? 似乎文中没有提到这部分。
另外文中开头提到的在时间、人力、成本之间的平衡,窃以为不妥,因为时间和人力本身就是成本的一部分,是不是改成成本和收益之间的平衡更好?或者人力、时间与收益。
对的,可能改成“收益”会更合适一些。