zl程序教程

您现在的位置是:首页 >  .Net

当前栏目

HttpRunner V3.x 从入门到精通

2023-02-18 16:37:28 时间

一、介绍

HttpRunner 是一款面向 HTTP(S) 协议的通用测试框架,只需编写维护一份YAML/JSON脚本,即可实现自动化测试、性能测试、线上监控、持续集成等多种测试需求。
官方文档:https://docs.httprunner.org/

二、框架设计理念

  • 充分复用优秀的开源项目,不追求重复造轮子,而是将强大的轮子组装成战车
  • 遵循 约定大于配置 的准则,在框架功能中融入自动化测试最佳工程实践
  • 追求投入产出比,一份投入即可实现多种测试需求

三、核心特点

  • 继承 Requests 的全部特性,轻松实现 HTTP(S) 的各种测试需求
  • 以YAML或JSON格式定义testcase,使用pytest运行,更加简洁优雅
  • 有了pytest,成百上千的插件随时可用
  • 支持variables/extract/validate/hooks机制来创建极其复杂的测试场景
  • 借助辅助函数(debugtalk.py),任何函数都可以在测试用例的任何部分中使用
  • 支持完善的测试用例分层机制,充分实现测试用例的复用
  • 使用python的第三方库jmespath,让提取和验证json响应更简单
  • 基于 HAR 实现接口录制和用例生成功能(har2case)
  • 结合 Locust 框架,无需额外的工作即可实现分布式性能测试
  • 执行方式采用 CLI 调用,可与 Jenkins 等持续集成工具完美结合
  • 集成了Allure,让测试报告更加漂亮,内容更丰富。
  • 极强的可扩展性,轻松实现二次开发和 Web 平台化

四、环境说明

HttpRunner 是一个基于 Python 开发的测试框架,可以运行在 macOS、Linux、Windows 系统平台上。笔者使用的是macOS系统,所以后续都是基于macOS系统的学习。

另外,HttpRunner 支持 Python 3.5 及以上的所有版本,虽然 HttpRunner 暂时保留了对 Python 2.7 的兼容支持,但强烈建议使用 Python 3.6 及以上版本。

电脑上还没安装Python的童鞋还请自行查询安装,非常的简单,这里附上python的官方地址:https://www.python.org/

五、安装HttpRunner

HttpRunner 的稳定版本托管在 PyPI 上,可以使用pip进行安装,非常的便捷。
打开命令行,输入安装命令即可:
二、安装HttpRunner
HttpRunner 的稳定版本托管在 PyPI 上,可以使用pip进行安装,非常的便捷。
打开CMD,输入安装命令即可:

pip install httprunner

如果已经安装过的,也可以进行更新升级一下。

pip install -U HttpRunner

检验是否安装成功
输入命令:

$ hrun -V
3.1.4

显示出版本号,说明安装成功。
你也可以通过输入:hrun -h,查看命令帮助说明。
在 HttpRunner 安装成功后,系统中会新增 4 个命令:

  • httprunner: 核心命令
  • hrun:httprunner 的缩写,功能与 httprunner 完全相同
  • hmakehttprunner make的别名,用于将YAML/JSON测试用例转换为pytest文件
  • har2case:辅助工具,可将标准通用的 HAR 格式(HTTP Archive)转换为YAML/JSON格式的测试用例

六、快速生成项目

环境装好了,相信很多童鞋已经迫不及待的想run起来了,但是面对一个陌生的框架又无从下手。没关系,我们可以用脚手架来快速生成一个httprunner项目。
我们不妨先输入httprunner startproject -h,来看一下命令说明。

chenshiengdeMBP:PythonCode chenshifeng$ httprunner startproject -h

usage: httprunner startproject [-h] [project_name]

positional arguments:
  project_name  Specify new project name.

optional arguments:
  -h, --help    show this help message and exit

可以看出,只需要在命令后面带上项目名称这个参数就好了,那就先来创建一个项目,名称叫httprunner_demo。

chenshiengdeMBP:PythonCode chenshifeng$ httprunner startproject httprunner_demo
2021-05-04 17:27:16.152 | INFO     | httprunner.scaffold:create_scaffold:43 - Create new project: httprunner_demo
Project Root Dir: /Users/chenshifeng/MyCode/PythonCode/httprunner_demo

created folder: httprunner_demo
created folder: httprunner_demo/har
created folder: httprunner_demo/testcases
created folder: httprunner_demo/reports
created file: httprunner_demo/testcases/demo_testcase_request.yml
created file: httprunner_demo/testcases/demo_testcase_ref.yml
created file: httprunner_demo/debugtalk.py
created file: httprunner_demo/.env
created file: httprunner_demo/.gitignore

$ tree httprunner_demo -a
2021-05-04 17:27:16.161 | WARNING  | httprunner.scaffold:show_tree:29 - tree command not exists, ignore.
Sentry is attempting to send 0 pending error messages
Waiting up to 2 seconds
Press Ctrl-C to quit
chenshiengdeMBP:PythonCode chenshifeng$ 

项目生成完毕,也是非常的简单。
如果你输入的项目名称已经存在,httprunner会给出warning提示。

chenshiengdeMBP:PythonCode chenshifeng$ httprunner startproject httprunner_demo
2021-05-04 17:29:00.631 | WARNING  | httprunner.scaffold:create_scaffold:32 - Project folder httprunner_demo exists, please specify a new project name.

$ tree httprunner_demo -a
2021-05-04 17:29:00.636 | WARNING  | httprunner.scaffold:show_tree:29 - tree command not exists, ignore.
Sentry is attempting to send 0 pending error messages
Waiting up to 2 seconds
Press Ctrl-C to quit

相信了解过django的童鞋能感觉到,httprunner startproject这个命令跟django里的django-admin.py startproject project_name 很像,没错,其实httprunner的想法正式来源于django,这就是httprunner作为一个优秀开源技术资源整合和复用的体现之一,后续还有很多,届时提点出来。

七、项目结构梳理

我把生成出的项目用pycharm打开,可以看的生成的目录结构如下图,那么这些都是什么意思呢?
image

  • debugtalk.py 放置在项目根目录下(借鉴了pytest的conftest.py文件的设计)
  • .env 放置在项目根目录下,可以用于存放一些环境变量
  • reports 文件夹:存储 HTML 测试报告
  • testcases 用于存放测试用例
  • har 可以存放录制导出的.har文件
    具体用法会在后续中细讲,我们可以点开生成的testcases文件夹下的测试用例,里面是提供了一个可运行的demo内容的,那先来运行一下看看。
    image

运行用例:
hrun httprunner_demo

chenshiengdeMBP:PythonCode chenshifeng$ hrun httprunner_demo
2021-05-04 17:38:15.284 | INFO     | httprunner.make:__make:512 - make path: /Users/chenshifeng/MyCode/PythonCode/httprunner_demo
2021-05-04 17:38:15.290 | INFO     | httprunner.compat:ensure_testcase_v3:219 - ensure compatibility with testcase format v2
2021-05-04 17:38:15.293 | INFO     | httprunner.loader:load_dot_env_file:127 - Loading environment variables from /Users/chenshifeng/MyCode/PythonCode/httprunner_demo/.env
2021-05-04 17:38:15.294 | DEBUG    | httprunner.utils:set_os_environ:33 - Set OS environment variable: USERNAME
2021-05-04 17:38:15.294 | DEBUG    | httprunner.utils:set_os_environ:33 - Set OS environment variable: PASSWORD
2021-05-04 17:38:15.296 | INFO     | httprunner.make:make_testcase:349 - start to make testcase: /Users/chenshifeng/MyCode/PythonCode/httprunner_demo/testcases/demo_testcase_ref.yml
2021-05-04 17:38:15.306 | INFO     | httprunner.compat:ensure_testcase_v3:219 - ensure compatibility with testcase format v2
2021-05-04 17:38:15.306 | INFO     | httprunner.make:make_testcase:349 - start to make testcase: /Users/chenshifeng/MyCode/PythonCode/httprunner_demo/testcases/demo_testcase_request.yml
2021-05-04 17:38:15.307 | INFO     | httprunner.make:make_testcase:442 - generated testcase: /Users/chenshifeng/MyCode/PythonCode/httprunner_demo/testcases/demo_testcase_request_test.py
2021-05-04 17:38:15.308 | INFO     | httprunner.make:make_testcase:442 - generated testcase: /Users/chenshifeng/MyCode/PythonCode/httprunner_demo/testcases/demo_testcase_ref_test.py
2021-05-04 17:38:15.317 | INFO     | httprunner.compat:ensure_testcase_v3:219 - ensure compatibility with testcase format v2
2021-05-04 17:38:15.317 | INFO     | httprunner.make:make_testcase:349 - start to make testcase: /Users/chenshifeng/MyCode/PythonCode/httprunner_demo/testcases/demo_testcase_request.yml
2021-05-04 17:38:15.317 | INFO     | httprunner.make:format_pytest_with_black:170 - format pytest cases with black ...
reformatted /Users/chenshifeng/MyCode/PythonCode/httprunner_demo/testcases/demo_testcase_ref_test.py
reformatted /Users/chenshifeng/MyCode/PythonCode/httprunner_demo/testcases/demo_testcase_request_test.py
All done! ✨ ? ✨
2 files reformatted.
2021-05-04 17:38:16.063 | INFO     | httprunner.cli:main_run:56 - start to run tests with pytest. HttpRunner version: 3.1.4
============================================= test session starts ==============================================
platform darwin -- Python 3.9.2, pytest-6.2.2, py-1.10.0, pluggy-0.13.1
rootdir: /Users/chenshifeng/MyCode/PythonCode
plugins: metadata-1.11.0, html-2.1.1, allure-pytest-2.8.40
collected 2 items                                                                                              

