Mock+Proxy在SDK项目的自己主动化測试实战

时间:2021-10-08 14:36:22

项目背景


广告SDK项目是为应用程序APP开发者提供移动广告平台接入的API程序集合,其形态就是一个植入宿主APP的jar包。提供的功能主要有以下几点:

- 为APP请求广告内容

- 用户行为打点

- 错误日志打点

- 反作弊

团队现状


在项目推进的过程中。逐渐暴露了一些问题:

1. 项目团队分为上海团队(服务端)和北京团队(client),因为信息同步,人力资源等其它原因。服务端与client的开发进度非常难保持同步,经常出现client等着和服务端联调的情况

2. 接口文档不稳定,理解有偏差

3. 协议变化频繁,消息不同步

4. 缺少服务端測试环境,可模拟的真实广告内容太少

5. 协议字段太多,传统的測试用例设计方法easy出现遗漏,尤其是异常情况处理。測试完毕以后測试人员对字段覆盖率没有信心

协议字段演示样例图

{
"ads": [{
"action": { "path": "" },
"adw":920,
"adh":900,
"template": "",
"action_type": 2,
"adid": "67346778",
"adm": "",
"adm_type": 0,
"deep_link": "",
"impid": "nXcM_kqBGqL=",
"tk_act": [""],
"tk_imp":[ ""],
"tk_ad_close": [""],
"tk_clk": [""],
"tk_dl_begin": ["" ],
"tk_dl_btn": [ ""],
"tk_dl_done": [""],
"tk_dp_suc": [],
"tk_ins": [ ""],
"tk_open": [""]
}],
"errno": "0"
}
  1. 測试用例设计极easy受需求影响,更新起来非常麻烦,成本非常高
  2. 手工測试方法运行效率低,且easy漏測

手动測试过程示意图

Mock+Proxy在SDK项目的自己主动化測试实战

分析&思路


上述几个问题,当中1、2、3都会对我们的測试工作产生影响。可是属于项目管理范畴。不在本文讨论范围内。那么针对4、5、6、7几个问题,应该怎样解决呢?

首先分析一下问题:

Q: 缺少服务端測试环境,可模拟的真实广告内容太少

A: 因为服务端团队在人力上的不足,无法为我们提供測试环境。通过沟通协商,方法暂定为由服务端同事预先配置好线上广告物料,即固定的线上广告资源。能够覆盖提測的广告类型,在服务端完毕功能逻辑之前,先利用mock方式測试client的功能逻辑以及展示,此时client和服务端后台无需交互。

Q: 协议字段太多。传统的測试用例设计方法easy出现遗漏,尤其是异常情况处理

A: 制定一个可靠的測试用例设计策略,以最少的case覆盖最多的情况。

Q:測试用例设计极easy受需求影响。变更起来非常麻烦,成本非常高

A: 对測试用例进行拆分,分为正常返回情况和异常处理两部分。

正常的处理包含系统环境、网络切换、下载、轮播、缓存、正常打点、安装卸载、UI检查等等须要人工检查的情况,因此这部分我们先梳理checklist,先组内review,再约产品和研发一起review。确保需求的完整性,另外开发过程中的需求变更是不可避免的,对于需求的变化要做到实时更新case。这部分case覆盖的点要足够全,而文字描写叙述要尽量的精简。确保更新起来能高速响应节奏的变化。而异常的部分。我们的做法是批量自己主动化生成case。生成策略会在以下具体描写叙述。

Q:手工測试方法效率低,且easy漏測

A: 正常的功能我们通过手工測试的方法覆盖,而对于client拿到的异常情况的error code要有全量的覆盖,比方我们的错误代码约定了21种,那么针对全部可能出现的错误代码都要想办法触发,这一部分工作希望从case生成到用例运行能100%的自己主动化实现。

调研过程


