关于自动化:BUG越改越多微信团队用自动化测试化险为夷

44次阅读

共计 9566 个字符,预计需要花费 24 分钟才能阅读完成。

腾小云导读

作为后盾开发 Coder,你可能会对以下场景感到似曾相识:历史上解决过的 BUG 重复横跳;版本兼容逻辑多,修复一个 BUG 触发了更多 BUG;上线时系统监控毫无异样,过段时间用户投诉某个页面无数据;改变祖传代码时如履薄冰,心智累赘极重。为此本文提出一个自动化测试零碎,它可能低成本实现 100% 的测试用例覆盖率,极大加重治理自动化测试用例的工作量并进步测试效率,保障后盾服务安稳变更。欢送浏览~

目录

1 背景

1.1 接口自动化测试介绍

1.2 现状及痛点

1.3 为什么要自研

1.4 指标

2 自动化测试零碎实现

2.1 整体架构

2.2 对立 HTTP 和 RPC 拜访模式

2.3 接口参数传递(参数池结构)

2.4 JSON Schema 组件

2.5 JSON Path 组件

2.6 变更零碎接入与调度

3 用例自动化生成

3.1 现状以及剖析思路

3.2 整体流程

3.3 流量特征分析

3.4 用例生成

3.5 用例发现与补全

3.6 流量特色利用

4 总结

01、背景

1.1 接口自动化测试介绍

顾名思义,接口测试就是对系统或者组件之间的接口进行测试,次要校验数据的替换、传递以及零碎间的相互依赖关系等。依据测试金字塔的模型实践,测试分为三层,别离是单元测试(Unit Tests)、服务测试(Service Tests)、UI 测试(UI Tests),而咱们的接口自动化测试就是服务测试层。

单元测试会导致工作量大幅晋升,在需要疾速迭代和人力缓和的背景下,很难继续推动,本文暂不探讨。而接口自动化测试容易实现、保护成本低,且收益更高,有着更高的投入产出比。

1.2 现状及痛点

实际上咱们有一个叫 WeJestAPITest 的自动化测试平台,它是基于 Facebook 开源的 Jest 测试框架搭建的,用于校验后盾的接口返回是否合乎预期。在这个平台此前运行了数年的测试,肯定水平上保障了后盾服务的安稳运行。

但在长期应用中咱们也发现了一些痛点:

遇到失败用例习惯性申请跳过测试,自动化测试形同虚设;版本需要迭代速度飞快,用例落后于需要变更,用例迭代老本高;开发同学很难参加到用例保护中,而测试同学对接口逻辑理解不深,编写的用例过于简略、生硬,导致覆盖率低、用例品质差,开发上线心理负担重。

咱们须要的不只是一个自动化测试零碎,而是一个更好用的、治理老本更低的自动化测试零碎。

1.3 为什么要自研

提到接口自动化测试工具,开源有 JMeter、Postman 等,司内也有成熟的 WeTest、ITEST 等,这些都是开箱即用的,但通过调研和评估,咱们还是决定本人造一个轮子。思考的点如下:

测试工具的实现原理并不简单,实现老本不高,保护难度不大;现有工具并不合乎业务要求,例如自定义的调度计划,以及反对外部 RPC 框架;咱们须要把自动化测试与现有的零碎连接起来,比方上线零碎,用例失败告警零碎,流量剖析零碎等;当咱们须要一些非标准能力的时候,内部工具很难疾速,甚至无奈反对,拓展性弱;这个零碎次要是为了笼罩后盾接口测试,应用体验上要更贴近后盾同学的应用习惯,升高用例治理老本。

1.4 指标

联合咱们遇到的痛点以及业务需要,自研的自动化测试零碎应该具备以下的能力:

它应该是跟实现语言无关的,甚至是无代码的,打消不同编程语言和框架带来的隔膜;编写用例应该是纯正的,用例跟测试服务拆散,变更用例不须要变更自动化测试服务;可能反对场景测试(多个用例组成场景),且能反对用例间的变量援用;提供多种调度计划,能够按全量调度、按业务模块调度、按用例组调度、按单个用例调度,充沛满足业务和调试需要;这个零碎要反对同时治理 HTTP 和 RPC 用例,能够笼罩申请的上下游链路;尽最大可能升高后盾同学编写用例的老本。

02、自动化测试零碎实现

2.1 整体架构

2.2 对立 HTTP 和 RPC 拜访模式

HTTP 和 RPC 申请在模式上能够被对立起来,其形容模式如下:

HTTP 拜访形式:http://host:port/urlpath + reqbody

RPC 拜访形式:rpc://ip:port/method + reqbody

通过这种对立的形容模式,再联合咱们的业务架构,就能够设计一种通用的拜访形式。后盾的零碎架构如下图所示:

从 proxy 层往下,所有的调用都是一个个后盾服务模块,HTTP 拜访的是逻辑层,RPC 拜访的是服务层。那么只须要配置用例的归属模块,通过模块名 + Client 配置就能够对 HTTP 和 RPC 申请进行辨别以及寻址。

从变更零碎的角度来看,咱们的上线变更也是按模块来的。因而把用例归属到一个个具体的模块,是最合乎后盾同学认知的做法。

因而咱们通过配置模块名这种对立的模式,为使用者提供了对立的治理形式,只须要指定模块名就能够任意拜访 HTTP 或者 RPC 申请,其流程如下:

在红色虚线框的流程中,只须要配置模块名,就能够通过模块名获取到 RPC 服务的所有信息,包含其接口定义、申请包定义、回包定义,这不是一种通用能力,须要业务基于零碎架构以及线上环境去拓展,但这带来了以下便当:

能够反对任意的业务 RPC 框架,拓展性强;只须要配置模块名就能够拜访所有的 RPC 申请,无需一一手动上传解析 proto 文件,缩小操作步骤;不须要关怀 proto 的更新,实时拉取线上 proto 的信息,协定永远是最新版本。

这里的对立蕴含两局部:第一局部是 拜访模式的对立 (模块),升高了配置用例的老本;第二局部是 数据的对立(JSON),它对立了对回包形式的校验,升高了校验老本。

2.3 接口参数传递(参数池结构)

很多业务场景的实现都是由多个接口组成的一条链路实现,而且这种链路型的自动化测试,通常会存在参数依赖关系,一个用例的入参,可能要依赖上游响应回包的某个字段值,因而须要提取进去并传递给下一个接口。如下图:

其解决方案是,通过正则或者 JSON Extracor 等提取的后果作为变量,而后再传递给上游用例应用,这也是很多测试工具应用的形式,然而保护起来不够不便,仍有进一步优化的空间。

于是咱们提出了参数池的概念,将每个用例可能用到的字段都放入一个池子里,这个池子的元素是一个个 key-value。key 是咱们要应用的变量,value 则是 key 对应的取值,值得注意的是,value 既能够是一个字面值,也能够是一个 JSONPointer 的门路,这个门路能够从响应回包中提取变量值。

在这种形式下,不同用例间的参数依赖不再是从上一个“传递”到下一个,而变成了一个随取随用的池子,因而咱们把它称为参数池。同时咱们通过自定义的语法,实现了一个简略的模板引擎,将咱们援用的变量替换为池子里的 value 值。参数池结构以及应用图示如下:

2.4 JSON Schema 组件

上面贴一段代码看看现有 WeJestAPITest 框架是如何对返回值做校验的,并剖析一下它可能存在的问题:

function bookInfoBaseCases(bookInfoObject) {it('预期 bookInfo.bookId 非空,且为字符串,且等于 12345', () => {expect(bookInfo.bookId).not.toBeNull();
        expect(typeof bookInfo.bookId).toEqual('string');
        expect(bookInfo.bookId).toEqual('12345');
    });
}

这种校验形式存在以下几个问题:

这是针对单个字段进行校验,如果一个回包里有几十上百个字段,这种手工形式不可能实现全量字段校验;编写一个用例须要有 js 根底,对其余编程语言的使用者不敌对;断言规定都是一条条散落在代码文件中,展现和治理有难度;调试须要变更测试服务,调试老本高。

现有框架的不便导致了用例治理上的种种问题,而咱们依据这些不便之处去反向思考,咱们到底须要什么样的校验形式,这种状况下咱们找到了 JSON Schema。

JSON Schema 是形容 JSON 数据格式的工具,Schema 能够了解为模式或者规定,它能够束缚 JSON 数据应该合乎哪些模式、有哪些字段、其值是如何体现的。JSON Schema 自身用 JSON 编写,且须要遵循 JSON 自身的语法标准。

上面以 bookInfo 的校验为例,写一份 JSON Schema 的校验规定:

// bookInfo 信息
{
    "bookId":"123456",
    "title":"书名 123",
    "author":"作者 123",
    "cover":"https://abc.com/cover/123456.jpg",
    "format":"epub",
    "price":100
}

// 对应的 JsonSchema 校验规定
{
    "type": "object",
    "required": ["bookId", "title", "author", "cover", "format", "price"],
    "properties": {
    "bookId": {
        "type": "string",
        "const": "123456"
    },
    "title": {
        "type": "string",
        "minLength": 1
    },
    "author": {
        "type": "string",
        "minLength": 1
    },
    "cover": {
        "type": "string",
        "format": "uri"
    },
    "format": {
        "type": "string",
        "enum": ["epub", "txt", "pdf", "mobi"]
    },
    "price": {
        "type": "number",
        "exclusiveMinimum": 0
    }
  }
}

通过比照,JSON Schema 的长处十分不言而喻:

可读性高,其构造跟 JSON 数据齐全对应;所有规定都处在一个 Schema 中,治理和展现清晰易懂;它自身是一个 JSON,对于任何编程语言的使用者都没有额定学习老本;此外,咱们能够通过一个现有的 JSON 反向生成 JSON Schema,而后在这个 JSON Schema 的根底上进行简略的批改,就能失去最终的校验规定,极大升高了咱们编辑用例的工作量和工夫老本。

2.5 JSON Path 组件

有了 JSON Schema 之后,咱们校验形式看似曾经十分完满了。它既能够低成本的笼罩全量字段校验,还能够很不便的进行字段类型、数值的校验。

但理论应用中咱们发现有些测试场景是 JSON Schema 笼罩不到的,比方:一条用户评论有 createtime 和 updatetime 两个字段,须要校验 updatetime >= createtime。这是 JSON Schema 的短板,它能够束缚 JSON 的字段,然而它没方法对两个字段进行比照;同时 JSON Schema 跟 JSON 是一对一的,如果咱们须要比拟两个不同 JSON 的同一个字段,它同样无能为力。这就引出了咱们须要的第二个工具 —— JSONPath。

JSONPath 是一个 JSON 的信息抽取工具,能够从 JSON 数据中抽取指定特定的值、对象或者数组,以及进行过滤、排序和聚合等操作。而 JSONPath 只是一个 JSON 字段的提取工具,要利用它来实现一个断言判断还须要进一步封装。

在这里咱们用一个 JSONPath 表达式来示意一个断言,上面是一些简略的应用示例:

// 校验 updateTime > createTime
$.updateTime > $.createTime

// 返回的 bookId 必须为某个固定值
$.bookId == ["123456"]

// datas 数组不能为空
$.datas.length > [0]

// datas 数组中必须蕴含某本书,且价格要大于 0
$.datas[?(@.bookId=='123456')] > [0]

值得注意的是,JSON Schema 和 JSON Path 断言校验并非二选一,既能够同时校验,也能够依据场景抉择任意一种校验形式。与此同时,如果我的项目前后端交互的协定是 XML、proto 或者其余协定,能够将其对立转为 JSON 格局,JSON 更容易了解且工具链更多更成熟,否则咱们将要为每一种序列化的协定都开发一套相似的工具,重复劳动。

