Httprunner:首先介绍下httprunner是干嘛的???,官方文档是这样描述的HttpRunner 是一款面向 HTTP(S) 协议的通用测试框架,只需编写维护一份 YAML/JSON 脚本,即可实现自动化测试、性能测试、线上监控、持续集成等多种测试需求。。。。看到了吧这个框架的牛逼之处 官方文档链接:https://cn.httprunner.org/
它的核心特性是这样的:
继承 Requests 的全部特性,轻松实现 HTTP(S) 的各种测试需求
采用 YAML/JSON 的形式描述测试场景,保障测试用例描述的统一性和可维护性
借助辅助函数(debugtalk.py),在测试脚本中轻松实现复杂的动态计算逻辑
支持完善的测试用例分层机制,充分实现测试用例的复用
测试前后支持完善的 hook 机制
响应结果支持丰富的校验机制
基于 HAR 实现接口录制和用例生成功能(har2case)
结合 Locust 框架,无需额外的工作即可实现分布式性能测试
执行方式采用 CLI 调用,可与 Jenkins 等持续集成工具完美结合
测试结果统计报告简洁清晰,附带详尽统计信息和日志记录
极强的可扩展性,轻松实现二次开发和 Web 平台化
特点:
扩展性高,测试用例是一份json/yaml格式的文件,不用自己录接口参数,用har2case命令将抓到har导出成一份json/yaml格式的测试用例文件。也就是说fiddler抓到接口导出成har包,用命令执行一下,测试用例就录制好了,如果测试用例没有依赖的情况出现,就能用命令运行。能写python代码,能调python各种,有能力的将httprunner自带的命令进行封装。完美吧!!!
注意:以下全部只针对我的项目 项目代码下载链接:https://files.cnblogs.com/files/wenjxu/AutoMateProject.rar
1:官方文档只有录制testcase的功能,我这边参照har2case的源码重新写了一遍,附加一个录制api的功能, 命令如下:python spell.py -a demo.har,执行后会在api文件路径生成一个demo.yaml文件
2:官方生成testcase的命令是har2case demo.har,我这边重新了写了一个命令python spell.py -t demo.har
开始上手:
准备:python3.7(自带pip),ide(pycharm) 这两个最常规的需要
安装环境:
步骤1
步骤2
步骤3
步骤4
步骤5
最后点击OK,OK,OK就可以了
也就是说安装一个python3.7和一个pycharm,并且在setting-》Project Interpreter 添加你下载的python3 文件下python.exe可执行程序就可以了
安装第三方依赖包:
1:打开Terminal输入命令pip install pipenv安装pipenv包管理工具,这个工具也是一个虚拟环境(非常好用)
2:输入命令pipenv install,会根据pipfile文件中的字段[packages]来下载对应的依赖包
3:下载好了后去找虚拟环境,把它加载到setting配置中,下载的虚拟环境其实就是一堆文件,一般会在c盘个人用户路径下。
备注:这三个依赖包就下载好了,之所以搞虚拟环境pipenv,是因为假如你写好代码下载了很多第三方依赖包也用到了,假如别人要用你的代码的时候并不知道要下载哪些第三方依赖包,还得自己一个一个文件去找麻烦的很,先利用pip install pipenv命令下载pipenv这个工具,然后pipenv install 一个命令就能下载所有需要的依赖包,这个命令会根据Pipfile文件中的[packages]来下载对应的包,以后写个代码需要用到第三方包的话,也可以pipenv install packagename 一下,非常方便,移植性也很高
开始工作
1:打开Fiddler抓取一个接口(最好没有接口依赖关系)
2:选中该接口,点击file-》Export Sessions -》Selected Sessions-》Next
3:导出har,最好存放的位置是在自己项目里,方便查找。
4:打开Terminal,输入命令python spell.py -a demo.har,这个执行完成后会在api目录生成一个以url 路径为名的yaml文件,不要删除demo.har后面生成testcase测试用例会用到。
5:打开文件这个yaml文件预览
5.1:这个文件总共有name,base_url,variables,request,validate字段
name:api接口描述
base_url:接口的域名
variables:变量参数,用于其它地方可以引用,引用方式$变量名
request:请求url,参数,method等等
validate:校验的请求返回的值
6:生成testcase测试用例,输入命令python spell.py -t demo.har,这个命令执行完成后,会在testcases 目录下生成一个以har文件名的json文件
7:打开该json文件预览,这个json里的数据是一个list of dict格式
7.1 第一个dict是config配置,name是testcase描述,variables(list of dict)里的变量是用于其它地方可以引用,作用域是整个文件,引用方式$变量名,output(list)是用于打印变量
7.2 第二个dict是一个teststep,如果有第三个dict的话,那也是一个teststep。
7.3 第二个dict中test是必须的,每一个teststep都会有,name是这个测试步骤的描述,api对应是一个目录路径(引用api目录中yaml文件),variables(list of dict)同样里面的变量用于其它地方引用,但是作用域只是这个teststep。extract(list of dict)用于处理依赖,validate用于校验请求返回的值,setup_hooks和teardown_hooks用于请求前的准备工作和请求后的结尾工作,先简单的描述下
8:输入命令hrun testcases\demo.json,执行完成后会在reports目录生成一个html文件
注意:/是linux中的斜杆,\是windows的斜杆
点击log-1
这个报告描述的很详细,非常的完成,可以慢慢体会下。
怎么理解这些生成文件的相互依赖关系和参数的调用和引用。也就是说我们日常如何去维护这些文件
一、理解api目录下yaml文件:
首先声明:一个yaml文件对应一个api接口,也就是说api目录下yaml文件内容只能是一个接口
1:让我们看下api目录下yaml文件,在用命令python spell.py -a demo.py(我自己参照源码写的一个录制api文件的功能)生成文件的时候,我将har文件中有关接口的参数数据都添加到variables(list of dict)中,其实它是这样的格式 [{“name”:”wenjxu”},{“from”:”3”}],而request: data | request: params | request: json都是在$引用variables的变量,为啥这样干呢?是因为testcases目录中json文件引用api目录下yaml文件仅仅覆盖其variables值,就能在json文件中引用多次该接口,让整个json文件看起来简洁不会存在很多重复的参数。(后续还会讲到)
2:request:data | request:params | request:json 这些请求的参数为啥分三种类型,其实跟接口请求有关系
params这种参数一般是url路径提交参数一种方式,一般是跟在url?后面,例如url?from=3这种形式
data这种参数需要分区请求头content-type三种类型了(application/x-www-form-urlencoded,multipart/form-data,application/json),如果是content-type是form-data和urlencoded,那么请求的参数存放在data中,如果是content-type是application/json,那么请求的参数存放在json中。(其实不需要太在意这些放在哪里。因为官方文档生成testcase也是这样做的,其实我们只需要关注有哪些参数就行)
3:然后看下validate(list of dict)其实它的格式是这样的,例如:[{“eq”:[“status_code”,200] }],这种说明校验这个接口运行后的状态码是否是200,我们可以这样[{“eq”:[“content.code”,”0”] }] ,这种说明我们需要校验接口返回结果的code是否是”0”,content关键字表示的是接口返回的值可以想象成一个变量,这种写法是固定的。不光可以eq比较值还有其它校验的方法
1、实际结果和期望结果是否相等:equals 或 eq 或 == 或 is
2、实际结果小于期望结果:less_than 或 lt
3、实际结果小于等于期望结果:less_than_or_equals 或 le
4、实际结果大于期望结果:greater_than 或 gt
5、实际结果大于等于期望结果:greater_than_or_equals 或 ge
6、实际结果和期望结果不相等:not_equals 或 ne
7、实际结果和期望结果是否相等:string_equals 或 str_eq
8、实际结果的长度和期望结果是否相等:length_equals 或 len_eq 或 count_eq
9、实际结果的长度大于和期望结果:length_greater_than 或 len_gt 或 count_gt 或 count_greater_than
10、实际结果的长度大于等于期望结果:length_greater_than_or_equals 或 len_ge 或 count_ge 或 count_greater_than_or_equals
11、实际结果的长度小于期望结果:length_less_than 或 len_lt 或 count_lt 或 count_less_than
12、实际结果的长度小于等于期望结果:length_less_than_or_equals 或 len_le 或 count_le 或 count_less_than_or_equals
13、实际结果包含期望结果:contains
14、实际结果被期望结果包含:contained_by
15、实际结果的字段类型和期望结果相同:type_match
16、检查实际结果和期望结果是否都是basestring的实例,如果都是就用正则匹配两个输入参数:regex_match
17、验证check_value是否已expect_value开始:startswith
18、验证check_value是否已expect_value结尾:endswith
二、理解testcases目录下json文件
1: 让我们来看下testcases下的json文件,config配置的作用域是全局的,也就是说里边的variables里的变量能让多个teststep引用,重说一遍第二个dict或者第三个dict都是一个teststep,里面的关键字几乎都是一样的。name是teststep的描述,api关键字(遵循官方文档测试用例分层机制)是引用api目录下对应的yaml文件,teststep中variables的作用域只是在当前teststep中有效。有一个特别注意的地方是如果teststep中variables有一个变量与config中variables重复,会优先使用teststep中变量,如果说config配置variables中变量与api目录下yaml文件中variables一样的话,会优先引用config配置中,总结一句话“就近原则”,validate也是一样的道理
2: 继续看json文件居然没有请求的信息????,请求的信息从哪里来,让我们打开api目录下yaml文件,请求的信息其实就是从api目录下yaml文件拿到request的相关信息。如果json文件中定义了request相关信息,还是那就话“就近原则”,不过我们不需要在json文件中定义request相关信息,如果每个teststep都定义了request信息的话就会存在大量参数看起来像是重复了,我们只需要添加需要变的参数到teststep中variables进行覆盖。是不是感觉有点像继承,子类(teststep)没有的话去找父类(config),父类没有找父父类(api目录下yaml文件),以此类推,子类有的话就用子类的。
三、处理复杂场景
1:参数依赖
1.1:想象一种场景,假如我们有一个接口是需要登陆接口请求后返回的token值,也就是说很多时候,接口之间有很多是相互关联,必须执行A接口拿到请求返回的值,将A接口请求返回的值拿到然后传入B接口中(当成B接口的请求参数)。
1.2:我们正确的做法是使用extract(list of dict)关键字,它的格式是这样的例如:[ {“token”:” content.token” } ],token是一个被其它接口所依赖的变量自己命名能看的懂就行,content.token 表示接口返回值.key,这是一个固定格式以点key式取值,如果接口的返回值是{“info”:[“name”,” age”],”msg”}这样的话,我们想引用[ ]里name字符串的话,格式是content.info[0],也就是说如果是list就用[index]引用,如果是dict就是点key进行引用。当我们extract 提取到接口返回的值的时候, 我们就可以$token引用传入下一个teststep中(建议放到下一个teststep的variables中,可以进行覆盖),说明一下extract提取的token是保存到内存当中并且这个值能被多次引用,并且不用担心引用多次造成所依赖的接口运行多次的问题(因为自始至终只会运行一次所依赖的接口)
2:调用函数
个人觉得这个框架扩展性最高的地方就是调用函数了,调用sql,执行sql都需要用到调用函数,像 setup_hook和teardown_hook两个方法都是需要调用函数(后续会讲到),例如我们需要一个时间,上传一个文件,拿一个随机字符串都是可以通过调用函数解决,调用方式:"${gen_random_string(15)}" "${函数名(参数值,参数值)}",注意调用的函数都是写在debugtalk文件中,参数值例如一个字符串千万中间别有空格要不然会报错,如果字符串中间有空格的话例如sql语句,我们可以将sql语句写到variables中用一个变量保存然后调用函数的时候就可以这样写"${gen_random_string($v,$r)}"
3:参数化
1.1参数化驱动其实就是提前准备一堆参数,然后循环调用同一个测试用例执行,另外重申一遍我们最好将 接口请求的参数抽取到variables中,方便我们针对某一个变量进行覆盖,开展很多工作也很方便。一般我们在testsuites目录下建json或者yaml文件开展参数化工作。我一般喜欢用json文件,只有testapi目 录才会使用到yaml文件。
1.2 模板如下
{
"config": {
"name": "create users with parameters"
},
"testcases": {
"create user $user_id": {
"testcase": "demo-quickstart.json",
"parameters": {
"user_agent": ["iOS/10.1", "iOS/10.2", "iOS/10.3"]
}
}
}
}
parameters其它调用方式:
"user_id": "${P(data/account.csv)}",
"username-password": "${get_account(10)}"
关键字parameters里添加需要驱动的参数名,对应一个列表多个参数。demo-quickstart.json文件这个 总共有三种书写参数值方式(1:对应一个列表 2:引用csv文件 3:调用函数),这里只讲第一种方式, 重复申明一遍我们最好把testcase里接口请求的参数都写在自己的variables中(不是config中), 方便 覆盖开展工作。注意如果demo-quickstart.json文件中有两个接口其中一个接口会用到user_agent参 数,运行这个参数化文件会总共运行3次,总共运行6个api接口(包括重复的),3次其中一个接口会 使用上不同的参数。
关于参数化数据驱动,这里只描述了最简单的场景和使用方式,如需了解更多,请进一步阅读官方文档 https://cn.httprunner.org/prepare/parameters/
4:setup_hook测试前的准备工作,teardown_hook测试后的收尾工作
1: 这两个方法太有用了,有些场景我们录制好了测试用例运行一次就结束了,再想运行的话就会报错。例如我们的项目签署协议一块,我们签署一次就结束了,一般开发逻辑可能是签署成功后把某个字段置为例如1,我们想再次运行这个测试用例就必须提前把这个字段update置为0,其实就是在运行测试用例之前我们调用执行下sql,setup_hook或者 teardown_hook就是用来专门干这种事的,其意思是每个 teststep之前触发setup_hook函数(用于准备工作),每个teststep之后触发teardown_hook函数(用于测试后的清理工作),目前我能想象到就这个场景。
2: 再看testcase中json文件,teardown_hook和setup_hook两个都是一个列表[],里面可以存多个字符串,注意这些字符串一般是”${函数名(参数)}”可以写多个这种,运行的时候会依次调用,注意如果写其它的字符串的话就失去了其意义。
5:cookies依赖处理
1:重点来了,cookies依赖这个是一个比较棘手的问题,我们模拟平时工作的场景,例如登陆一个系统, 我们只是登陆一次,服务器返回cookies并且存到客户端中,其它接口请求都是从客户端拿到并且携带一块去服务器请求,注意登陆获取cookies的接口只运行一次该如何处理???
会写代码可以看看:
我目前能想到这样处理,框架有个方法https://cn.httprunner.org/development/dev-api/
runner实例化出的对象可以用于执行一个api文件或者json文件,执行文件完成后这个对象有个属性应该是_summary(官方文档没有更新)是运行结果数据(具体数据结构看下运行出来结果,官方文档有些没更新),我们只要取到 cookies并且利用RequestsCookieJar模块设置完成后返回一个对象(api文件可以调用)。有一个问题,我们说会有多接口都会依赖cookies,每个测试用例都会调用,那岂不是执行一个测试用例都是会执行下登陆接口,不符合我们的预期,该如何做???我们可以这样将设置好的cookies对象保存到缓存中,如果缓存中有了就取缓存中的cookies,如果没有就执行登陆接口,自始至终只会运行 一遍登陆接口,具体代码实现省略。。。。
用法。。。。。。
2:先准备录制好登陆接口,在api文件的variables中调用debugtalk.py文件中get_cookies函数(传入登陆接口文件的路径) ,赋予一个变量cookie_jar(可以赋予,可以不赋予直接调用),然后在api文件request中加上cookies字段引用上面的cookie_jar变量,注意这个函数比较特殊一定要传入能拿到cookies接口文件,其它文件就报错了, 用法比较严格
6:文件上传
1:文件上传一般都是基于form-data这种请求头进行处理,这个格式真的非常特殊非常特殊非常特殊,格式如图:
如果是普通的请求参数,格式是这样的:name=”请求参数名”,紧接着参数值是在\r\n\r\n“值”\r\n 里面。如果是有关文件的请求参数,格式是这样的:name=”请求参数名”; filename结尾,紧接着参数值是在\r\n\r\n”值”\r\n里面,但是值一个二进制数据。
2:说明一下,httprunner框架针对这个form-data格式的接口,我觉得有点没有处理好。Httprunner框架是将这些乱七八糟的数据都序列化了你用hrun命令执行一下就会看到一长串的数据,我是这样处理的, 如果请求头是form-data格式的,就用正则分两步匹配出相关的请求参数和请求值,第一步匹配普通的请求参数和值,第二部匹配文件相关的请求参数但是不匹配值,而是一个格式"${get_file(file_path)}"(后续会说这个为啥写这个格式),整个接口如果有文件相关的请求参数就匹配,如果没有就不匹配。
3:把文件相关的请求参数和普通的请求参数都提前出来就好处理了,如果有文件相关的请求参数,那么在生成yaml格式的api文件的时候,就写入文件中,注意headers把content-type等于form-data相关的去掉,我写的功能python spell -a demo.har 在生成api文件的时候就已经忽略了(要不然运行文件会出错)
这个文件传参格式也是固定的。这个格式是requests请求的一个传参格式,如图
files(固定语法)字段专门用来上传文件,对一个字典格式,img上传文件请求的参数名,open(“文件路 径”, “rb”)是一个以二进制读的方式打开一个文件,返回一个对象(文件句柄)。如果有多个上传文件请求的参数的话也会将其存到该字典当中。注意看我们生成的yaml文件file:是调用一个函数,也就是说我们必须在debugtalk.py文件中写入一个函数如图
平常用的时候,只需要注意是否有文件上传的字段,如果有只需要把相关文件放在项目的data路径下,然后将file_path覆盖成一个文件的路径就可以了运行了