httprunner_demo/testcases/demo_testcase_ref_test.py .                                                    [ 50%]
httprunner_demo/testcases/demo_testcase_request_test.py .                                                [100%]

============================================== 2 passed in 6.21s ===============================================

可以看的httprunner输出了运行过程中的调试信息。最后,运行结束,2个用例运行pass。

八、录制生成测试用例

在正式手动编写case之前,我们可以先来熟悉下httprunner的录制生成用例功能。
用postman的童鞋都知道,里面有个功能可以将接口转换成代码,可以直接copy过来使用,提升case编写效率。
那httprunner的录制生成用例功能又是怎么回事呢?

8.1、har2case

其实,这都要依托于另一个独立的项目-har2case。
原理就是当前主流的抓包工具和浏览器都支持将抓取得到的数据包导出为标准通用的 HAR 格式(HTTP Archive),然后 HttpRunner 将 HAR 格式的数据包转换为YAML/JSON格式的测试用例文件。
比如,我现在用macOS系统上的Charles去抓取一个百度首页搜索httprunner的请求。
image
选中这个请求,点击左上角的File——Export Sessions——(可以选择导出选中的也可以导出所有),这里我们选择导出选中的,导出HTTPArchive,文件名baidu_home.har,保存到了项目的har目录下。
image
image

8.2、转换为pytest文件

运行命令将har文件转换成测试用例:

har2case baidu_home.har
chenshifengdeMacBook-Pro:har chenshifeng$ har2case baidu_home.har
2021-05-04 17:52:23.608 | INFO     | httprunner.ext.har2case.core:gen_testcase:356 - Start to generate testcase from /Users/chenshifeng/MyCode/PythonCode/httprunner_demo/har/baidu_home.har
2021-05-04 17:52:23.608 | INFO     | httprunner.ext.har2case.core:_make_testcase:347 - Extract info from HAR file and prepare for testcase.
2021-05-04 17:52:23.611 | INFO     | httprunner.compat:ensure_testcase_v3:219 - ensure compatibility with testcase format v2
2021-05-04 17:52:23.611 | INFO     | httprunner.loader:load_dot_env_file:127 - Loading environment variables from /Users/chenshifeng/MyCode/PythonCode/httprunner_demo/.env
2021-05-04 17:52:23.612 | DEBUG    | httprunner.utils:set_os_environ:33 - Set OS environment variable: USERNAME
2021-05-04 17:52:23.612 | DEBUG    | httprunner.utils:set_os_environ:33 - Set OS environment variable: PASSWORD
2021-05-04 17:52:23.612 | INFO     | httprunner.make:make_testcase:349 - start to make testcase: /Users/chenshifeng/MyCode/PythonCode/httprunner_demo/har/baidu_home.har
2021-05-04 17:52:23.613 | INFO     | httprunner.make:make_testcase:442 - generated testcase: /Users/chenshifeng/MyCode/PythonCode/httprunner_demo/har/baidu_home_test.py
2021-05-04 17:52:23.613 | INFO     | httprunner.make:format_pytest_with_black:170 - format pytest cases with black ...
reformatted /Users/chenshifeng/MyCode/PythonCode/httprunner_demo/har/baidu_home_test.py
All done! ✨ ? ✨
1 file reformatted.
2021-05-04 17:52:23.914 | INFO     | httprunner.ext.har2case.core:gen_testcase:377 - generated testcase: /Users/chenshifeng/MyCode/PythonCode/httprunner_demo/har/baidu_home_test.py
Sentry is attempting to send 0 pending error messages
Waiting up to 2 seconds
Press Ctrl-C to quit

生成完毕,在har目录下可以看到生成出的python文件:

# NOTE: Generated By HttpRunner v3.1.4
# FROM: har/baidu_home.har


from httprunner import HttpRunner, Config, Step, RunRequest, RunTestCase


class TestCaseBaiduHome(HttpRunner):

    config = Config("testcase description").verify(False)

    teststeps = [
        Step(
            RunRequest("/s")
            .get("https://www.baidu.com/s")
            .with_params(
                **{
                    "ie": "utf-8",
                    "mod": "1",
                    "isbd": "1",
                    "isid": "C9FF25725AB54698",
                    "f": "8",
                    "rsv_bp": "1",
                    "rsv_idx": "1",
                    "tn": "baidu",
                    "wd": "httprunner",
                    "fenlei": "256",
                    "oq": "httprunner%20%26lt%3B",
                    "rsv_pq": "86a39119000039fe",
                    "rsv_t": "9d65Kx91ldJ2V3LDLjZmstZTQ27dNOYAMJ++oE9TlK6y2o+O7A7XdDS6Yus",
                    "rqlang": "cn",
                    "rsv_enter": "0",
                    "rsv_dl": "tb",
                    "rsv_sug3": "2",
                    "rsv_sug1": "2",
                    "rsv_sug7": "000",
                    "rsv_btype": "t",
                    "prefixsug": "httprunner",
                    "rsp": "1",
                    "inputT": "6648",
                    "rsv_sug4": "7252",
                    "rsv_sug": "2",
                    "bs": "httprunner 3",
                    "rsv_sid": "undefined",
                    "_ss": "1",
                    "clist": "",
                    "hsug": "httprunner 3\thttprunner",
                    "f4s": "1",
                    "csor": "10",
                    "_cr1": "40730",
                }
            )
            .with_headers(
                **{
                    "Host": "www.baidu.com",
                    "Connection": "keep-alive",
                    "Accept": "*/*",
                    "is_xhr": "1",
                    "X-Requested-With": "XMLHttpRequest",
                    "is_referer": "https://www.baidu.com/s?ie=utf-8&f=8&rsv_bp=1&rsv_idx=1&tn=baidu&wd=httprunner%203&fenlei=256&oq=httprunner%203&rsv_pq=86a39119000039fe&rsv_t=2b6c1PBdGIcDYzEKyW9BkMzeCPMYcfbqTSf%2FEDXZuefGUrmcy2q1pxhJ0NQ&rqlang=cn",
                    "User-Agent": "Mozilla/5.0 (Macintosh; Intel Mac OS X 10_16_0) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/85.0.4183.102 Safari/537.36",
                    "Sec-Fetch-Site": "same-origin",
                    "Sec-Fetch-Mode": "cors",
                    "Sec-Fetch-Dest": "empty",
                    "Referer": "https://www.baidu.com/s?ie=utf-8&f=8&rsv_bp=1&rsv_idx=1&tn=baidu&wd=httprunner&fenlei=256&oq=httprunner%2520%2526lt%253B&rsv_pq=86a39119000039fe&rsv_t=9d65Kx91ldJ2V3LDLjZmstZTQ27dNOYAMJ%2B%2BoE9TlK6y2o%2BO7A7XdDS6Yus&rqlang=cn&rsv_enter=0&rsv_dl=tb&rsv_sug3=2&rsv_sug1=2&rsv_sug7=000&rsv_btype=t&prefixsug=httprunner&rsp=1&inputT=6648&rsv_sug4=7252&rsv_sug=2",
                    "Accept-Encoding": "gzip, deflate, br",
                    "Accept-Language": "zh-CN,zh;q=0.9",
                    "Cookie": "BIDUPSID=EA49B0E234E0F93BBD3C0082A586CDEA; PSTM=1619952293; BAIDUID=C9FF25B24E5A3C59C96D61DB506725AB:FG=1; BD_UPN=123253; BDORZ=B490B5EBF6F3CD402E515D22BCDA1598; H_PS_PSSID=33986_33819_33849_33759_33675_33607_26350_33996; H_PS_645EC=9d65Kx91ldJ2V3LDLjZmstZTQ27dNOYAMJ%2B%2BoE9TlK6y2o%2BO7A7XdDS6Yus; delPer=0; BD_CK_SAM=1; PSINO=5; BDSVRTM=14; WWW_ST=1620121549937",
                }
            )
            .with_cookies(
                **{
                    "BIDUPSID": "EA49B0E234E0F93BBD3C0082A586CDEA",
                    "PSTM": "1619952293",
                    "BAIDUID": "C9FF25B24E5A3C59C96D61DB506725AB:FG=1",
                    "BD_UPN": "123253",
                    "BDORZ": "B490B5EBF6F3CD402E515D22BCDA1598",
                    "H_PS_PSSID": "33986_33819_33849_33759_33675_33607_26350_33996",
                    "H_PS_645EC": "9d65Kx91ldJ2V3LDLjZmstZTQ27dNOYAMJ%2B%2BoE9TlK6y2o%2BO7A7XdDS6Yus",
                    "delPer": "0",
                    "BD_CK_SAM": "1",
                    "PSINO": "5",
                    "BDSVRTM": "14",
                    "WWW_ST": "1620121549937",
                }
            )
            .validate()
            .assert_equal("status_code", 200)
            .assert_equal('headers."Content-Type"', "text/html;charset=utf-8")
        ),
    ]