2.6 变更零碎接入与调度

在这里,咱们应用异步 MQ 去调度测试工作,它有三个次要的特点:

多触发源 任意粒度 指定环境
反对变更零碎、治理平台、例行任务调度等多个起源的工作触发信号。 反对按全量用例调度、按变更模块调度、按用例组调度、按单用例调度。 反对调度到现网环境和测试环境,甚至能够指定 IP 对某台机器定向测试。

03、自动化测试零碎实现

在领有了一个接口自动化测试平台之后,咱们面临一个新的问题:如何疾速晋升自动化测试的覆盖率?

这个问题有一个隐含的前提,咱们须要一个能够掂量覆盖率的指标,接下来将介绍咱们如何结构这个指标,并分享一些晋升覆盖率的计划。

3.1 变更零碎接入与调度

要掂量覆盖率,第一反馈必然是基于前后端约定的协定进行剖析。然而沿着这条思路去剖析咱们遇到了以下几个难点:

协定治理不标准,散落在 git 文档、yapi、wiki 等多处中央,且格局不对立;文档落后于理论接口协议,且可靠性有待讲究;协定参数并非都是正交的,应用协定计算出来的参数组合不符合实际状况;因而,应用前后端协定进行剖析这条路是行不通的。因而咱们打算从线上流量动手,对流量的参数特色进行剖析,并应用线上流量来生成自动化测试用例。

3.2 整体流程

3.3 流量特征分析

一个 HTTP 申请,咱们通常须要剖析的是以下局部:申请办法、URL、申请包、返回包。而联合咱们的业务场景,咱们还须要一些额定的信息:用户 ID、平台(安卓、IOS、网页等)、客户端版本号等。咱们调研过一些流量采集剖析并生成用例的零碎,大多只能对通用信息进行剖析,并不能很好的联合业务场景进行剖析,拓展性有余。

咱们有一个申请,其 url 参数为 listType=1&listMode=2、vid 为 10000、平台为 android、版本号为 7.2.0,其申请体如下:

{
  "bookId":"12345",
  "filterType":1,
  "filterTags":["abc","def"],
  "commOptions":{
    "ops1":"testops1",
    "ops2":"testops2"
  }
}

其中 url 和 header 里的参数都很容易解析,不再赘言,上面讲一下 JSON 申请中的参数提取办法。这里咱们用 JSONPointer 来示意一个参数的门路,作为这个参数的 key 值,那么能够提取取得以下参数:

// url 和 header 中提取的参数
listType=1
listMode=2
vid=10000
platform=android
appver=7.2.0

// JSON 中提取的参数
/bookId=12345
/filterType=1
/filterTags=["abc", "def"]
/commOptions/opts1=testops1
/commOptions/opts2=testops2

如此一来,参数的表现形式能够对立为 key-value 的模式,咱们调研的工具也根本止步于此,接下来要么是用正交计算用例的形式辅助人工编辑用例,要么就是对大量流量生成的用例进行去重。

但这达不到咱们预设的指标,咱们无妨更进一步,通过大量的线上流量结构出接口参数的特色,在这里咱们提出一个定义,接口参数的特色包含五局部:

参数个数;参数类型;参数取值范畴;参数可枚举性;参数可组合性。

咱们的工作次要集中在参数的可枚举性剖析,这也是参数剖析的突破点。假如咱们从线上对某个接口进行采样,采样条数为 1W 条,将失去以下的参数:

listType=[1, 2, 3, 4]
listMode=[1, 2]
vid=[10000, 10001, 10002, 10003, ...] // 3000+
platform=[android, ios, web]
appver=[7.2.0, 7.1.0, 7.3.0, ...] // 20
/bookId=[12345, 23456, 34567, 56779, ...] // 4000+
/filterType=[1, 2]
/filterTags=[abc, def, efg]
/commOptions/opts1=[testops1, testops1_]
/commOptions/opts2=[testops2]

