在测试用例中,包含预期结果这么一项,用于辅助测试人员执行测试用例时判断系统的功能是否正常。而在自动化测试中,咱们的目标是让测试用例自动执行,所以自动化测试用例中一样须要包含预期结果一项,只不过系统响应结果再也不由人工来进行判断,而是交由测试工具或框架来实现。html
这部分功能对应的就是测试结果校验器(validator),基本上能称得上自动化测试工具或框架的都包含该功能特性。python
HttpRunner
在设计之初,结果校验器(validator)的实现比较简单。git
对于每个test
,能够指定0个或多个校验项,放置在validate
中。在自动化测试执行的时候,会在发起HTTP请求、解析结果响应以后,逐个检查各个校验项,若存在任意校验项不经过的状况,则该test
将终止并被标记为失败。github
- test:
name: get token
request:
url: http://127.0.0.1:5000/api/get-token
method: GET
extract:
- token: content.token
validate:
- {"check": "status_code", "comparator": "eq", "expect": 200}
- {"check": "content.token", "comparator": "len_eq", "expect": 16}
复制代码
如上例所示,每个校验项均为一个json
结构,里面包含check
、expect
、comparator
三个属性字段。其中,check
对应着要检查的字段,expect
对应着检查字段预期的值,这两项是必须指定的;comparator
字段对应着比较方法,若不指定,则默认采用eq
,即检查字段与预期值相等。正则表达式
为了实现尽量强大的检查功能,check
属性值可经过链式操做精确指定具体的字段,comparator
也内置实现了大量的检查功能。算法
举个例子可能会更清晰些。假如某结构的响应结果以下:数据库
// status code: 200
// response headers
{
"Content-Type": "application/json"
}
// response body content
{
"success": False,
"person": {
"name": {
"first_name": "Leo",
"last_name": "Lee",
},
"age": 29,
"cities": ["Guangzhou", "Shenzhen"]
}
}
复制代码
那么假如咱们要检查status code
,check
就能够指定为status_code
;假如要检查response headers
中的Content-Type
,check
就能够指定为headers.content-type
;假如要检查response body
中的first_name
,check
就能够指定为content.person.name.first_name
。能够看出,假以下一层级为字典结构,那么就能够经过.
运算符指定下一层级的key
,依次类推。json
对于字段内容为列表list
的状况略有不一样,咱们须要经过序号来指定具体检查哪一项内容。例如,Guangzhou
对应的检查项为content.person.cities.0
,Shenzhen
对应的检查项为content.person.cities.1
。api
在比较方式(comparator
)方面,HttpRunner
除了eq
,还内置了大量的检查方法。例如,咱们能够经过gt
、ge
、lt
、le
等比较数值大小,经过len_eq
、len_gt
、len_lt
等比较长度是否相等(列表、字典、字符串均适用),经过contains
、contained_by
来判断包含关系,经过startswith
、endswith
判断字符串的开头结尾,甚至经过regex_match
来判断是否知足正则匹配等。详细的比较方式还有许多,须要时可查看comparator表格。app
在大多数状况下,HttpRunner
的结果校验器(validator)是够用的。不过问题在于,框架不可能为用户实现全部的检查方法,假如用户须要某些特殊的检查方法时,HttpRunner
就无法实现了。
这的确是一个问题,以前Junho2010
提的issue #29中举了一个例子,应该也算是比较有表明性。
发送请求时的数据使用了随机生成,而后须要比较结果中的数据是不是和这个相关(经过某个算法转换)。好比我输入的是321,个人结果是
(3+2+1) * avg(3+2+1)
这种转化,目前的comparator是比较难于实现的。
要解决这个问题,最好的方式应该是在HttpRunner
中实现自定义结果校验器的机制;用户在有须要的时候,能够本身编写校验函数,而后在validate
中引用校验函数。以前也介绍过HttpRunner
的热加载机制,《约定大于配置:ApiTestEngine实现热加载机制》,自定义结果校验器应该也是能够采用这种方式来实现的。
第二个须要优化的点,HttpRunner
的结果校验器还不支持变量引用,会形成某些场景下的局限性。例如,testwangchao
曾提过一个issue #52:
接口response内,会返回数据库内的自增ID。ID校验的时候,但愿
expected
为参数化的值。
validate:
- {"check": "content.data.table_list.0.id", "expected": "$id"}
复制代码
另外,在《ApiTestEngine,再也不局限于API的测试》一文中有介绍过,结果提取器(extract
)新增实现了经过正则表达式对任意文本响应内容的字段提取。考虑到结果校验器(validate
)也须要先从结果响应中提取出特定字段才能与预期值进行比较,在具体实现上彻底能够复用同一部分代码,所以在validate
的check
部分也能够进行统一化处理。
通过前面的局限性问题描述,咱们的改造目标也明确了,主要有三个方面:
具体的改造过程就不写了,有兴趣的同窗能够直接阅读源码,重点查看httprunner/context.py
中的parse_validator
、do_validation
和validate
三个函数。
通过优化后,改造目标中的三项功能都实现了。为了更好地展示改造后的结果校验器,此处将结合实例进行演示。
先来看第一个优化项,新增支持自定义结果校验器。
假设咱们须要使用HTTP响应状态码各个数字的和来进行校验,例如,201
状态码对应的数字和为3,503
状态码对应的数字和为8。该实例只是为了演示用,实际上并不会用到这样的校验方式。
首先,该种校验方式在HttpRunner
中并无内置,所以须要咱们本身来实现。实现方式与热加载机制相同,只须要将自定义的校验函数放置到当前YAML/JSON
文件同级或者父级目录的debugtalk.py
中。
对于自定义的校验函数,须要遵循三个规则:
debugtalk.py
中assert
将实际运算结果与预期结果值进行比较对于前面提到的演示案例,咱们就能够在debugtalk.py
中编写以下校验函数。
def sum_status_code(status_code, expect_sum):
""" sum status code digits e.g. 400 => 4, 201 => 3 """
sum_value = 0
for digit in str(status_code):
sum_value += int(digit)
assert sum_value == expect_sum
复制代码
而后,在YAML/JSON
格式测试用例的validate
中,咱们就能够将校验函数名称sum_status_code
做为comparator
进行使用了。
- test:
name: get token
request:
url: http://127.0.0.1:5000/api/get-token
method: GET
validate:
- {"check": "status_code", "comparator": "eq", "expect": 200}
- {"check": "status_code", "comparator": "sum_status_code", "expect": 2}
复制代码
因而可知,自定义的校验函数sum_status_code
与HttpRunner
内置的校验方法eq
在使用方式上彻底相同,应该没有理解上的难度。
对于第二个优化项,结果校验器中实现变量引用。在使用方式上咱们应该与request
中的变量引用一致,即经过$var
的方式来引用变量var
。
- test:
name: get token
request:
url: http://127.0.0.1:5000/api/get-token
method: GET
variables:
- expect_status_code: 200
- token_len: 16
extract:
- token: content.token
validate:
- {"check": "status_code", "comparator": "eq", "expect": "$expect_status_code"}
- {"check": "content.token", "comparator": "len_eq", "expect": "$token_len"}
- {"check": "$token", "comparator": "len_eq", "expect": "$token_len"}
复制代码
经过以上示例能够看出,在结果校验器validate
中,check
和expect
都可实现实现变量的引用;而引用的变量,能够来自四种类型:
test
中定义的variables
,例如expect_status_code
test
中提取(extract
)的结果变量,例如token
testset
中,先前test
中提取(extract
)的结果变量testset
中,全局配置config
中定义的变量而check
字段除了能够引用变量,以及保留了以前的链式操做定位字段(例如上例中的content.token
)外,还新增了采用正则表达式提取内容的方式,也就是第三个优化项。
假设以下接口的响应结果内容为LB123abcRB789
,那么要提取出abc
部分进行校验,就能够采用以下描述方式。
- test:
name: get token
request:
url: http://127.0.0.1:5000/api/get-token
method: GET
validate:
- {"check": "LB123(.*)RB789", "comparator": "eq", "expect": "abc"}
复制代码
可见在使用方式上与在结果提取器(extract
)中彻底相同。
最后,为了进一步简化结果校验的描述,我在validate
中新增实现了一种描述方式。
简化后的描述方式与原始方式对好比下:
validate:
- comparator_name: [check_item, expect_value]
- {"check": check_item, "comparator": comparator_name, "expect": expect_value}
复制代码
一样是前面的例子,采用新的描述方式后会更加简洁。而两种方式表达的含义是彻底等价的。
- test:
name: get token
request:
url: http://127.0.0.1:5000/api/get-token
method: GET
validate:
- eq: ["status_code", $expect_status_code]
- sum_status_code: ["status_code", 2]
- len_eq: ["$token", $token_len]
- len_eq: ["content.token", 16]
- eq: ["LB123(.*)RB789", "abc"]
复制代码
固然,这次优化保证了与历史版本的兼容,以前编写的测试用例脚本的运行是彻底不会受到任何影响的。