if __name__ == "__main__":
    TestCaseBaiduHome().test_start()

因为httprunner封装了pytest,所有既可以用hrun去运行,也可以用pytest去运行。
hrun
image
pytest
image

8.3、转换为YAML/JSON

很简单,只要在命令后面多加对应的参数就行了。-2y/--to-yml 或者-2j/--to-json
转为YAML:

har2case baidu_home.har -2y

image
可以查看到转换生成的yaml文件了。

config:
    name: testcase description
    variables: {}
    verify: false
teststeps:
-   name: /s
    request:
        cookies:
            BAIDUID: C9FF25B24E5A3C59C96D61DB506725AB:FG=1
            BDORZ: B490B5EBF6F3CD402E515D22BCDA1598
            BDSVRTM: '14'
            BD_CK_SAM: '1'
            BD_UPN: '123253'
            BIDUPSID: EA49B0E234E0F93BBD3C0082A586CDEA
            H_PS_645EC: 9d65Kx91ldJ2V3LDLjZmstZTQ27dNOYAMJ%2B%2BoE9TlK6y2o%2BO7A7XdDS6Yus
            H_PS_PSSID: '33986_33819_33849_33759_33675_33607_26350_33996'
            PSINO: '5'
            PSTM: '1619952293'
            WWW_ST: '1620121549937'
            delPer: '0'
        headers:
            Accept: '*/*'
            Accept-Encoding: gzip, deflate, br
            Accept-Language: zh-CN,zh;q=0.9
            Connection: keep-alive
            Cookie: BIDUPSID=EA49B0E234E0F93BBD3C0082A586CDEA; PSTM=1619952293; BAIDUID=C9FF25B24E5A3C59C96D61DB506725AB:FG=1;
                BD_UPN=123253; BDORZ=B490B5EBF6F3CD402E515D22BCDA1598; H_PS_PSSID=33986_33819_33849_33759_33675_33607_26350_33996;
                H_PS_645EC=9d65Kx91ldJ2V3LDLjZmstZTQ27dNOYAMJ%2B%2BoE9TlK6y2o%2BO7A7XdDS6Yus;
                delPer=0; BD_CK_SAM=1; PSINO=5; BDSVRTM=14; WWW_ST=1620121549937
            Host: www.baidu.com
            Referer: https://www.baidu.com/s?ie=utf-8&f=8&rsv_bp=1&rsv_idx=1&tn=baidu&wd=httprunner&fenlei=256&oq=httprunner%2520%2526lt%253B&rsv_pq=86a39119000039fe&rsv_t=9d65Kx91ldJ2V3LDLjZmstZTQ27dNOYAMJ%2B%2BoE9TlK6y2o%2BO7A7XdDS6Yus&rqlang=cn&rsv_enter=0&rsv_dl=tb&rsv_sug3=2&rsv_sug1=2&rsv_sug7=000&rsv_btype=t&prefixsug=httprunner&rsp=1&inputT=6648&rsv_sug4=7252&rsv_sug=2
            Sec-Fetch-Dest: empty
            Sec-Fetch-Mode: cors
            Sec-Fetch-Site: same-origin
            User-Agent: Mozilla/5.0 (Macintosh; Intel Mac OS X 10_16_0) AppleWebKit/537.36
                (KHTML, like Gecko) Chrome/85.0.4183.102 Safari/537.36
            X-Requested-With: XMLHttpRequest
            is_referer: https://www.baidu.com/s?ie=utf-8&f=8&rsv_bp=1&rsv_idx=1&tn=baidu&wd=httprunner%203&fenlei=256&oq=httprunner%203&rsv_pq=86a39119000039fe&rsv_t=2b6c1PBdGIcDYzEKyW9BkMzeCPMYcfbqTSf%2FEDXZuefGUrmcy2q1pxhJ0NQ&rqlang=cn
            is_xhr: '1'
        method: GET
        params:
            _cr1: '40730'
            _ss: '1'
            bs: httprunner 3
            clist: ''
            csor: '10'
            f: '8'
            f4s: '1'
            fenlei: '256'
            hsug: "httprunner 3\thttprunner"
            ie: utf-8
            inputT: '6648'
            isbd: '1'
            isid: C9FF25725AB54698
            mod: '1'
            oq: httprunner%20%26lt%3B
            prefixsug: httprunner
            rqlang: cn
            rsp: '1'
            rsv_bp: '1'
            rsv_btype: t
            rsv_dl: tb
            rsv_enter: '0'
            rsv_idx: '1'
            rsv_pq: 86a39119000039fe
            rsv_sid: undefined
            rsv_sug: '2'
            rsv_sug1: '2'
            rsv_sug3: '2'
            rsv_sug4: '7252'
            rsv_sug7: '000'
            rsv_t: 9d65Kx91ldJ2V3LDLjZmstZTQ27dNOYAMJ++oE9TlK6y2o+O7A7XdDS6Yus
            tn: baidu
            wd: httprunner
        url: https://www.baidu.com/s
    validate:
    -   eq:
        - status_code
        - 200
    -   eq:
        - headers.Content-Type
        - text/html;charset=utf-8

转换为JSON:

har2case baidu_home.har -2j

image
可以看的对应的json文件:

{
    "config": {
        "name": "testcase description",
        "variables": {},
        "verify": false
    },
    "teststeps": [
        {
            "name": "/s",
            "request": {
                "url": "https://www.baidu.com/s",
                "params": {
                    "ie": "utf-8",
                    "mod": "1",
                    "isbd": "1",
                    "isid": "C9FF25725AB54698",
                    "f": "8",
                    "rsv_bp": "1",
                    "rsv_idx": "1",
                    "tn": "baidu",
                    "wd": "httprunner",
                    "fenlei": "256",
                    "oq": "httprunner%20%26lt%3B",
                    "rsv_pq": "86a39119000039fe",
                    "rsv_t": "9d65Kx91ldJ2V3LDLjZmstZTQ27dNOYAMJ++oE9TlK6y2o+O7A7XdDS6Yus",
                    "rqlang": "cn",
                    "rsv_enter": "0",
                    "rsv_dl": "tb",
                    "rsv_sug3": "2",
                    "rsv_sug1": "2",
                    "rsv_sug7": "000",
                    "rsv_btype": "t",
                    "prefixsug": "httprunner",
                    "rsp": "1",
                    "inputT": "6648",
                    "rsv_sug4": "7252",
                    "rsv_sug": "2",
                    "bs": "httprunner 3",
                    "rsv_sid": "undefined",
                    "_ss": "1",
                    "clist": "",
                    "hsug": "httprunner 3\thttprunner",
                    "f4s": "1",
                    "csor": "10",
                    "_cr1": "40730"
                },
                "method": "GET",
                "cookies": {
                    "BIDUPSID": "EA49B0E234E0F93BBD3C0082A586CDEA",
                    "PSTM": "1619952293",
                    "BAIDUID": "C9FF25B24E5A3C59C96D61DB506725AB:FG=1",
                    "BD_UPN": "123253",
                    "BDORZ": "B490B5EBF6F3CD402E515D22BCDA1598",
                    "H_PS_PSSID": "33986_33819_33849_33759_33675_33607_26350_33996",
                    "H_PS_645EC": "9d65Kx91ldJ2V3LDLjZmstZTQ27dNOYAMJ%2B%2BoE9TlK6y2o%2BO7A7XdDS6Yus",
                    "delPer": "0",
                    "BD_CK_SAM": "1",
                    "PSINO": "5",
                    "BDSVRTM": "14",
                    "WWW_ST": "1620121549937"
                },
                "headers": {
                    "Host": "www.baidu.com",
                    "Connection": "keep-alive",
                    "Accept": "*/*",
                    "is_xhr": "1",
                    "X-Requested-With": "XMLHttpRequest",
                    "is_referer": "https://www.baidu.com/s?ie=utf-8&f=8&rsv_bp=1&rsv_idx=1&tn=baidu&wd=httprunner%203&fenlei=256&oq=httprunner%203&rsv_pq=86a39119000039fe&rsv_t=2b6c1PBdGIcDYzEKyW9BkMzeCPMYcfbqTSf%2FEDXZuefGUrmcy2q1pxhJ0NQ&rqlang=cn",
                    "User-Agent": "Mozilla/5.0 (Macintosh; Intel Mac OS X 10_16_0) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/85.0.4183.102 Safari/537.36",
                    "Sec-Fetch-Site": "same-origin",
                    "Sec-Fetch-Mode": "cors",
                    "Sec-Fetch-Dest": "empty",
                    "Referer": "https://www.baidu.com/s?ie=utf-8&f=8&rsv_bp=1&rsv_idx=1&tn=baidu&wd=httprunner&fenlei=256&oq=httprunner%2520%2526lt%253B&rsv_pq=86a39119000039fe&rsv_t=9d65Kx91ldJ2V3LDLjZmstZTQ27dNOYAMJ%2B%2BoE9TlK6y2o%2BO7A7XdDS6Yus&rqlang=cn&rsv_enter=0&rsv_dl=tb&rsv_sug3=2&rsv_sug1=2&rsv_sug7=000&rsv_btype=t&prefixsug=httprunner&rsp=1&inputT=6648&rsv_sug4=7252&rsv_sug=2",
                    "Accept-Encoding": "gzip, deflate, br",
                    "Accept-Language": "zh-CN,zh;q=0.9",
                    "Cookie": "BIDUPSID=EA49B0E234E0F93BBD3C0082A586CDEA; PSTM=1619952293; BAIDUID=C9FF25B24E5A3C59C96D61DB506725AB:FG=1; BD_UPN=123253; BDORZ=B490B5EBF6F3CD402E515D22BCDA1598; H_PS_PSSID=33986_33819_33849_33759_33675_33607_26350_33996; H_PS_645EC=9d65Kx91ldJ2V3LDLjZmstZTQ27dNOYAMJ%2B%2BoE9TlK6y2o%2BO7A7XdDS6Yus; delPer=0; BD_CK_SAM=1; PSINO=5; BDSVRTM=14; WWW_ST=1620121549937"
                }
            },
            "validate": [
                {
                    "eq": [
                        "status_code",
                        200
                    ]
                },
                {
                    "eq": [
                        "headers.Content-Type",
                        "text/html;charset=utf-8"
                    ]
                }
            ]
        }
    ]
}

以上转换出的pytest、yaml、json这3种格式的文件效果都是一样的,用hrun都可以运行,但是用pytest执行的话只可以运行.py的文件了。

九、 测试用例-结构解析

9.1、官方首推pytest格式

上篇文章我们知道了,httprunner可以支持三种格式的用例,分别是pytest、yaml和json。yaml和json是以前的版本所使用的用例格式,但是在3.x版本上,官方强烈建议使用的是pytest格式的用例。
image
上图是来自官方的用例格式关系图,可以看出来,httprunner再对于第三方导出的har文件进行了转换处理,有的人喜欢转换成json,有的人喜欢转换成yaml。但是最终,还是通过解析json格式的文件,生成pytest的python文件。
既然最后都是要生成pytest,那何不一步到位呢?哈哈,我想这就是官方推荐pytest格式的原因吧。
我还是挺喜欢的,因为我对于pytest使用的较多,那么接下来也是基于pytest格式的用例进行解析。

9.2、用例结构解析

录制生成的case很便捷,但是这并不是说,不需要我们做任何的改动了。在实践的过程中,我们仍然会根据我们实际项目的不同需求来对case作进一步的调整,所以彻底的了解case的构造尤为重要。
首先,我录制了一个百度搜索“httprunner”的一个请求,转换成pytest文件后如下:
image

可以看到:

  • 每个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的另一个原因吧,哈哈。
    image
    image

9.3 httprunner的用例结构与常见的用例的比较

补一个官方完整的一个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()
  1. httprunner中的testcase,其实说的就是上面的这一整个Python文件。
  2. teststeps列表中的Step,其实就是我自己编写case时候的一个个def test_xxx():pass。
  3. 每一个Step内部,依然是按照 传参——调用接口——断言,这样的过程来的。
    万变不离其宗,httprunner框架目前看起来,确实可以让编写更加的便捷、简洁,但是这只是目前从demo的过程中得到的结论,后面还需要落地实战才可以。

9.4、 测试用例-config

我们再一起来看,上面官方给出的例子

一、name(必填)

即用例名称,这是一个必填参数。测试用例名称,将显示在执行日志和测试报告中。比如,我在之前的百度搜索的case里,加入name。
image
运行后,在debug日志里,可以看的用例名称被展示出来。
image

二、base_url(选填)

其实这个配置一般在多环境切换中最常用。
比如你的这套测试用例在qa环境,uat环境都要使用,那么就可以把基础地址(举例http://demo.qa.com),设置进去。在后面的teststep中,只需要填上接口的相对路径就好了(举例 /get)。
这样的话,切换环境运行,只需要修改base_url即可。
image

三、variables(选填)

变量,这里可以存放一些公共的变量,可以在测试用例里引用。这里大家可以记住这个“公共”的词眼,因为在后面的Step中,还会有步骤变量。
比如说,我的接口有个传参是不变的,比如用户名username,而且后面的每个Step都会用到这个传参,那么username就可以放在config的公共变量里。
另外,Step里的变量优先级是比config里的变量要高的,如果有2个同名的变量的话,那么引用的时候,是优先引用步骤里的变量的。
image

四、verify(选填)

用来决定是否验证服务器TLS证书的开关。
通常设置为False,当请求https请求时,就会跳过验证。如果你运行时候发现抛错SSLError,可以检查一下是不是verify没传,或者设置了True。
image

五、export(选填)

导出的变量,主要是用于Step之间参数的传递。还是以上面的官方代码为例:
image
1.在config中配置export“foo3”这个变量。
2.在第一个Step中,.extract() 提取了"body.args.foo2"给变量“foo3”。
3.在第二个Step中,引用变量"foo3"。
像参数传递,提取这些点,会放在后面单独讲解,前面还是以熟悉框架为主。

9.5 、测试用例-teststeps-RunRequest

一、测试用例分层模型

一个testcase里(就是一个pytest格式的Python文件)可以有一个或者多个测试步骤,就是teststeps[]列表里的Step。

我的理解每一个Step就可以类比成pytest框架下的def test_xxx()的用例函数,在Step里通常都会要请求API完成测试,也可以调用其他测试用例来完成更多的需求。

可以来看下官方的测试用例逻辑图(2.x版本不同,3.x弃用了2.x的API概念):
image
可以看到,testsuite包含了testcase,testcase1需要依赖testcase2才可以完成,那么就可以在teststep12对其进行引用;而testcase2又依赖于testcase3,那么也可以在teststep22进行引用。
但是在整个testsuite下,这3个testcase都是相互独立的,可以独自运行。如果需要相互调用,则是在testcase内部去完成处理。

可能看起来有点绕,其实官方想表达的就是测试用例分层的一个思想:

测试用例(testcase)应该是完整且独立的,每条测试用例应该是都可以独立运行的
测试用例是测试步骤(teststep)的有序集合
测试用例集(testsuite)是测试用例的无序集合,集合中的测试用例应该都是相互独立,不存在先后依赖关系的;如果确实存在先后依赖关系,那就需要在测试用例中完成依赖的处理
其实这一点,在我们自己使用pytest框架编写测试用例的时候同样贯彻到了。为了自动化测试的稳定性和可维护性,每个测试用例之间相互独立是非常有必要的。

二、teststeps-RunRequest

先上一段Step的代码,结合下面的点对照着看:

    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")
        ),