有了以上提取到的参数枚举值,咱们设定一个正当的阈值(比方 30),就能够判断哪些参数是可枚举的,很显著 vid 和 /bookId 并不是可枚举的参数,在笼罩用例时不须要对这两个参数进行笼罩。

在实践中,咱们发现固定阈值并不能精准辨认到无效的枚举参数,阈值须要追随采样的数据动静调整。不同接口申请量可能从几十到几十万不等,如果一个接口申请条数只有 30 条,每一个参数的枚举值都小于设定的阈值,所有参数都是无效参数,这不符合实际状况。因而阈值要随着采样条数的变动而变动,能够按申请数量阶梯变动,也能够按申请数量成比例变动。对于特定参数,还要提供人工配置疾速染指,指定参数是否可枚举。

在咱们晓得哪些参数是可枚举的无效参数后,接下来能够对参数的可组合性进行剖析。实际上咱们并不需要剖析任意两个参数两两是否可组合,基于线上流量去剖析即可。咱们简略给一个例子:

listType=1&listMode=1&platform=android&appver=7.2.0
listType=1&listMode=1&platform=ios&appver=7.2.0
listType=1&listMode=1&platform=web&appver=7.2.0
listType=2&listMode=1&platform=android&appver=7.2.0
listType=2&listMode=1&platform=ios&appver=7.2.0
listType=2&listMode=1&platform=web&appver=7.2.0
listType=3&listMode=2&platform=android&appver=7.2.0
listType=3&listMode=2&platform=ios&appver=7.2.0
listType=3&listMode=2&platform=web&appver=7.2.0

那么在笼罩用例时咱们须要笼罩这 9 个组合,通过组合分析咱们甚至能够发现线上是否有谬误应用的参数组合,需要是否产生了变更产生了新的组合参数。

要晋升覆盖率,实质上就是笼罩所有可枚举参数的枚举类型以及组合,这就是咱们在下面提到过的覆盖率指标。有了这个指标,咱们就能够对覆盖率提出以下计算公式:

全局覆盖率 = 已笼罩的接口数 / 全副接口数 * 100%

接口无效用例 = 全副可枚举参数的可枚举值 + 全副可枚举参数的组合

接口覆盖率 = 已笼罩的无效用例数 / 接口无效用例数 * 100%

PS:当接口覆盖率达 100% 时视为接口已实现用例笼罩

3.4 用例生成

通过上面对流量的特征分析以及筛选,咱们失去了一批无效流量,接下来就能够应用这些流量来自动化生成用例,其中最次要的工作是为用例生成校验的 JSON Schema 规定。其生成过程如下图所示:

如上图所示,任何 JSON Schema 的生成工具所生成的 Schema 都不可能百分百满足业务需要,咱们依然要依据业务场景对 Schema 进行微调。比方在搜寻场景下,咱们用一个 results 数组来承载返回后果,生成器生成的 Schema 只约定了 results 字段必须要存在,并且字段类型为数组类型。如果有一天返回了一个空的 results 数组,那么默认生成的 Schema 是查看不出这个问题的,咱们能够为 results 数组减少 minItems = 1 的规定,要求 results 数组必须大于等于 1,下次校验时遇到空数组就可能告警进去。

同时,在用例执行时遇到校验不通过的状况,咱们也设计了一套自动化 promote 用例的流程,不须要手工对用例进行改变。其流程如下:

其中用例优化分为三种状况:

移除用例:用例已生效,间接删除用例;替换用例:用例不合乎预期,从线上依据同样的参数选取申请从新生成一个用例;优化 Schema:用例中某些字段并非必须字段,或者属于预期内的变动(比方用户的未购变已购导致某些字段被替换)。

