httprunner 使用教程
#安装
pip3.7 install httprunner
hrun -V 查看版本,能看到版本信息,就代表安装成功
hrun -h 查看帮助信息
#命令说明
在 HttpRunner 安装成功后,系统中会新增 4 个命令:
httprunner : 核心命令
hrun:httprunner 的缩写,功能与 httprunner 完全相同
hmake:httprunner make的别名,用于将YAML/JSON测试用例转换为pytest文件
har2case:辅助工具,可将标准通用的 HAR 格式(HTTP Archive)转换为YAML/JSON格式的测试用例
#查看命令说明
httprunner startproject -h
#新建项目
httprunner startproject httprunner_demo
#查看项目结构
macname@MacdeMacBook-Pro Desktop % cd httprunner_demo macname@MacdeMacBook-Pro httprunner_demo % macname@MacdeMacBook-Pro httprunner_demo % ls -l total 8 -rw-r--r--@ 1 macname staff 185 12 10 09:47 debugtalk.py drwxr-xr-x 2 macname staff 64 12 10 09:47 har drwxr-xr-x 2 macname staff 64 12 10 09:47 reports drwxr-xr-x 4 macname staff 128 12 10 09:47 testcases macname@MacdeMacBook-Pro httprunner_demo %
#结构说明
debugtalk.py 放置在项目根目录下(借鉴了pytest的conftest文件的设计)
.env 放置在项目根目录下,可以用于存放一些环境变量
reports 文件夹:存储 HTML 测试报告
testcases 用于存放测试用例
har 可以存放录制导出的.har文件
具体用法会在后续中细讲,本章不展开。我们可以点开生成的testcases文件夹下的测试用例,里面是提供了一个可运行的demo内容的,那先来运行一下看看。
#运行项目
macname@MacdeMacBook-Pro Desktop % macname@MacdeMacBook-Pro Desktop % hrun httprunner_demo 2020-12-10 09:53:27.334 | INFO | httprunner.make:__make:512 - make path: /Users/macname/Desktop/httprunner_demo 2020-12-10 09:53:27.340 | INFO | httprunner.compat:ensure_testcase_v3:219 - ensure compatibility with testcase format v2 2020-12-10 09:53:27.343 | INFO | httprunner.loader:load_dot_env_file:127 - Loading environment variables from /Users/macname/Desktop/httprunner_demo/.env 2020-12-10 09:53:27.344 | DEBUG | httprunner.utils:set_os_environ:33 - Set OS environment variable: USERNAME 2020-12-10 09:53:27.344 | DEBUG | httprunner.utils:set_os_environ:33 - Set OS environment variable: PASSWORD 2020-12-10 09:53:27.350 | INFO | httprunner.make:make_testcase:349 - start to make testcase: /Users/macname/Desktop/httprunner_demo/testcases/demo_testcase_ref.yml 2020-12-10 09:53:27.361 | INFO | httprunner.compat:ensure_testcase_v3:219 - ensure compatibility with testcase format v2 2020-12-10 09:53:27.362 | INFO | httprunner.make:make_testcase:349 - start to make testcase: /Users/macname/Desktop/httprunner_demo/testcases/demo_testcase_request.yml 2020-12-10 09:53:27.363 | INFO | httprunner.make:make_testcase:442 - generated testcase: /Users/macname/Desktop/httprunner_demo/testcases/demo_testcase_request_test.py 2020-12-10 09:53:27.364 | INFO | httprunner.make:make_testcase:442 - generated testcase: /Users/macname/Desktop/httprunner_demo/testcases/demo_testcase_ref_test.py 2020-12-10 09:53:27.375 | INFO | httprunner.compat:ensure_testcase_v3:219 - ensure compatibility with testcase format v2 2020-12-10 09:53:27.375 | INFO | httprunner.make:make_testcase:349 - start to make testcase: /Users/macname/Desktop/httprunner_demo/testcases/demo_testcase_request.yml 2020-12-10 09:53:27.376 | INFO | httprunner.make:format_pytest_with_black:170 - format pytest cases with black ... reformatted /Users/macname/Desktop/httprunner_demo/testcases/demo_testcase_ref_test.py reformatted /Users/macname/Desktop/httprunner_demo/testcases/demo_testcase_request_test.py All done! ✨ 🍰 ✨ 2 files reformatted. 2020-12-10 09:53:27.704 | INFO | httprunner.cli:main_run:56 - start to run tests with pytest. HttpRunner version: 3.1.4 ====================================================== test session starts ======================================================= platform darwin -- Python 3.7.4, pytest-5.4.3, py-1.8.0, pluggy-0.13.0 rootdir: /Users/macname/Desktop plugins: metadata-1.11.0, html-2.1.1, ordering-0.6, cov-2.8.1 collected 2 items httprunner_demo/testcases/demo_testcase_request_test.py . [ 50%] httprunner_demo/testcases/demo_testcase_ref_test.py . [100%] ======================================================= 2 passed in 5.17s ======================================================== macname@MacdeMacBook-Pro Desktop %
#转换为pytest文件
macname@MacdeMacBook-Pro Desktop % cd httprunner_demo/har macname@MacdeMacBook-Pro har % macname@MacdeMacBook-Pro har % har2case yinxiang.har 2020-12-10 10:00:18.639 | INFO | httprunner.ext.har2case.core:gen_testcase:356 - Start to generate testcase from /Users/macname/Desktop/httprunner_demo/har/yinxiang.har 2020-12-10 10:00:18.639 | INFO | httprunner.ext.har2case.core:_make_testcase:347 - Extract info from HAR file and prepare for testcase. 2020-12-10 10:00:18.641 | INFO | httprunner.compat:ensure_testcase_v3:219 - ensure compatibility with testcase format v2 2020-12-10 10:00:18.643 | INFO | httprunner.loader:load_dot_env_file:127 - Loading environment variables from /Users/macname/Desktop/httprunner_demo/.env 2020-12-10 10:00:18.643 | DEBUG | httprunner.utils:set_os_environ:33 - Set OS environment variable: USERNAME 2020-12-10 10:00:18.644 | DEBUG | httprunner.utils:set_os_environ:33 - Set OS environment variable: PASSWORD 2020-12-10 10:00:18.644 | INFO | httprunner.make:make_testcase:349 - start to make testcase: /Users/macname/Desktop/httprunner_demo/har/yinxiang.har 2020-12-10 10:00:18.645 | INFO | httprunner.make:make_testcase:442 - generated testcase: /Users/macname/Desktop/httprunner_demo/har/yinxiang_test.py 2020-12-10 10:00:18.646 | INFO | httprunner.make:format_pytest_with_black:170 - format pytest cases with black ... reformatted /Users/macname/Desktop/httprunner_demo/har/yinxiang_test.py All done! ✨ 🍰 ✨ 1 file reformatted. 2020-12-10 10:00:19.095 | INFO | httprunner.ext.har2case.core:gen_testcase:377 - generated testcase: /Users/macname/Desktop/httprunner_demo/har/yinxiang_test.py Sentry is attempting to send 0 pending error messages Waiting up to 2 seconds Press Ctrl-C to quit macname@MacdeMacBook-Pro har % ls __init__.py yinxiang.har yinxiang_test.py macname@MacdeMacBook-Pro har % macname@MacdeMacBook-Pro har %
#转为YAML:
har2case yinxiang.har -2y
#转换为JSON:
har2case yinxiang.har -2j
结果:
macname@MacdeMacBook-Pro har % macname@MacdeMacBook-Pro har % ls -l total 216 -rw-r--r-- 1 macname staff 48 12 10 10:00 __init__.py -rw-r--r-- 1 macname staff 42124 12 10 09:56 yinxiang.har -rw-r--r-- 1 macname staff 21468 12 10 10:05 yinxiang.json -rw-r--r-- 1 macname staff 14698 12 10 10:05 yinxiang.yml -rw-r--r-- 1 macname staff 17863 12 10 10:00 yinxiang_test.py macname@MacdeMacBook-Pro har % macname@MacdeMacBook-Pro har % macname@MacdeMacBook-Pro har %
httprunner可以支持三种格式的用例,分别是pytest、yaml和json。yaml和json是以前的版本所使用的用例格式,但是在3.x版本上,官方强烈建议使用的是pytest格式的用例。
上图是来自官方的用例格式关系图,可以看出来,httprunner再对于第三方导出的har文件进行了转换处理,有的人喜欢转换成json,有的人喜欢转换成yaml。但是最终,还是通过解析json格式的文件,生成pytest的python文件。
既然最后都是要生成pytest,那何不一步到位呢?哈哈,我想这就是官方推荐pytest格式的原因吧。
我还是挺喜欢的,因为我对于pytest使用的较多,那么接下来也是基于pytest格式的用例进行解析。
#用例结构分析
录制生成的case很便捷,但是这并不是说,不需要我们做任何的改动了。在实践的过程中,我们仍然会根据我们实际项目的不同需求来对case作进一步的调整,所以彻底的了解case的构造尤为重要。
首先,我录制了一个百度搜索“httprunner”的一个请求,转换成pytest文件后如下:
可以看到:
- 每个testcase都是HttpRunner的子类
- 必须有两个类属性:config和teststeps。
- 单个teststeps列表中的单个Step内部通过链式调用(RunRequest().get().with_params().with_header().with_cookies().validate().assert_equal())
- config:配置测试用例级设置,包括基础url、验证、变量、导出。
- teststeps:teststep的列表(list[Step]),每个步骤对应于一个API请求,也可以调用另一个testcase。此外,还支持variables/extract/validate/hooks机制来创建极其复杂的测试场景。
- 链调用:可以看到一个case的请求,经过了各个环节的调用,这也是httprunner 3.x版本一大亮点。现在的ide编辑器越来越强大,比如你使用pycharm的话,都不用你怎么记忆用例的格式,顺手就...(点)出来了,这或许也是官方推荐使用pytest的另一个原因吧,哈哈。
补一个官方完整的一个demo代码,并说说httprunner中的用例与我自己编写的测试用例之间的联系。
from httprunner import HttpRunner, Config, Step, RunRequest, RunTestCase class TestCaseRequestWithFunctions(HttpRunner): config = ( Config("request methods testcase with functions") .variables( **{ "foo1": "config_bar1", "foo2": "config_bar2", "expect_foo1": "config_bar1", "expect_foo2": "config_bar2", } ) .base_url("http://demo.qa.com") .verify(False) .export(*["foo3"]) ) teststeps = [ Step( RunRequest("get with params") .with_variables( **{"foo1": "bar11", "foo2": "bar21", "sum_v": "${sum_two(1, 2)}"} ) .get("/get") .with_params(**{"foo1": "$foo1", "foo2": "$foo2", "sum_v": "$sum_v"}) .with_headers(**{"User-Agent": "HttpRunner/${get_httprunner_version()}"}) .extract() .with_jmespath("body.args.foo2", "foo3") .validate() .assert_equal("status_code", 200) .assert_equal("body.args.foo1", "bar11") .assert_equal("body.args.sum_v", "3") .assert_equal("body.args.foo2", "bar21") ), Step( RunRequest("post form data") .with_variables(**{"foo2": "bar23"}) .post("/post") .with_headers( **{ "User-Agent": "HttpRunner/${get_httprunner_version()}", "Content-Type": "application/x-www-form-urlencoded", } ) .with_data("foo1=$foo1&foo2=$foo2&foo3=$foo3") .validate() .assert_equal("status_code", 200) .assert_equal("body.form.foo1", "$expect_foo1") .assert_equal("body.form.foo2", "bar23") .assert_equal("body.form.foo3", "bar21") ), ] if __name__ == "__main__": TestCaseRequestWithFunctions().test_start()
#
#
- httprunner中的testcase,其实说的就是上面的这一整个Python文件。
- teststeps列表中的Step,其实就是我自己编写case时候的一个个def test_xxx():pass。
- 而每一个Step内部,依然是按照 传参——调用接口——断言,这样的过程来的。
万变不离其宗,httprunner框架目前看起来,确实可以让编写更加的便捷、简洁,但是这只是目前从demo的过程中得到的结论,后面还需要落地实战才可以。
我们了解到了config,在配置中,我们可以配置测试用例级级别的一些设置,比如基础url、验证、变量、导出。
我们一起来看,官方给出的一个例子:
from httprunner import HttpRunner, Config, Step, RunRequest, RunTestCase class TestCaseRequestWithFunctions(HttpRunner): config = ( Config("request methods testcase with functions") .variables( **{ "foo1": "config_bar1", "foo2": "config_bar2", "expect_foo1": "config_bar1", "expect_foo2": "config_bar2", } ) .base_url("http://demo.qa.com") .verify(False) .export(*["foo3"]) ) teststeps = [ Step( RunRequest("get with params") .with_variables( **{"foo1": "bar11", "foo2": "bar21", "sum_v": "${sum_two(1, 2)}"} ) .get("/get") .with_params(**{"foo1": "$foo1", "foo2": "$foo2", "sum_v": "$sum_v"}) .with_headers(**{"User-Agent": "HttpRunner/${get_httprunner_version()}"}) .extract() .with_jmespath("body.args.foo2", "foo3") .validate() .assert_equal("status_code", 200) .assert_equal("body.args.foo1", "bar11") .assert_equal("body.args.sum_v", "3") .assert_equal("body.args.foo2", "bar21") ), Step( RunRequest("post form data") .with_variables(**{"foo2": "bar23"}) .post("/post") .with_headers( **{ "User-Agent": "HttpRunner/${get_httprunner_version()}", "Content-Type": "application/x-www-form-urlencoded", } ) .with_data("foo1=$foo1&foo2=$foo2&foo3=$foo3") .validate() .assert_equal("status_code", 200) .assert_equal("body.form.foo1", "$expect_foo1") .assert_equal("body.form.foo2", "bar23") .assert_equal("body.form.foo3", "bar21") ), ] if __name__ == "__main__": TestCaseRequestWithFunctions().test_start()
一、name(必填)
即用例名称,这是一个必填参数。测试用例名称,将显示在执行日志和测试报告中。比如,我在之前的百度搜索的case里,加入name。
二、base_url(选填)
其实这个配置一般在多环境切换中最常用。
比如你的这套测试用例在qa环境,uat环境都要使用,那么就可以把基础地址(举例http://demo.qa.com),设置进去。在后面的teststep中,只需要填上接口的相对路径就好了(举例 /get)。
这样的话,切换环境运行,只需要修改base_url即可。
三、variables(选填)
变量,这里可以存放一些公共的变量,可以在测试用例里引用。这里大家可以记住这个“公共”的词眼,因为在后面的Step中,还会有步骤变量。
比如说,我的接口有个传参是不变的,比如用户名username,而且后面的没个Step都会用到这个传参,那么username就可以放在config的公共变量里。
另外,Step里的变量优先级是比config里的变量要高的,如果有2个同名的变量的话,那么引用的时候,是优先引用步骤里的变量的。
四、verify(选填)
用来决定是否验证服务器TLS证书的开关。
通常设置为False,当请求https请求时,就会跳过验证。如果你运行时候发现抛错SSLError,可以检查一下是不是verify没传,或者设置了True。
五、export(选填)
导出的变量,主要是用于Step之间参数的传递。还是以上面的官方代码为例:
- 在config中配置export“foo3”这个变量。
- 在第一个Step中,.extract() 提取了"body.args.foo2"给变量“foo3”。
- 在第二个Step中,引用变量"foo3"。
像参数传递,提取这些点,会放在后面单独讲解,前面还是以熟悉框架为主。
测试用例分层模型
一个testcase里(就是一个pytest格式的Python文件)可以有一个或者多个测试步骤,就是teststeps[]列表里的Step。
我的理解每一个Step就可以类比成pytest框架下的def test_xxx()的用例函数,在Step里通常都会要请求API完成测试,也可以调用其他测试用例来完成更多的需求。
可以来看下官方的测试用例逻辑图(2.x版本不同,3.x弃用了2.x的API概念):
可以看到,testsuite包含了testcase,testcase1需要依赖testcase2才可以完成,那么就可以在teststep12对其进行引用;而testcase2又依赖于testcase3,那么也可以在teststep22进行引用。
但是在整个testsuite下,这3个testcase都是相互独立的,可以独自运行。如果需要相互调用,则是在testcase内部去完成处理。
可能看起来有点绕,其实官方想表达的就是测试用例分层的一个思想:
- 测试用例(testcase)应该是完整且独立的,每条测试用例应该是都可以独立运行的
- 测试用例是测试步骤(teststep)的有序集合
- 测试用例集(testsuite)是测试用例的无序集合,集合中的测试用例应该都是相互独立,不存在先后依赖关系的;如果确实存在先后依赖关系,那就需要在测试用例中完成依赖的处理
其实这一点,在我们自己使用pytest框架编写测试用例的时候同样贯彻到了。为了自动化测试的稳定性和可维护性,每个测试用例之间相互独立是非常有必要的。
1.RunRequest(name)
RunRequest的参数名用于指定teststep名称,它将显示在执行日志和测试报告中。
2. .with_variables
用于存放变量,但是这里的是Step步骤里的变量,不同的Step的变量是相互独立的。所以对于多个Step都要使用的变量,我们可以放到config的变量里去。
另外,如果config和Step里有重名的变量,那么当你引用这个变量的时候,Step变量会覆盖config变量。
3. .method(url)
这里就是指定请求API的方法了,常用的get、post等等。如图所示,就是用的get方法,括号里的url就是要请求的地址了。
这里要注意的是,如果在config里设置了基础url,那么步骤里的url就只能设置相对路径了。
4. .with_params
这个就简单了,测接口不都得要传参么,对于params类型的传参,就放这就行了,key-value键值对的形式。对于body形式的传参,看后面。
5. .with_headers
同样,有header要带上的就放这里。
6. .with_cookies
需要带cookie的,可以用.with_cookies方法。
7. .with_data
对于body类型的传参,可以用.with_data。
8. .with_json
如果是json类型的body请求体,可以用.with_json。
9. .extract
这里就是要做提取操作了,使用.with_jmespath(jmes_path: Text, var_name: Text)。
这里是采用了JMESPath语言,JMESPath是JSON的查询语言,可以便捷的提取json中你需要的元素。
第一个参数是你的目标元素的jmespath表达式,第二个元素则是用来存放这个元素的变量,供有需要的引用。
这里不展开,后面单讲。
10. .validate
断言,我们测试最终就是要验证接口返回是否符合预期。
那在httprunner框架中,可以使用assert_XXX(jmes_path: Text, expected_value: Any)来进行提取和验证。
第一个参数还是jmespath表达式,第二个参数则是预期值。
assert_XXX这种方式相信用过自动化测试框架的都不会陌生,所以也非常容易上手。目前httprunner还是封装了非常丰富的断言方法的,相信可以满足绝大多数的需求了。
teststeps = [ Step( RunTestCase("request with functions") .with_variables( **{"foo1": "testcase_ref_bar1", "expect_foo1": "testcase_ref_bar1"} ) .call(RequestWithFunctions) .export(*["foo3"]) ), Step( RunRequest("post form data") .with_variables(**{"foo1": "bar1"}) .post("/post") .with_headers( **{ "User-Agent": "HttpRunner/${get_httprunner_version()}", "Content-Type": "application/x-www-form-urlencoded", } ) .with_data("foo1=$foo1&foo2=$foo3") .validate() .assert_equal("status_code", 200) .assert_equal("body.form.foo1", "bar1") .assert_equal("body.form.foo2", "bar21") ), ]
1. RunTestCase(name)
这个参数呢还是一个名称,毕竟RunTestCase还是一个Step,这个名称同样会在日志和报告中显示。
2. .with_variables
这个变量跟RunRequest里的用法一样。
3. .call
这里就是指定你要引用的testcase类名称了。
4. .export
可以指定要导出的变量,以供后续Step引用。
可以看的.export()内部是一个列表[],这里可以用来导出多个变量。
一、运行testcase的几种场景
1. 运行单个case
通常单个case的话我会在编辑器里用main方法运行,不过也可以用命令行运行,看你喜欢。
main方法里在类的后面调用test_start()方法即可。
命令行的话,就是直接在hrun后面加上case的路径,就可以运行了。
hrun httprunner_demo\testcases\get_user_name_test.py
from httprunner import HttpRunner, Config, Step, RunRequest class TestCaseRequestWithGetUserName(HttpRunner): config = ( Config("test /getUserName") .base_url("http://localhost:5000") .verify(False) .export(*["username"]) ) teststeps = [ Step( RunRequest("getUserName") .get("/getUserName") .extract() .with_jmespath("body.username", "username") .validate() .assert_equal("body.username", "wesson") ), ] if __name__ == "__main__": TestCaseRequestWithGetUserName().test_start()#这里
2. 运行多个case
hrun httprunner_demo\testcases\get_user_name_test.py httprunner_demo\testcases\join_str_test.py
3. 运行整个case文件夹
hrun httprunner_demo\testcases\
二、运行YAML/JSON文件格式的case
当你转换har文件时候,同级文件夹下,就会生成对应的pytest的文件,文件名称的末尾会有_test。
如果运行YAML/JSON文件,其实httprunner会先把它们转换为pytest格式的,再去运行。
所以,用httprunner 3.x版本的话,写case的话还是直接用pytest格式吧。
三、运行pytest格式的文件
对应pytest文件来说,用hrun或者pytest命令运行都是可以的。
因为hrun只是封装了pytest,所以pytest运行的所有参数,同样可以在hrun后面加。
hrun -h
pytest -h
用例引用
#get_user_name_test.py
from httprunner import HttpRunner, Config, Step, RunRequest class TestCaseRequestWithGetUserName(HttpRunner): config = ( Config("test /getUserName") .base_url("http://localhost:5000") .verify(False) .export(*["username"])#这里定义出要导出的变量 ) teststeps = [ Step( RunRequest("getUserName") .get("/getUserName") .extract() .with_jmespath("body.username", "username")#提取出目标值,赋值给username变量 .validate() .assert_equal("body.username", "wesson") ), ] if __name__ == "__main__": TestCaseRequestWithGetUserName().test_start()
#TestCaseRequestWithJoinStr.py
from httprunner import HttpRunner, Config, Step, RunRequest, RunTestCase from .get_user_name_test import TestCaseRequestWithGetUserName #记得要导入引用的类 class TestCaseRequestWithJoinStr(HttpRunner): config = ( Config("test /joinStr") .base_url("http://localhost:5000") .verify(False) ) teststeps = [ Step( RunTestCase("setUp getUserName") .call(TestCaseRequestWithGetUserName)#导入后就可以调用了 .export(*["username"])#在RunTestCase步骤中定义这个变量的导出 ), Step( RunRequest("joinStr") .get("/joinStr") .with_params(**{"str1": "hello", "str2": "$username"})#在第二个传参中引用导出的变量 .validate() .assert_equal("body.result", "hello $username")#断言的预期值也引用变量 ), ] if __name__ == "__main__": TestCaseRequestWithJoinStr().test_start()