从上面的代码可以看出,RunRequest的作用就是在测试步骤中请求API,并且可以对于API的响应结果进行提取、断言。

1.RunRequest(name)

RunRequest的参数名用于指定teststep名称,它将显示在执行日志和测试报告中。
image

2. .with_variables

用于存放变量,但是这里的是Step步骤里的变量,不同的Step的变量是相互独立的。所以对于多个Step都要使用的变量,我们可以放到config的变量里去。

另外,如果config和Step里有重名的变量,那么当你引用这个变量的时候,Step变量会覆盖config变量。
image

3. .method(url)

这里就是指定请求API的方法了,常用的get、post等等。如图所示,就是用的get方法,括号里的url就是要请求的地址了。

这里要注意的是,如果在config里设置了基础url,那么步骤里的url就只能设置相对路径了。
image

4. .with_params

这个就简单了,测接口不都得要传参么,对于params类型的传参,就放这就行了,key-value键值对的形式。对于body形式的传参,看后面。
image

5. .with_headers

同样,有header要带上的就放这里。
image

6. .with_cookies

需要带cookie的,可以用.with_cookies方法。
image

7. .with_data

对于body类型的传参,可以用.with_data。
image

8. .with_json

如果是json类型的body请求体,可以用.with_json。
image

9. .extract

这里就是要做提取操作了,使用.with_jmespath(jmes_path: Text, var_name: Text)。

这里是采用了JMESPath语言,JMESPath是JSON的查询语言,可以便捷的提取json中你需要的元素。

第一个参数是你的目标元素的jmespath表达式,第二个元素则是用来存放这个元素的变量,供有需要的引用。

这里不展开,后面单讲。
image

10. .validate

断言,我们测试最终就是要验证接口返回是否符合预期。

那在httprunner框架中,可以使用assert_XXX(jmes_path: Text, expected_value: Any)来进行提取和验证。

第一个参数还是jmespath表达式,第二个参数则是预期值。

assert_XXX这种方式相信用过自动化测试框架的都不会陌生,所以也非常容易上手。目前httprunner还是封装了非常丰富的断言方法的,相信可以满足绝大多数的需求了。
image

11. .upload

主要用于文件上传,以下提供一个案例:

 teststeps = [
        Step(
            RunRequest("/upc/oss/upload")
            .post("http://xxxxx/upc/oss/upload")
            .with_cookies(**{"ACCESS_TOKEN": "fa432c1dba0a443b9def0e66c6bd7736"})
            .upload(**{"file": "/Users/chenshifeng/Desktop/图片/d000baa1cd11728bcdde8185ccfcc3cec2fd2ca1.jpg", "title": "titlename"})
            .validate()
            .assert_equal("status_code", 200)
            .assert_equal('headers."Content-Type"', "application/json;charset=UTF-8")
            .assert_equal("body.success", True)
            .assert_equal("body.errCode", "0")
            .assert_equal("body.errMsg", "success")
        ),
    ]

9.6、测试用例-teststeps-RunTestCase

以前我在写接口自动化用例的时候,为了保证用例的独立性,需要在setUp里调用各种满足用例的一些前置条件,其中就不乏调用了其他测试用例中的方法。

而httprunner也是支持了这一项很重要的特性,通过RunTestCase对其他测试用例进行调用,并且还可以导出用例中你所需要的变量,来满足后续用例的的运行。

首先还是来看下RunTestCase的用法,然后再用实例去实践。下面是官方的一个演示代码。

import os
import sys

sys.path.insert(0, os.getcwd())

from httprunner import HttpRunner, Config, Step, RunRequest, RunTestCase

from examples.postman_echo.request_methods.request_with_functions_test import (
    TestCaseRequestWithFunctions as RequestWithFunctions,
)


class TestCaseRequestWithTestcaseReference(HttpRunner):
    config = (
        Config("request methods testcase: reference testcase")
        .variables(
            **{
                "foo1": "testsuite_config_bar1",
                "expect_foo1": "testsuite_config_bar1",
                "expect_foo2": "config_bar2",
            }
        )
        .base_url("https://postman-echo.com")
        .verify(False)
    )

    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")
        ),
    ]


if __name__ == "__main__":
    TestCaseRequestWithTestcaseReference().test_start()
  1. RunTestCase(name)
    这个参数呢还是一个名称,毕竟RunTestCase还是一个Step,这个名称同样会在日志和报告中显示。
    image
  2. .with_variables
    这个变量跟RunRequest里的用法一样。
  3. .call
    这里就是指定你要引用的testcase类名称了。
    image

这里需要特别说明下,导入的testcase需要起个别名,不能已test开头,会被系统识别成一个testcase

  1. .export
    可以指定要导出的变量,以供后续Step引用。
    可以看的.export()内部是一个列表[],这里可以用来导出多个变量。
    image

9.7、用例引用、变量传递

看到这里,对于httprunner已经有了一个大概的了解,现在想对于一些比较重要或者常用的功能,进行一些实践操作。
毕竟那谁说过,“纸上得来终觉浅,绝知此事要躬行。”
上一篇提到了RunTestCase,里面有2个重要的特征:

一个是在一个用例中引用另一个测试用例,另一个则是变量的导出与引用。

那就先来实践一下这2个货。
我用flask快速写了2个接口,以供在本地调用:

#!/usr/bin/python
# -*- coding: UTF-8 -*-
"""
@author:chenshifeng
@test_flask_demo.py
@time:2021/03/12
"""
from flask import Flask
from flask import request

app = Flask(__name__)


@app.route('/')
def hello_world():
    return 'Hello World!'


@app.route('/getUserName', methods=['GET'])
def get_user_name():
    if request.method == 'GET':
        return {
            "username": "chenshifeng",
            "age": "18",
            "from": "China",
        }


@app.route('/joinStr', methods=['GET'])
def str_join():
    if request.method == 'GET':
        str1 = request.args.get("str1")
        str2 = request.args.get("str2")
        after_join = str1 + " " + str2
        return {
            "result": after_join
        }


if __name__ == '__main__':
    app.run()

一共有2个接口:

  1. /getUserName,查询用户名,返回是我写死的字典。
  2. /joinStr,两个字符串拼接,返回的是拼接后的结果。

一、编写测试用例
根据之前学习过的,直接编写case,因为这个接口没有传参,cookie之类的,就省掉了,只是demo用。

  1. 接口:/getUserName
#!/usr/bin/python
# -*- coding: UTF-8 -*-
"""
@author:chenshifeng
@file:test_getUserName_demo.py
@time:2021/05/05
"""
from httprunner import HttpRunner, Config, Step, RunRequest, RunTestCase


class TestCaseRequestWithGetUserName(HttpRunner):
    config = (
        Config("test /getUserName")
        .base_url("http://localhost:5000")
        .verify(False)
    )

    teststeps = [
        Step(
            RunRequest("getUserName")
            .get("/getUserName")
            .validate()
            .assert_equal("body.username", "chenshifeng")
        ),

    ]


if __name__ == "__main__":
    TestCaseRequestWithGetUserName().test_start()

这里呢,步骤都有了,断言是验证返回的username字段值是不是“chenshifeng”,运行一下,可以看到测试通过。
image
2. 接口:/joinStr
这个接口就需要2个传参了,那么在Step里通过.with_params()来传参。

#!/usr/bin/python
# -*- coding: UTF-8 -*-
"""
@author:chenshifeng
@file:joinStr_demo_test.py
@time:2021/05/05
"""
from httprunner import HttpRunner, Config, Step, RunRequest, RunTestCase