咱们应用的 Schema 生成工具是 genson,它能够为一个 JSON 生成对应的 JSON Schema。这个工具有个很重要的个性:它是一个多输出的 JSON Schema 生成工具,能够接管多个 JSON 或者 Schema 作为输出参数,生成一个合乎所有输出要求的 Schema,这一点正是咱们自动化的要害,使得咱们不须要手动编辑校验规定。上面简略展现一下咱们当初的零碎是如何优化失败用例的:

3.5 用例发现与补全

用例的自动化发现分为两个离线工作:一个是新接口的发现,一个是新用例的发现。

新接口是指咱们有新的性能上线,当线上有流量拜访时,咱们应该及时发现这个新的申请,并将这个申请纳入咱们的自动化测试治理范畴。

新用例是指通过对流量剖析,发现了新加的可枚举参数,或者之前用例未曾笼罩的参数组合,咱们通过比照线上流量和曾经采集落库的用例进行 diff 剖析,失去并生成新的用例。

下图是对用例的自动化发现与补全的简略示例:

3.6 流量特色利用

基于下面提到的流量特征分析以及用例生成,咱们的用例个数从 150+ 晋升到 8000+,实现了读接口 100% 用例笼罩,覆盖率有了一个质的飞跃。

对于写接口实现了覆盖率统计以及用例举荐,极大升高了在编辑用例时的心智累赘,不须要本人去结构参数以及遍历所有的参数组合,跟随着举荐的用例去补全即可。

同时针对咱们后面提到的前后端协定扩散在各个中央,且接口与文档不统一的问题,咱们通过线上流量对申请参数和申请回包的 Schema 进行继续的迭代,而后再将 Schema 反向生成 JSON,就能够失去一份最全、最新的接口协议,同时这份协定还能够提供给客户端同学用来结构参数进行 mock 联调。

04、总结

至此,咱们曾经实现了整个后盾接口自动化测试零碎的搭建,并实现了预设的全副指标:

集成 JSON Schema 和 JSONPath 这两个组件,实现了一个无代码以及用例跟测试服务拆散的自动化测试零碎;通过用例的组合以及参数池结构实现了场景测试和用例间变量援用;反对了多种定制化的调度计划,并接入到上线零碎流程中;买通 HTTP 和 RPC 接口拜访,联合业务架构极大升高了接入 RPC 用例的老本;通过用例自动化生成进一步升高用例治理老本,疾速进步了自动化测试的覆盖率。

对于旧用例零碎上的数据,咱们破费了将近两周,将数千行测试代码、将近一千条校验规定全副迁徙到新的自动化测试平台上,失去了 150+ 的新用例,并且校验的规定变成了 150+ 的 JSON Schema,不须要保护任何一行代码,就失去了比之前更欠缺的全字段校验规定笼罩。

此外,咱们通过用例发现和用例生成,生成了 8000+ 的用例,实现了读接口 100% 用例笼罩,并屡次辅助发现线上异样数据问题,在用户还未感知前就曾经将问题扼杀在摇篮之中。

笔者认为,本文最重要的并不是对各种工具的集成和应用,100% 的用例笼罩也并非本文的最终目标。各种开源和付费工具不可胜数,只有舍得投入人力 100% 的用例笼罩也并非难事。本文真正重要的是提出了一种 通用的测试框架架构 ,以及基于线上流量剖析失去了一种 测试覆盖率的度量计划

秉持着这种思路,上文中咱们提到的 调度零碎、用例执行 MQ、校验工具、测试告警零碎、流量采集零碎、用例生成零碎,都能够基于业务灵便调整,低成本实现大规模用例笼罩。以上是本次分享的全部内容,如果感觉内容有用,欢送分享转发。

-End-

原创作者|柯宗言

技术责编|许阳寅、罗国佳

各位开发者都遇到过什么样头疼的 BUG,欢送在腾讯云开发者公众号评论区分享探讨。咱们将选取 1 则最有意义的分享,送出腾讯云开发者 - 手段垫 1 个(见下图)。6 月 26 日中午 12 点开奖。

正文完
 0