有了解决思路,那么须要想办法把想法落地。我们提炼出几个须要攻克的技术难点:

  • 难点一:mock框架选型

    做过单元測试的同学应该了解“桩(stub)測试”。即通过hard code方式验证函数的输入输出是否准确和健壮,而mock測试和桩測试相似,功能要更加丰富一些,能够模拟产品交互环节中的部分场景,换句话说,能够让測试工作提前介入研发流程中。

    多用于须要联调的环节,比方支付场景,购买流程,第三方插件调用等等业务。之前我们採用的Fiddler重定向请求结果到本地文件的方式模拟服务端的response来欺骗client,也能够理解为mock測试。

    最初我们计划自己写一个proxy server监听指定port,截取全部的http/https请求。再替换response内容完毕mock測试。后来一次偶然的机会接触了阿里开源出来的anyproxy(http://anyproxy.io/cn/),了解了一下该工具,发现这款工具刚好满足了我们的几个需求:

    • 代码开源
    • 规则可定制
    • 支持https可视化
    • 易部署、学习成本低
    • UI可视化。相似fiddler

    Mock+Proxy在SDK项目的自己主动化測试实战

    实际使用截图(我们对response展示做了点优化):

    Mock+Proxy在SDK项目的自己主动化測试实战

  • 难点二:可靠的測试用例设计策略

    在讨论接口測试用例设计之前,我们须要预先圈定一个思考范围,以免过度的思维发散。

    结合我们的业务特征,因为SDK的功能大部分是单接口。少部分是关联接口。因此我们的设计基于单接口而非单个业务场景

    接口的測试用例设计有别于其它測试用例,其业务逻辑主要体如今字段的取值上,每一个取值体现了一种业务逻辑,我们做了一些调研。学习了其它业务团队的接口測试用例写法。发现測试人员喜欢这样设计case:

    Mock+Proxy在SDK项目的自己主动化測试实战

这种case无疑是工整、直观的,可读性比較强。非常方便的复制粘贴,再通过改动当中的一个或者几个值,形成一个庞大的二维数组。

看到这个表格。一些熟练的測试project师会立刻联想到边界值、等价类设计、正交试验法等。

然而要想保证每一个场景都被完整的覆盖。理论上我们须要測试全部字段的笛卡尔积。这种方式能够保证不论什么取值都会被覆盖到。可是当字段比較多的时候。測试用例的数量会呈爆炸式的增长。毫无疑问这种方式是不可行的。我们须要一个算法,能做到以下几点:

1、 以最少的组合覆盖尽可能多的场景

2 、覆盖全部字段的全部取值

3 、有统计学支撑,生成的数据有规律可循

有了需求,我们開始进行了可行性方案的研究,秉承不反复造*的理念,我们查阅了国内外非常多的资料,逐渐的缩小了范围,在说出解决方式之前,先给大家简介两个重要的算法:“ OATS(Orthogonal Array Testing Strategy)”和“Pairwise/All-Pairs Testing”。简称“正交表法”和“配对測试法”。


正交表法

正交表法有两个重要的特性,大家尝试着理解一下:

1.每列中不同数字出现的次数相等

备注:这一特点表明每一个因素的每一个水平与其它因素的每一个水平參与试验的几率是全然同样的。从而保证了在各个水平中最大限度地排除了其它因素水平的干扰,能有效地比較试验结果并找出最优的试验条件。

2.在随意两列其横向组成的数字对中,每种数字对出现的次数相等

备注:这个特点保证了试验点均匀地分散在因素与水平的全然组合之中,因此具有非常强的代表性。

举个样例:有三个字段,每一个字段能够取三个值,设字段表现为A(A1,A2,A3)、B(B1,B2,B3)、C(C1,C2,C3),能够组成的集合恰好能够表现为一个三维空间图,例如以下图所看到的:

Mock+Proxy在SDK项目的自己主动化測试实战

图中的正方体中每一个字段的每一个水平代表的是一个面。共九个面,随意两个字段的水平之间都存在交点,共27(3x3x3)个,这就是笛卡尔积。依照两大特性设计出的正交表如右图所看到的,试验点用⊙表示。我们看到,在9个平面中每一个平面上都恰好有三个点而每一个平面的每行每列都有一个点,并且仅仅有一个点,总共九个点。

这种试验方案。试验点的分布非常均匀。试验次数也不多。

国外有一个站点能查询正交表的结果案例:http://www.york.ac.uk/depts/maths/tables/orthogonal.htm

Mock+Proxy在SDK项目的自己主动化測试实战


配对測试法

配对測试法(Pairwise)是L. L. Thurstone( 1887 – 1955)在1927年首先提出来的。他是美国的一位心理统计学家。

Pairwise是基于数学统计和对传统的正交分析法进行优化后得到的产物。

定义:Most field faults were caused by either incorrect single values or by an interaction of pairs of values.” If that’s generally correct, we ought to focus our testing on the risk of single-mode and double-mode faults. We can get excellent coverage by choosing tests such that 1) each state of each variable is tested, and 2) each variable in each of its states is tested in a pair with every other variable in each of its states. This is called pairwise testing or all-pairs testing.