class TestCaseRequestWithJoinStr(HttpRunner):
    config = (
        Config("test /joinStr")
        .base_url("http://localhost:5000")
        .verify(False)
    )

    teststeps = [
        Step(
            RunRequest("joinStr")
            .get("/joinStr")
            .with_params(**{"str1": "hello", "str2": "chenshifeng"})
            .validate()
            .assert_equal("body.result", "hello chenshifeng")
        ),

    ]


if __name__ == "__main__":
    TestCaseRequestWithJoinStr().test_start()

这里传入的参数分别是“hello”、“chenshifeng”,这个字符串在拼接的时候是加了一个空格的,所以断言的时候我预期的值是"hello chenshifeng"。
运行测试,可以看到测试通过。
image
二、testcase引用&变量传递
以上是2个分开的case,都可以分别正常运行。

假设,/joinStr接口的第二个参数,是依赖/getUserName接口的返回,那么现在这2个testcase之间就有了依赖关系。

那么在写/joinStr接口用例的时候,就需要去引用/getUserName的测试用例了,并且需要把/getUserName用例的变量导出来,/joinStr的测试用例传参时候使用。

  1. 首先,先修改/getUserName接口的case:
from httprunner import HttpRunner, Config, Step, RunRequest, RunTestCase


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", "chenshifeng")
        ),

    ]


if __name__ == "__main__":
    TestCaseRequestWithGetUserName().test_start()

关注注释部分的代码,一个是config里定义了这个要导出的变量,另一个是在Step中,讲目标值提取出来,赋值给这个变量。
2. 接下来,修改/joinStr接口的测试用例:

from httprunner import HttpRunner, Config, Step, RunRequest, RunTestCase
from testcases.test_getUserName_demo import TestCaseRequestWithGetUserName as RequestWithGetUserName   # 记得要导入引用的类


