从编写《接口自动化测试的最佳工程实践(ApiTestEngine)》至今,已经快半年了。在这一段时间内,ApiTestEngine
经过持续迭代,也已完全实现了当初预设的目标。
然而,在设计ApiTestEngine
之初只考虑了面向最常规的 API 接口类型,即HTTP
响应内容为JSON
数据结构的类型。那么,如果HTTP
接口响应内容不是JSON
,而是XML
或SOAP
,甚至为HTML
呢?
答案是,不支持!
不支持的原因是什么呢?
其实,不管是何种业务类型或者技术架构的系统接口,我们在对其进行测试时都可以拆分为三步:
而ApiTestEngine
不支持XML/HTML
类型的接口,问题恰恰是出现在解析接口响应
和校验测试结果
这两个环节。考虑到校验测试结果
环节是依赖于解析接口响应
,即需要先从接口响应结果中解析出具体的字段,才能实现与预期结果的校验检测,因此,制约ApiTestEngine
无法支持XML/HTML
类型接口的根本原因在于无法支持对XML/HTML
的解析。
也因为这个原因,ApiTestEngine
存在局限性,没法推广到公司内部的所有项目组。遇到JSON
类型以外的接口时,只能再使用别的测试工具,体验上很是不爽。
在经历了一段时间的不爽后,我开始重新思考ApiTestEngine
的设计,希望使其具有更大的适用范围。通过前面的分析我们也不难看出,解决问题的关键在于实现针对XML/HTML
的解析器。
在实现XML/HTML
的解析器之前,我们不妨先看下ApiTestEngine
的JSON
解析器是怎么工作的。
在JSON
类型的数据结构中,无论结构有多么复杂,数据字段都只可能为如下三种数据类型之一:
基于这一背景,ApiTestEngine
在实现JSON
的字段提取器(extractor
)时,就采用了点(.
)的运算符。
例如,假如HTTP
接口响应的headers
和body
为如下内容:
response headers:
{
"Content-Type": "application/json",
"Content-Length": 69
}
response body:
{
"success": false,
"person": {
"name": {
"first_name": "Leo",
"last_name": "Lee",
},
"age": 29,
"cities": ["Guangzhou", "Shenzhen"]
}
}
那么对应的字段提取方式就为:
"headers.content-type" => "application/json"
"headers.content-length" => 69
"body.success"/"content.success"/"text.success" => false
"content.person.name.first_name" => "Leo"
"content.person.age" => 29
"content.person.cities" => ["Guangzhou", "Shenzhen"]
"content.person.cities.0" => "Guangzhou"
"content.person.cities.1" => "Shenzhen"
可以看出,通过点(.
)运算符,我们可以从上往下逐级定位到具体的字段:
.key
来指定下一级的节点,例如.person
,指定了content
下的person
节点;.index
来指定下一级的节点,例如.0
,指定了cities
下的第一个元素。定位到具体字段后,我们也就可以方便地提取字段值供后续使用了,作为参数或者进行结果校验均可。
从点(.
)运算符的描述形式上来看,它和XML/HTML
的xpath
十分类似。既然如此,那我们针对XML/HTML
类型的接口,是否可以基于xpath
来实现解析器呢?
在大多数情况下的确可以。例如,针对如下 HTML 页面,当我们要获取标题信息时,我们就可以通过xpath
来指定提取字段:body/h1
<html>
<body>
<h1>订单页面</h1>
<div>
<p>订单号:SA89193</p>
</div>
</body>
</html>
然而,如果我们想获取订单号(SA89193)时,使用xpath
就没有办法了(通过body/div/p
获取到的是订单号:SA89193
,还需进一步地进行处理)。
那除了xpath
,我们还能使用什么其它方法从XML/HTML
中提取特定字段呢?
由于早些年对LoadRunner
比较熟悉,因此我首先想到了LoadRunner
的web_reg_save_param
函数;在该函数中,我们可以通过指定左右边界(LB & RB)来查找字段,将其提取出来并保存到变量中供后续使用。借鉴这种方式虽然可行,但在描述方式上还是比较复杂,特别是在YAML
测试用例的extract
中描述的时候。
再一想,这种方式的底层实现不就是正则表达式么。而且我们通过 Python 脚本解析网页时,采用正则表达式来对目标字段进行匹配和提取,的确也是通用性非常强的方式。
例如,假设我们现在想从http://debugtalk.com
首页中提取出座右铭,通过查看网页源代码,我们可以看到座右铭对应的位置。
<h2 class="blog-motto">探索一个软件工程师的无限可能</h2>
那么,要提取 “探索一个软件工程师的无限可能” 字符串时,我们就可以使用正则表达式r"blog-motto\">(.*)</h2>"
进行匹配,然后使用regex
的group
将匹配内容提取出来。
对应的 Python 脚本实现如下所示。
>>> import re, requests
>>> resp = requests.get("http://debugtalk.com")
>>> content = resp.text
>>> matched = re.search(r"blog-motto\">(.*)</h2>", content)
>>> matched.group(1)
'探索一个软件工程师的无限可能'
思路确定后,实现起来就很快了。
此处省略 256 字。。。
最终,我在ApiTestEngine
中新增实现了一个基于正则表达式的提取器。使用形式与 JSON 解析保持一致,只需要将之前的点(.
)运算符更改为正则表达式即可。
还是前面提取座右铭的例子,我们就可以通过YAML
格式来编写测试用例。
- test:
name: demo
request:
url: http://debugtalk.com/
method: GET
extract:
- motto: 'blog-motto\">(.*)</h2>'
validate:
- {"check": "status_code", "expected": 200}
需要说明的是,指定的正则表达式必须满足r".*\(.*\).*"
的格式要求,必须并且只能有一个分组(即一对括号)。如果在同一段内容中需要提取多个字段,那就分多次匹配即可。
实现了基于正则表达式的提取器后,我们就彻底实现了对任意格式HTTP
响应内容的解析,不仅限于XML/HTML
类型,对于任意基于HTTP
协议的的接口,ApiTestEngine
都可以适用了。当然,如果接口响应是JSON
类型,我们虽然可以也使用正则表达式提取,但更建议采用原有的点(.
)运算符形式,因为描述更清晰。
至此,ApiTestEngine
可以说是真正意义上实现了,面向任意类型的HTTP
协议接口,只需要编写维护一份YAML
用例,即可同时实现接口自动化测试、性能测试、持续集成、线上监控的全测试类型覆盖!
现在看来,ApiTestEngine
的名字与其实际功能有些不大匹配了,是该考虑改名了。
原始链接:http://debugtalk.com/post/apitestengine-not-only-about-json-api/