大概意思是:缺陷往往是由一个參数或两个參数的组合所导致的,那么我们选择比較好的測试组合的原则就是:

1)每一个因子的水平值都能被測试到。

2)随意两个因子的各个水平值组合都能被測试到,这就叫配对測试法。

參看:http://www.developsense.com/pairwiseTesting.html

Pairwise基于例如以下2个假设:

1. 每一个维度都是正交的。即每一个维度互相都没有交集。

2. 依据数学统计分析。73%的缺陷(单因子是35%,双因子是38%)是由单因子或2个因子相互作用产生的。19%的缺陷是由3个因子相互作用产生的。

因此。pairwise基于覆盖全部2因子的交互作用产生的用例集合性价比最高而产生的。国外也有一份相似的数学统计:

Mock+Proxy在SDK项目的自己主动化測试实战

我们通过一个订飞机票的实际样例来看一下,配对測试法是怎样从笛卡尔积中提炼出局部最优解的。

依旧是三个字段的组合,各自是Destination(Canada, Mexico, USA),Class(Coach, Business Class, First Class), Seat Preference(Aisle, Window)。所相应的笛卡尔积共同拥有3x3x2=18中測试组合,例如以下表所看到的。

Mock+Proxy在SDK项目的自己主动化測试实战

经过配对測试法筛选后,结果例如以下:

Mock+Proxy在SDK项目的自己主动化測试实战

经过筛选以后,我们的測试用例变成了9条。case数量精简了50%。简单总结pairwise的筛选原理就是,发现两两配对在全集中有反复的就去掉当中之中的一个,这样筛选也有副作用,每次筛选完了条数是固定的。可是结果却不尽同样。可是通过上面的介绍我们不难比較出两种算法的差异。

说了那么多。再回到我们之前提到的设计策略几个需求,能够觉得pairwise算法的特征基本满足了我们的需求。

  • 难点三:測试用例自己主动化生成

    确定了用例设计的算法策略后,我们信心十足的准备開始设计我们的response返回值case了,我们套用文献中的排列分布方式应用到实际接口json中,悲伤的发现我们要组合的字段不是3个。而是20-35个左右,假设通过人工的方式来进行case设计的话,就算仅仅考虑最多两个字段的值发生变化。数量也是非常惊人的,WWWWWWhat???

    Mock+Proxy在SDK项目的自己主动化測试实战

本着“偷懒是人类进步的第一动力”的想法,我们自然不会前功尽弃,自己主动化測试是我们的必选之路,接下来要做的就是调研眼下已经存在的基于pairwise算法的工具有哪些,以下是经过调研后得到的工具列表。

Mock+Proxy在SDK项目的自己主动化測试实战

基于pairwise算法的工具如此之多,那么同样模型设定下产生的结果是否存在差异呢?我们看一下这张图:

Mock+Proxy在SDK项目的自己主动化測试实战