class TestCaseRequestWithJoinStr(HttpRunner):
    config = (
        Config("test /joinStr")
        .base_url("http://localhost:5000")
        .verify(False)
    )

    teststeps = [
        Step(
            RunTestCase("setUp getUserName")
            .call(RequestWithGetUserName)   # 导入后就可以调用了
            .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()

按照直接学习的内容,case已经修改好,现在运行/joinStr接口的测试用例,可以看到运行通过。
image
刚接触httprunner的这个pytest格式的语法时候,感觉还不习惯,但是你跟着实践走下来,发现习惯的还是很快的,快动手试试吧。

十、总结:运行testcase的几种方式

在之前的demo过程中,已经运行过testcase了,那这篇就也来汇总一下,运行case相关的知识点。

10.1、运行testcase的几种场景

  1. 运行单个case
    通常单个case的话我会在编辑器里用main方法运行,不过也可以用命令行运行,看你喜欢。
#!/usr/bin/python
# -*- coding: UTF-8 -*-
"""
@author:chenshifeng
@file:joinStr_demo_test.py
@time:2021/05/05
"""
from httprunner import HttpRunner, Config, Step, RunRequest, RunTestCase
from testcases.test_getUserName_demo import TestCaseRequestWithGetUserName as RequestWithGetUserName   # 记得要导入引用的类


class TestCaseRequestWithJoinStr(HttpRunner):
    config = (
        Config("test /joinStr")
        .base_url("http://localhost:5000")
        .verify(False)
    )

    teststeps = [
        Step(
            RunTestCase("setUp getUserName")
            .call(RequestWithGetUserName)   # 导入后就可以调用了
            .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()   # 这里

main方法里在类的后面调用test_start()方法即可。
命令行的话,就是直接在hrun后面加上case的路径,就可以运行了。

chenshifengdeMacBook-Pro:httprunner_demo chenshifeng$ hrun testcases/demo_testcase_request_test.py 
2021-05-05 01:04:33.386 | INFO     | httprunner.make:__make:512 - make path: /Users/chenshifeng/MyCode/PythonCode/httprunner_demo/testcases/demo_testcase_request_test.py
2021-05-05 01:04:33.387 | INFO     | httprunner.make:format_pytest_with_black:170 - format pytest cases with black ...
No Path provided. Nothing to do ?
2021-05-05 01:04:33.581 | INFO     | httprunner.cli:main_run:56 - start to run tests with pytest. HttpRunner version: 3.1.4
============================================================================= test session starts =============================================================================
platform darwin -- Python 3.9.2, pytest-6.2.2, py-1.10.0, pluggy-0.13.1
rootdir: /Users/chenshifeng/MyCode/PythonCode/httprunner_demo
plugins: metadata-1.11.0, html-2.1.1, allure-pytest-2.8.40
collected 1 item                                                                                                                                                              

testcases/demo_testcase_request_test.py .                                                                                                                               [100%]

============================================================================== 1 passed in 2.28s ==============================================================================
chenshifengdeMacBook-Pro:httprunner_demo chenshifeng$ hrun testcases/getUserName_demo_test.py 
2021-05-05 01:05:13.318 | INFO     | httprunner.make:__make:512 - make path: /Users/chenshifeng/MyCode/PythonCode/httprunner_demo/testcases/getUserName_demo_test.py
2021-05-05 01:05:13.318 | INFO     | httprunner.make:format_pytest_with_black:170 - format pytest cases with black ...
No Path provided. Nothing to do ?
2021-05-05 01:05:13.510 | INFO     | httprunner.cli:main_run:56 - start to run tests with pytest. HttpRunner version: 3.1.4
============================================================================= test session starts =============================================================================
platform darwin -- Python 3.9.2, pytest-6.2.2, py-1.10.0, pluggy-0.13.1
rootdir: /Users/chenshifeng/MyCode/PythonCode/httprunner_demo
plugins: metadata-1.11.0, html-2.1.1, allure-pytest-2.8.40
collected 1 item                                                                                                                                                              

testcases/getUserName_demo_test.py .                                                                                                                                    [100%]

============================================================================== 1 passed in 0.18s ==============================================================================
Sentry is attempting to send 0 pending error messages
Waiting up to 2 seconds
Press Ctrl-C to quit
  1. 运行多个case
    也可以选择运行多个case,hrun后面多个路径之间用空格隔开。
chenshifengdeMacBook-Pro:httprunner_demo chenshifeng$ hrun testcases/getUserName_demo_test.py  testcases/joinStr_demo_test.py 
2021-05-05 01:12:04.043 | INFO     | httprunner.make:__make:512 - make path: /Users/chenshifeng/MyCode/PythonCode/httprunner_demo/testcases/getUserName_demo_test.py
2021-05-05 01:12:04.044 | INFO     | httprunner.make:__make:512 - make path: /Users/chenshifeng/MyCode/PythonCode/httprunner_demo/testcases/joinStr_demo_test.py
2021-05-05 01:12:04.044 | INFO     | httprunner.make:format_pytest_with_black:170 - format pytest cases with black ...
No Path provided. Nothing to do ?
2021-05-05 01:12:04.231 | INFO     | httprunner.cli:main_run:56 - start to run tests with pytest. HttpRunner version: 3.1.4
============================================================================= test session starts =============================================================================
platform darwin -- Python 3.9.2, pytest-6.2.2, py-1.10.0, pluggy-0.13.1
rootdir: /Users/chenshifeng/MyCode/PythonCode/httprunner_demo
plugins: metadata-1.11.0, html-2.1.1, allure-pytest-2.8.40
collected 2 items                                                                                                                                                             

testcases/getUserName_demo_test.py .                                                                                                                                    [ 50%]
testcases/joinStr_demo_test.py .                                                                                                                                        [100%]

============================================================================== 2 passed in 0.22s ==============================================================================
Sentry is attempting to send 0 pending error messages
Waiting up to 2 seconds
Press Ctrl-C to quit
  1. 运行整个case文件夹
    通常在testcases这个目录下会存放我们的测试用例,那么也可以直接运行这个目录的路径,来运行下面所有的case。
chenshifengdeMacBook-Pro:httprunner_demo chenshifeng$ hrun testcases/
2021-05-05 01:13:05.177 | INFO     | httprunner.make:__make:512 - make path: /Users/chenshifeng/MyCode/PythonCode/httprunner_demo/testcases/
2021-05-05 01:13:05.183 | INFO     | httprunner.compat:ensure_testcase_v3:219 - ensure compatibility with testcase format v2
2021-05-05 01:13:05.186 | INFO     | httprunner.loader:load_dot_env_file:127 - Loading environment variables from /Users/chenshifeng/MyCode/PythonCode/httprunner_demo/.env
2021-05-05 01:13:05.187 | DEBUG    | httprunner.utils:set_os_environ:33 - Set OS environment variable: USERNAME
2021-05-05 01:13:05.187 | DEBUG    | httprunner.utils:set_os_environ:33 - Set OS environment variable: PASSWORD
2021-05-05 01:13:05.187 | INFO     | httprunner.make:make_testcase:349 - start to make testcase: /Users/chenshifeng/MyCode/PythonCode/httprunner_demo/testcases/demo_testcase_ref.yml
2021-05-05 01:13:05.199 | INFO     | httprunner.compat:ensure_testcase_v3:219 - ensure compatibility with testcase format v2
2021-05-05 01:13:05.200 | INFO     | httprunner.make:make_testcase:349 - start to make testcase: /Users/chenshifeng/MyCode/PythonCode/httprunner_demo/testcases/demo_testcase_request.yml
2021-05-05 01:13:05.200 | INFO     | httprunner.make:make_testcase:442 - generated testcase: /Users/chenshifeng/MyCode/PythonCode/httprunner_demo/testcases/demo_testcase_request_test.py
2021-05-05 01:13:05.201 | INFO     | httprunner.make:make_testcase:442 - generated testcase: /Users/chenshifeng/MyCode/PythonCode/httprunner_demo/testcases/demo_testcase_ref_test.py
2021-05-05 01:13:05.211 | INFO     | httprunner.compat:ensure_testcase_v3:219 - ensure compatibility with testcase format v2
2021-05-05 01:13:05.213 | INFO     | httprunner.make:make_testcase:349 - start to make testcase: /Users/chenshifeng/MyCode/PythonCode/httprunner_demo/testcases/demo_testcase_request.yml
2021-05-05 01:13:05.213 | INFO     | httprunner.make:format_pytest_with_black:170 - format pytest cases with black ...
reformatted /Users/chenshifeng/MyCode/PythonCode/httprunner_demo/testcases/demo_testcase_ref_test.py
reformatted /Users/chenshifeng/MyCode/PythonCode/httprunner_demo/testcases/demo_testcase_request_test.py
All done! ✨ ? ✨
2 files reformatted.
2021-05-05 01:13:05.687 | INFO     | httprunner.cli:main_run:56 - start to run tests with pytest. HttpRunner version: 3.1.4
============================================================================= test session starts =============================================================================
platform darwin -- Python 3.9.2, pytest-6.2.2, py-1.10.0, pluggy-0.13.1
rootdir: /Users/chenshifeng/MyCode/PythonCode/httprunner_demo
plugins: metadata-1.11.0, html-2.1.1, allure-pytest-2.8.40
collected 4 items                                                                                                                                                             

testcases/demo_testcase_ref_test.py .                                                                                                                                   [ 25%]
testcases/joinStr_demo_test.py .                                                                                                                                        [ 50%]
testcases/demo_testcase_request_test.py .                                                                                                                               [ 75%]
testcases/getUserName_demo_test.py .                                                                                                                                    [100%]

============================================================================== 4 passed in 3.93s ==============================================================================

10.2、运行YAML/JSON文件格式的case

如果运行YAML/JSON文件,同级文件夹下,就会生成对应的pytest的文件,文件名称的末尾会有_test。其实httprunner会先把它们转换为pytest格式的,再去运行。
所以,用httprunner 3.x版本的话,写case的话还是直接用pytest格式吧。

10.3、运行pytest格式的文件

对应pytest文件来说,用hrun或者pytest命令运行都是可以的。
因为hrun只是封装了pytest,所以pytest运行的所有参数,同样可以在hrun后面加。

十一、测试报告

11.1 HTML报告

Httprunner 安装之后自带 pytest-html插件,当你想生成 HTML测试报告时,可以添加命令参数--html

hrun testcases/demo_testcase_request.yml --html=reports/demo_report.html

--html=reports/demo_report.html中的reports/demo_report.html是测试报告的存放路径,没有带文件夹的时候会存放在命令运行的当前文件夹,此处是项目根目录

chenshifengdeMacBook-Pro:interface chenshifeng$  hrun testcases/demo_testcase_request.yml --html=reports/demo_report.html
2021-05-06 22:57:17.072 | INFO     | httprunner.make:__make:512 - make path: /Users/chenshifeng/MyCode/PythonCode/SFtest/interface/testcases/demo_testcase_request.yml
2021-05-06 22:57:17.083 | INFO     | httprunner.compat:ensure_testcase_v3:219 - ensure compatibility with testcase format v2
2021-05-06 22:57:17.087 | INFO     | httprunner.loader:load_dot_env_file:127 - Loading environment variables from /Users/chenshifeng/MyCode/PythonCode/SFtest/interface/.env
2021-05-06 22:57:17.088 | DEBUG    | httprunner.utils:set_os_environ:33 - Set OS environment variable: USERNAME
2021-05-06 22:57:17.088 | DEBUG    | httprunner.utils:set_os_environ:33 - Set OS environment variable: PASSWORD
2021-05-06 22:57:17.089 | INFO     | httprunner.make:make_testcase:349 - start to make testcase: /Users/chenshifeng/MyCode/PythonCode/SFtest/interface/testcases/demo_testcase_request.yml
2021-05-06 22:57:17.090 | INFO     | httprunner.make:make_testcase:442 - generated testcase: /Users/chenshifeng/MyCode/PythonCode/SFtest/interface/testcases/demo_testcase_request_test.py
2021-05-06 22:57:17.090 | INFO     | httprunner.make:format_pytest_with_black:170 - format pytest cases with black ...
reformatted /Users/chenshifeng/MyCode/PythonCode/SFtest/interface/testcases/demo_testcase_request_test.py
All done! ✨ ? ✨
1 file reformatted.
2021-05-06 22:57:17.375 | INFO     | httprunner.cli:main_run:56 - start to run tests with pytest. HttpRunner version: 3.1.4
============================================================================= test session starts =============================================================================
platform darwin -- Python 3.9.2, pytest-5.4.3, py-1.10.0, pluggy-0.13.1
rootdir: /Users/chenshifeng/MyCode/PythonCode/SFtest/interface
plugins: testconfig-0.2.0, metadata-1.11.0, html-2.1.1, allure-pytest-2.8.40
collected 1 item                                                                                                                                                              

testcases/demo_testcase_request_test.py .                                                                                                                               [100%]

--------------------------------- generated html file: file:///Users/chenshifeng/MyCode/PythonCode/SFtest/interface/reports/demo_report.html ----------------------------------
============================================================================== 1 passed in 6.97s ==============================================================================

生成的报告如下
image

这个报告大家了解一下即可,接下来着重说说大名鼎鼎的 allure 测试报告。

10.2 allure报告

HTTPrunner 集成了 pytest,所以 HTTPrunner v3.x可以使用pytest 的所有插件,包括测试报告插件,例如pytest-html 和alluer-pytest。
不过 HTTPrunner 默认并未安装 allure,你需要另外安装。
安装有两种方式:

  • 安装allure的 pytest 依赖库allure-pytest;
  • 安装 HTTPrunner的allure 依赖库 httprunner[allure]。

安装 allure-pytest:

pip3 install allure-pytest

安装 httprunnerallure

pip3 install "httprunner[allure]"

一旦allure-pytest 准备好,以下参数就可以与 hrun/pytest命令一起使用:

  • --alluredir=DIR: 生成 allure 报告到指定目录
  • --clean-alluredir: 如果指定目录已存在则清理该文件夹
  • --allure-no-capture:不要将 pytest 捕获的日志记录(logging)、标准输出(stdout)、标准错误(stderr)附加到报告中
    要使 Allure 侦听器能够在测试执行期间收集结果,只需添加--alluredir选项,并提供指向存储结果的文件夹路径。如:
 hrun testcases/ --alluredir=reports/allure_reports/ --clean-alluredir
 chenshifengdeMacBook-Pro:interface chenshifeng$ hrun testcases/ --alluredir=reports/allure_reports/ --clean-alluredir
2021-05-06 23:10:19.710 | INFO     | httprunner.make:__make:512 - make path: /Users/chenshifeng/MyCode/PythonCode/SFtest/interface/testcases/
2021-05-06 23:10:19.716 | INFO     | httprunner.compat:ensure_testcase_v3:219 - ensure compatibility with testcase format v2
2021-05-06 23:10:19.720 | INFO     | httprunner.loader:load_dot_env_file:127 - Loading environment variables from /Users/chenshifeng/MyCode/PythonCode/SFtest/interface/.env
2021-05-06 23:10:19.720 | DEBUG    | httprunner.utils:set_os_environ:33 - Set OS environment variable: USERNAME
2021-05-06 23:10:19.721 | DEBUG    | httprunner.utils:set_os_environ:33 - Set OS environment variable: PASSWORD
2021-05-06 23:10:19.721 | INFO     | httprunner.make:make_testcase:349 - start to make testcase: /Users/chenshifeng/MyCode/PythonCode/SFtest/interface/testcases/demo_testcase_ref.yml
2021-05-06 23:10:19.732 | INFO     | httprunner.compat:ensure_testcase_v3:219 - ensure compatibility with testcase format v2
2021-05-06 23:10:19.733 | INFO     | httprunner.make:make_testcase:349 - start to make testcase: /Users/chenshifeng/MyCode/PythonCode/SFtest/interface/testcases/demo_testcase_request.yml
2021-05-06 23:10:19.733 | INFO     | httprunner.make:make_testcase:442 - generated testcase: /Users/chenshifeng/MyCode/PythonCode/SFtest/interface/testcases/demo_testcase_request_test.py
2021-05-06 23:10:19.734 | INFO     | httprunner.make:make_testcase:442 - generated testcase: /Users/chenshifeng/MyCode/PythonCode/SFtest/interface/testcases/demo_testcase_ref_test.py
2021-05-06 23:10:19.744 | INFO     | httprunner.compat:ensure_testcase_v3:219 - ensure compatibility with testcase format v2
2021-05-06 23:10:19.744 | INFO     | httprunner.make:make_testcase:349 - start to make testcase: /Users/chenshifeng/MyCode/PythonCode/SFtest/interface/testcases/demo_testcase_request.yml
2021-05-06 23:10:19.745 | INFO     | httprunner.make:format_pytest_with_black:170 - format pytest cases with black ...
reformatted /Users/chenshifeng/MyCode/PythonCode/SFtest/interface/testcases/demo_testcase_ref_test.py
reformatted /Users/chenshifeng/MyCode/PythonCode/SFtest/interface/testcases/demo_testcase_request_test.py
All done! ✨ ? ✨
2 files reformatted.
2021-05-06 23:10:20.218 | INFO     | httprunner.cli:main_run:56 - start to run tests with pytest. HttpRunner version: 3.1.4
============================================================================= test session starts =============================================================================
platform darwin -- Python 3.9.2, pytest-5.4.3, py-1.10.0, pluggy-0.13.1
rootdir: /Users/chenshifeng/MyCode/PythonCode/SFtest/interface
plugins: testconfig-0.2.0, metadata-1.11.0, html-2.1.1, allure-pytest-2.8.40
collected 2 items                                                                                                                                                             

testcases/demo_testcase_ref_test.py .                                                                                                                                   [ 50%]
testcases/demo_testcase_request_test.py .                                                                                                                               [100%]

============================================================================== 2 passed in 5.17s ==============================================================================

reports/allure_reports/ 只会存储收集的测试结果并非完成的报告,还需要通过命令生成。

要在测试完成后查看实际报告,您需要使用Allure命令行实用程序从结果中生成报告。

allure serve reports/allure_reports/
 
chenshifengdeMacBook-Pro:interface chenshifeng$ allure serve reports/allure_reports/
Generating report to temp directory...
Report successfully generated to /var/folders/p0/3_7fwrvx6n3ftpfd4wjb01300000gn/T/6934992980047989302/allure-report
Starting web server...
2021-05-06 23:15:35.507:INFO::main: Logging initialized @7514ms to org.eclipse.jetty.util.log.StdErrLog
Server started at <http://192.168.12.105:63501/>. Press <Ctrl+C> to exit

此命令将在默认浏览器中显示你生成的报告。

image
关于allure的更多使用方法参考:https://www.cnblogs.com/feng0815/p/13792188.html
同时你也可以在 Jenkins 中安装 allure 报告插件,将结果与Jenkins 集成。

补充

2021.6.4进行如下补充:

1、辅助函数debugtalk.py

你是否会遇到这样的问题,对于一些需要实时变化的参数或要求唯一性校验的参数,在进行自动化时不知道怎么实时改变参数的值?
别怕,现在它来了,httprunner的辅助函数功能可以解决你的所有烦恼。
httprunner的辅助函数debugtalk.py在你生成脚手架时会自动生成在项目的根目录下,默认只有3个函数
image
我们可以根据自己的实际需要来增加函数,那么该如何调用的,非常简单,我们只需要在任何需要使用的地方通过${function()}方法调用即可,

举例说明,比如我在做接口自动化的过程中有个填写身份证的入参,且有唯一性校验,还有账号、姓名、手机号字段需要有唯一性校验,那么我就写的个随机生成身份证、生成随机数和随机生成手机号的函数

def get_random_number(digit: int):
    '''
    生成随机数
    Args:
        digit: 需要生成多少位的随机数

    Returns:所需要的随机数

    '''
    return ''.join([random.choice([str(i) for i in range(9)]) for i in range(digit)])

def create_ident_generator():
    '''随机生成身份证'''

    # 身份证号的前两位,省份代号
    sheng = (
        '11', '12', '13', '14', '15', '21', '22', '23', '31', '32', '33', '34', '35', '36', '37', '41', '42', '43',
        '44',
        '45', '46', '50', '51', '52', '53', '54', '61', '62', '63', '64', '65', '66')
    # 随机选择距离今天在7000到25000的日期作为出生日期(没有特殊要求我就随便设置的,有特殊要求的此处可以完善下)
    birthdate = (datetime.date.today() - datetime.timedelta(days=random.randint(7000, 25000)))
    # 拼接出身份证号的前17位(第3-第6位为市和区的代码,中国太大此处就偷懒了写了定值,有要求的可以做个随机来完善下;第15-第17位为出生的顺序码,随机在100到199中选择)
    ident = sheng[random.randint(0, 31)] + '0101' + birthdate.strftime("%Y%m%d") + str(random.randint(100, 199))

    # 前17位每位需要乘上的系数,用字典表示,比如第一位需要乘上7,最后一位需要乘上2
    coe = {1: 7, 2: 9, 3: 10, 4: 5, 5: 8, 6: 4, 7: 2, 8: 1, 9: 6, 10: 3, 11: 7, 12: 9, 13: 10, 14: 5, 15: 8, 16: 4,
           17: 2}
    summation = 0

    # for循环计算前17位每位乘上系数之后的和
    for i in range(17):
        summation = summation + int(ident[i:i + 1]) * coe[i + 1]  # ident[i:i+1]使用的是python的切片获得每位数字

    # 前17位每位乘上系数之后的和除以11得到的余数对照表,比如余数是0,那第18位就是1
    key = {0: '1', 1: '0', 2: 'X', 3: '9', 4: '8', 5: '7', 6: '6', 7: '5', 8: '4', 9: '3', 10: '2'}

    # 拼接得到完整的18位身份证号
    return ident + key[summation % 11]

def create_phone_generator():
    '''
    随机生成手机号
    Returns:

    '''
    # 第二位数字
    second = [3, 4, 5, 7, 8][random.randint(0, 4)]

    # 第三位数字
    third = {
        3: random.randint(0, 9),
        4: [5, 7, 9][random.randint(0, 2)],
        5: [i for i in range(10) if i != 4][random.randint(0, 8)],
        7: [i for i in range(10) if i not in [4, 9]][random.randint(0, 7)],
        8: random.randint(0, 9),
    }[second]

    # 最后八位数字
    suffix = random.randint(9999999, 100000000)

    # 拼接手机号
    return "1{}{}{}".format(second, third, suffix)

然后再脚本中直接调用这几个个函数即可
image
运行,查看结果
image
可以看出,每次都根据函数生成的不同的值。

2、全局变量.env

httprunner脚手架会默认创建创建.env文件,将需要设置为环境变量或全局变量的值,存储在.env中
它与debugtalk.py文件一样,存放在项目跟目录下。
然后我们可以通过${ENV(VariableName)}调用即可
举例说明,下图是我的.env文件存放的常量
image
如上图。我存放了项目的基本url和登陆cookie,其他cookie是每次运行登陆脚本自动写进去的,然后在所需要的地方直接调用,当然,你也可以在所需要cookie的地方直接调佣登陆函数 ,看你喜欢那种方法了。
然后在所需要的地方调用
image