综合比較各工具产生的数据结果后,我们能够发现不同工具之间的结果差异并不大,基本上能够满足我们现有的需求。经过一番讨论后,我们决定採用微软的PICT(https://github.com/Microsoft/pict)作为case生成工具。原因有几点:

1. 代码开源可扩展

2. 源代码依旧在维护。贡献比較活跃

3. 产品成熟,语法丰富

4. 基于贪心算法。局部最优解


难点四:測试用例生成的设计

用例生成过程分为五个步骤:

Mock+Proxy在SDK项目的自己主动化測试实战

1. 准备字段值

依据Wiki的接口文档,測试人员理清字段结构。字段类型,字段取值范围后,结合传统的case设计理念。构造出每一个字段的赋值。存放到整理好的excel中。大概是这种:

Mock+Proxy在SDK项目的自己主动化測试实战

有的同学可能会问:你这样整理也挺麻烦。感觉人工也没省多少事儿。这样设计的优点是,当字段发生变化的时候。仅仅须要从源头改动字段属性、值、层级、甚至删除,后面整个流程中的case都会统一生效,字段集中管理。牵一发而动全身。和UI自己主动化用到的page-object设计相似。

2. 构建模型

有了面粉了。还须要加工一下才干变成我们想要的面包,我们须要把准备的数据整理成能够批量生成的可识别文件,即模型文件。PICT的模型文件有自己的格式。相似这样:

參数定义
[子模型定义]
[约束定义]

举个样例,前面提到的订票系统的样例加工成模型文件是这种。后面会给大家介绍语法含义:

Destination:  Canada, Mexico, USA
Class: Coach, Business Class, First Class
Seat Preference: Aisle, Window
{Destination, Class} @ 2

3. 生成Case

通过pairwise工具将模型文件组装成我们想要的case,那么上面的模型生成的case会是这样:

Destination Class Seat Preference
Canada First Class Window
USA First Class Aisle
Canada Coach Aisle
USA Business Class Window
Mexico Business Class Aisle
Canada Business Class Aisle
Mexico First Class Window
USA Coach Window
Mexico Coach Window

注:选择强度为2,因此上面的矩阵是两两变化的。

如前面所说,这里生成的矩阵内容不是固定的!

4. 准备期望结果

输入数据已经准备好了,那么相对于case而言,是不是还缺一个期望结果呢?在这里我们碰到了一个难题,可能做过case自己主动生成的同学都会遇到的,就是生成排列组合是非常easy的,怎样让这些组合变得有意义,体如今我们的期望结果上。那么一次性生成如此多的case,怎样让输入值和期望结果对号入座呢?

我们的做法是:拆分了postive testing 和 negative testing(合法输入測试和非法输入測试或负面測试)。通过整理接口case我们不难发现。合法输入的case事实上占整个case的比重并不大。工作量比較大的是各种參数的异常数据输入,相应的会产生error code或二次请求。仅仅须要我们在整理数据的时候给出相应的error code就可以,如图所看到的:

Mock+Proxy在SDK项目的自己主动化測试实战

有的同学会问:我们协议还不稳定,error code也不明白,有些输入也不知道相应什么error code,怎么破?别急。后面告诉大家。

5. 生成mock数据

完毕了以上准备工作以后,剩下的就是生成我们mock须要的response json数据了。

解析Wiki协议中的json模版,给相应的json字段赋上生成的值。这里须要写一段代码来完毕,在此不做赘述。

番外篇:工具的二次开发

在使用过程中。我们发现工具PICT不能满足业务场景的复杂度要求,主要有两点:

- 异常输入測试的时候,不能同一时候输入多个异常值

在case设计中多个异常值输入是非经常见的測试场景。尽管pict提供负面測试(negative testing)功能。即假设模型文件里,有值被标记为异常值(默认的异常值标识符为“~”),则case中会随机出现一个异常输入的值,可是PICT限制每一个case仅仅能有一个异常值存在,原因是多数异常值的组合尽管可能会引发问题,可是代码在catch了一个异常值造成的异常后,不会再去处理还有一个异常值。

先通过一个演示样例来感受一下pict的负面測试。演示样例模型文件例如以下:

Destination:      Canada, Mexico, USA, ~Japan
Class: Coach, Business Class, First Class
Seat Preference: Aisle, Window, ~Door

产生的case例如以下:

Mock+Proxy在SDK项目的自己主动化測试实战

通过上图能够看出,PICT同一时候保证了正常值的组合,也保证了异常值的组合,可是我们不难发现,每一个case仅仅会出现一个异常值,那么 ~Japan,First Class, ~Door的case就会遗漏,显然case覆盖率不够,不能满足我们的需求。

针对这个问题,在对PICT的源代码进行了具体的解读后,我们对代码进行了二次开发,扩展了负面測试的覆盖范围。彻底攻克了这个问题,改动后的模型文件例如以下:

Destination:      Canada, Mexico, USA, ~Japan
Class: Coach, Business Class, First Class
Seat Preference: Aisle, Window, ~Door
~{Destination, Seat Preference}
//添加一行公式,在模型文件里指定了Destination和Seat Preference两个字段能够进行异常值组合,数量不限。

扩展后的case生成是这种:

Mock+Proxy在SDK项目的自己主动化測试实战

  • 正則表達式过于简单。不支持复杂的语句

    PICT支持IF[ ] THEN[ ]格式的约束规则。可是约束规则中LIKEkeyword的通配符操作仅仅支持*和?(分别表示随意多个字符和随意一个字符)。显然简单的通配符操作限制了约束规则的表达能力。

    因此,我们在原有的基础上,引入C++的regex库支持正則表達式,改动后支持了更丰富的正則表達式。

    例如以下演示样例中,添加一条规则。假设Destination字段为数字类型(”\d”),那么Seat Preference字段也为数字类型。

Destination:      Canada, Mexico, USA, 3
Class: Coach, Business Class, First Class
Seat Preference: Aisle, Window, 4
If [Destination] like "\d" then [Seat Preference] like "\d";

生成的case例如以下图:

Mock+Proxy在SDK项目的自己主动化測试实战

有了强大的正則表達式,再加上多异常组合输入的支持。眼下已经全然能覆盖我们须要的不论什么场景,向开源致敬!

结构流程设计


在整个測试过程中,我们唯一须要人工介入的就是字段值的赋值以及跟error code的相应关系设计,协议字段的取值会受业务影响,临时无法通过自己主动化的方式来进行。流程如图所看到的:

Mock+Proxy在SDK项目的自己主动化測试实战

最初的版本号比較简单,结构大概是这种:

Mock+Proxy在SDK项目的自己主动化測试实战

原本的设想是想绕过广告宿主直接调用SDK的API请求广告,从而节省一部分时间,且更easy自己主动化,可是因为广告SDK本身特殊的设计。这个想法无法实现,因此当时的设计是通过触发APPbutton点击发送request给SDK。再由SDK发送加工后的请求到proxy server。再经过mock server处理数据以后,返回给client来显示广告。在此过程中,彻底抛弃了Fiddler重定向的传统做法。

Mock+Proxy在SDK项目的自己主动化測试实战

在阶段二我们攻克了几个问题:

- 实现内部循环请求广告,解决手工触发请求的问题

- 监控APP自身出现的crash和ANR

- 解决case失败后能够rerun,

- 解决中途运行中断能够rerun

- 因为一些广告请求失败会触发二次打点请求。因此我们须要把相应的case和打点请求结果匹配上,我们通过在request中添加caseID来解决该问题

- 解决多种广告类型不能连续一次性运行完,须要切换场景的问题

- 当出现期望结果与实际结果不符时,自己主动又一次运行该case 若干次(可配置),假设一直失败计为case失败

Mock+Proxy在SDK项目的自己主动化測试实战

该阶段非常明显,我们遇到了运行速度的问题。因为广告种类的添加。我们的case达到了3400余条,因为还须要兼顾广告渲染完毕后的打点结果检查,运行全量case耗时达到了3个小时多。偏离了我们mock測试的初衷。因此如上图所看到的。我们用到了分布式结构。mock server能够通过client指纹信息来调度和发送任务给指定的手机,把case和设备紧密连接在一起,避免反复运行同样的case。

另外我们把config、case、期望结果、运行结果等诸多信息全部迁移到database中,一方面解决频繁的文件读取问题,还有一方面攻克了分布式调度跨server的问题。

截止眼下,我们的測试数据是这种:

Case总数 发现BUG 遗漏的异常处理
3498条 77个 331个

前面提到的问题,假设error code尚未明白,case应该怎样匹配呢?我们的做法是设定一个基准error code,当运行结果出来后,会有实际结果与期望结果不符的case。拿去和开发对一下就能够,而调整error code期望结果以后。又一次生成case也仅仅只是分分钟的事。

我们的运行时间:

Mock+Proxy在SDK项目的自己主动化測试实战

收益:协议变更时,仅仅须要改动最開始的存放字段值的文件,兴许的建模、case生成、期望结果填充、运行測试用例全部自己主动完毕。測试人员查看运行结果就可以

Mock+Proxy在SDK项目的自己主动化測试实战

Mock+Proxy在SDK项目的自己主动化測试实战

问题总结


因为我们也是第一次在mock測试中实践自己主动化构造測试数据,包含用到的pairwise模型的合理性和准确性,都属于初次尝试,眼下在项目中取得了一定的效果,可是也遇到了非常多的困难,个中酸楚不足以一一道来,同一时候架构和流程还有非常多优化空间。

眼下依旧留存的问题包含:

- 自己主动生成case中,int、string、date等字段提取公共case。比方特殊字符、空、null、js等常规异常检查

- 更复杂的逻辑,比方关联字段依赖、加密字段、随机数、MD5、token等情况

- 非http(s)的自己定义协议

- 分布式调度的更大规模的使用

- SDK的自己主动化測试对于APP的强依赖关系

- 正常的功能測试验证

- 业务逻辑产生的漏測率统计

诸如此类的问题还有非常多非常多。尤其是结合项目自身特点,就会更加复杂,希望能通过我们的探索之路给同行们很多其它的启示。