httprunner3.x的简介及使用

HttpRunner简介


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

相关文档

有必要先看一下httprunner2.x文档,了解一下httprunner的演进过程

安装


支持python 3.6+

pip3 install httprunner
  • 升级
pip3 install -U httprunner
  • 校验
$ httprunner -V 
3.1.4

常用命令

  • 使用-h或--help查看命令的使用方式
$ httprunner -h
usage: httprunner [-h] [-V] {run,startproject,har2case,make} ...

One-stop solution for HTTP(S) testing.

positional arguments:
  {run,startproject,har2case,make}
                        sub-command help
    run                 Make HttpRunner testcases and run with pytest.
    startproject        Create a new project with template structure.
    har2case            Convert HAR(HTTP Archive) to YAML/JSON testcases for
                        HttpRunner.
    make                Convert YAML/JSON testcases to pytest cases.

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

关于位置参数的命令的进一步使用方式仍可使用-h查看,如 httprunner run -h

  • httprunner startproject
  • httprunner run:等价于hrun
  • har2case:也可使用httprunner har2case

快速开始


官方参考示例

  1. 生成脚手架httprunner startproject demo
  2. 运行用例hrun demohttprunner run demo

脚手架

  • 先来看下脚手架目录结构
$ tree demo
demo  # 项目目录
├── debugtalk.py # (可选)项目中可能需要的逻辑运算辅助函数等
├── har # 抓包导出的har文件
├── reports # 测试报告
└── testcases # 测试用例
    ├── demo_testcase_ref.yml   # 支持yaml或json或py文件
    └── demo_testcase_request.yml

这只是演示示例的一个简单结构,实际项目中可以使用httprunner startproject创建脚手架,然后根据项目需要添加其他目录,如数据驱动data目录来存放csv文件,.env文件存储项目环境变量或敏感信息,testsuites目录用于测试用例集存放等

  • 一个完整的项目目录
demo
├── .env
├── data
│   ├── account.csv
│   └── vip.csv
├── debugtalk.py
├── har
├── logs
│   └── 2aad97e8-2c00-46a6-8897-e129e0d741be.run.log
├── reports
│   ├── assets
│   │   └── style.css
│   └── demo.html
├── testcases
│   ├── __init__.py
│   ├── demo_testcase_request.yml
│   ├── demo_testcase_request_test.py
└── testsuites
    └── suite.yml

用例生成


抓包并导出har文件

  • har文件夹用于存放抓包导出的har文件
  • 使用fiddler或charles抓包,并选择需要的session导出为.har文件放于har文件夹

用例生成命令har2case

  1. 抓取http://httpbin.org/get并导出放于har文件夹如命名为httpbin_get.har
  2. 执行如下命令
$ har2case har/httpbin_get.har

则在har文件夹中生成如下测试用例httpbin_get_test.py

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


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


class TestCaseHttpbinGet(HttpRunner):

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

    teststeps = [
        Step(
            RunRequest("/get")
            .get("http://httpbin.org/get")
            .with_headers(
                **{
                    "Host": "httpbin.org",
                    "Cache-Control": "max-age=0",
                    "DNT": "1",
                    "Upgrade-Insecure-Requests": "1",
                    "User-Agent": "Mozilla/5.0 (Macintosh; Intel Mac OS X 11_2_2) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/89.0.4389.90 Safari/537.36",
                    "Accept": "text/html,application/xhtml+xml,application/xml;q=0.9,image/avif,image/webp,image/apng,*/*;q=0.8,application/signed-exchange;v=b3;q=0.9",
                    "Accept-Encoding": "gzip, deflate",
                    "Accept-Language": "zh-CN,zh;q=0.9,en;q=0.8",
                    "Connection": "keep-alive",
                }
            )
            .validate()
            .assert_equal("status_code", 200)
            .assert_equal("body.origin", "218.82.206.141")
            .assert_equal("body.url", "http://httpbin.org/get")
        ),
    ]


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

用例生成的补充说明

  1. HttpRunner v3.x 支持3种用例格式:pytest、YAML和JSON
  2. HttpRunner 3.x中har2case命令默认生成pytest用例(如上示例,即不添加任何参数),这种用例的编写更方便使用IDE的补全(链式调用和语法提示),编写起来更方便,推荐使用
  3. 同样也可以使用YAML/JSON格式的用例(添加参数)使用如下命令,不推荐
har2case har/httpbin_get.har -2y  # 或--to-yml
har2case har/httpbin_get.har -2j  # 或--to-json
  1. YAML/JSON格式用例和pytest用例其实是等价的,使用httprunner makehmake命令转换成pytest用例
httprunner make har/httpbin_get.yml
httprunner make har/httpbin_get.json

用例分层及用例编写


用例分层

  • 每个测试步骤teststep对应一个API请求其他测试用例的引用,描述单次接口测试的全部内容,包括发起接口请求、解析响应结果、检验结果等
  • 每个测试用例testcase都有1个或多个测试步骤teststep,测试用例是测试步骤的有序集合,测试用例应该是完整且独立的,每条测试用例应该是都可以独立运行的
  • 测试用例集testsuit是测试用例testcase的无序集合,集合中的测试用例应该都是相互独立,不存在先后依赖关系的

测试用例

这里仅介绍pytest用例的编写。每一个测试用例都是HttpRunner的子类,必须包含两个属性config及teststeps

# HttpRunner部分源码
class HttpRunner(object):
    config: Config
    teststeps: List[Step]
......

config: Config

# Config部分源码
class Config(object):
    def __init__(self, name: Text):
        self.__name = name
        self.__variables = {}
        self.__base_url = ""
        self.__verify = False
        self.__export = []
        self.__weight = 1

        caller_frame = inspect.stack()[1]
        self.__path = caller_frame.filename
......
  • name:测试用例的名称,将在log和报告中展示
  • base_url(可选):如果base_url被指定,测试步骤中的url只能写相对路径。当你要在不同环境下测试时,这个配置非常有用
  • variables(可选):定义的全局变量,作用域为整个用例。每个测试步骤都可以引用config variables。但step variables 优先级高于 config variables
  • verify(可选):指定是否验证服务器的TLS证书
  • export(可选的):指定输出的测试用例变量,当一个测试用例在另一个测试用例的步骤中被引用时,config export将被提取并在随后的测试步骤中使用

teststeps: List[Step]

# Step部分源码
class Step(object):
    def __init__(
        self,
        step_context: Union[
            StepRequestValidation,
            StepRequestExtraction,
            RequestWithOptionalArgs,
            RunTestCase, #
            StepRefCase, #
        ],
    ):
        self.__step_context = step_context.perform()
  • RunRequest(name)
    在测试步骤step中对api发起请求,并对响应做一些校验或提取

    • name:用来定义测试步骤 name,将出现在log和测试报告中。
    • .with_variables:测试步骤中定义的变量,作用域为当前测试步骤;测试步骤中的变量,会覆盖config variables中的同名变量
    • .method(url):设置http方法,与requests.request 中的method和url参数对应;如果在config中设置了base_url,url只需要相对路径path
    • .with_params:设置请求的query string,与requests.request中的params参数对应
    • .with_headers:设置请求的headers,与requests.request中的headers参数对应
    • .with_cookies:设置请求的cookies,与requests.request中的cookies参数对应
    • .with_data:设置请求的body,与requests.request中的data参数对应
    • .with_json:设置请求的json格式body,与requests.request中的json参数对应

    关于requests相关参数可参考我之前的文章Python Requests 库的使用官方文档

    • extract:从请求的响应结果中提取参数,并保存到参数变量中(例如登陆账号后的用户token),后续测试用例可通过$token的形式进行引用
      • with_jmespath(jmes_path: Text, var_name: Text):这里使用的是jmespath来提取json response body的内容,关于jmespath的使用参看之前的文章jemespath的使用官方文档;var_name存储提取出来的值,可用于之后的测试步骤
    • validate:测试用例中定义的结果校验项,作用域为当前测试用例,用于实现对当前测试用例运行结果的校验
      • assert_XXX(jmes_path: Text, expected_value: Any, message: Text = ""):同样使用jemespath来提取响应中需要校验的字段值,并与expected_value进行比较。message用于描述断言失败原因
      • assert_xxx相关方法可参看官方api或源码(IDE补全查看)
  • RunTestCase(name)
    在测试步骤中引用另一个测试用例

    • .with_variables:同RunRequest中的.with_variables
    • .call:指定引用的测试用例的(可调用对象)
    • .export:从引用的测试用例中提取的变量,该变量在后面的test steps中可以引用

在测试用例和测试步骤层面还提供hook机制来定制更复杂的场景,这里不详细介绍,请参考官方文档

variables优先级

从上面的测试用例的编写说明中,可以在不同的位置配置变量。可以使用不同的变量名来避免混淆,也可以 使用相同的变量名(如果必须的话),则但相同的变量变量名遵循如下的优先级

  • 在测试用例testcase中:
  1. step variables > extracted variables
  2. parameter variables > config variables
  3. extracted variables > parameter variables > config variables
  4. config variables 优先级最低
  • 在测试用例集testsuit中:
    testcase variables > export variables > testsuite config variables > 被引用的testcase config variables

debugtalk.py


  • 每个项目有且仅有一个该文件
  • 存放自定义python函数

在测试用例中引用

  • ${func(*arges, **kwargs)}

.env


  • .env文件,项目根目录
  • 用于敏感数据信息存放,存储采用name=value的格式

在测试用例中引用

  • ${ENV(name)}
  • 还可以在debugtalk.py 中通过 Python 内置的函数 os.environ 对环境变量进行引用,然后再实现处理逻辑,最后在用例中通过${func(*arges, **kwargs)}来引用

参数化数据驱动


  • 可在测试用例testcase或测试用例集testsuite中使用参数化数据驱动
from httprunner import Parameters
print(Parameters)
------输出结果---------
<function parse_parameters at 0x1078a4790>
# Parameters 部分源码
from httprunner.parser import parse_parameters as Parameters

def parse_parameters(parameters: Dict,) -> List[Dict]:
""" parse parameters and generate cartesian product.

    Args:
        parameters (Dict) parameters: parameter name and value mapping
            parameter value may be in three types:
                (1) data list, e.g. ["iOS/10.1", "iOS/10.2", "iOS/10.3"]
                (2) call built-in parameterize function, "${parameterize(account.csv)}"
                (3) call custom function in debugtalk.py, "${gen_app_version()}"

    Returns:
        list: cartesian product list

    Examples:
        >>> parameters = {
            "user_agent": ["iOS/10.1", "iOS/10.2", "iOS/10.3"],
            "username-password": "${parameterize(account.csv)}",
            "app_version": "${gen_app_version()}",
        }
        >>> parse_parameters(parameters)

    """

脚本中直接指定参数列表

  • 适合参数列表比较小的情况
  • Parameters函数参数为字典,且字典的value值为list
  • 字典的key如果是多个具有关联性的参数,需要将其定义在一起,并采用短横线(-)进行链接
  • 在测试用例或测试用例集中使用$var_name来引用
# 例1
from httprunner import Parameters
print(Parameters({"user_agent": ["iOS/10.1", "iOS/10.2", "iOS/10.3"]}))
------输出结果-------
[{'user_agent': 'iOS/10.1'}, {'user_agent': 'iOS/10.2'}, {'user_agent': 'iOS/10.3'}]

# 例2
from httprunner import Parameters
print(Parameters({"username-password": [['jerry', '12345a'], ['tom', '12345b']]}))
-------输出结果--------
[{'username': 'jerry', 'password': '12345a'}, {'username': 'tom', 'password': '12345b'}]

引用CSV文件

  • 适合数据量比较大的情况
  • Parameters函数参数为字典,value值为${parameterize(path/to/csv_file)}
  • Parameters函数参数字典的key值(多个用短横线(-)分割)必须包含在CSV 文件中第一行的参数名称中,顺序可以不一致,参数个数也可以不一致

在data/account.csv中输入内容如下:

username,password
jerry,12345a
tom,12345b

CSV 文件中的第一行必须为参数名称,从第二行开始为参数值,每个(组)值占一行;具有多个参数,则参数名称和数值的间隔符需实用英文逗号

from httprunner import Parameters
print(Parameters({"username-password": "${parameterize(data/account.csv)}"}))
---------输出结果---------
[{'username': 'jerry', 'password': '12345a'}, {'username': 'tom', 'password': '12345b'}]

在debugtalk.py中自定义的函数生成参数列表

  • 该种方式最为灵活,可通过自定义 Python 函数实现任意场景的数据驱动机制,也可用于动态生成参数列表
  • 在自定义函数中,生成的参数列表必须为 list of dict 的数据结构,该设计主要是为了与 CSV 文件的处理机制保持一致
  • Parameters函数的字典参数key值(多个则用短横线(-)分割)必须与自定义函数返回的list中的dict key值相呼应(与CSV 文件的处理机制保持一致)

在debugtalk.py定义如下函数

def get_user_id():
    return [
        {"user_id": 1001},
        {"user_id": 1002},
        {"user_id": 1003},
        {"user_id": 1004}
    ]
from httprunner import Parameters
print(Parameters({"user_id": "${get_user_id()}"}))
--------输出结果--------
[{'user_id': 1001}, {'user_id': 1002}, {'user_id': 1003}, {'user_id': 1004}]

在测试用例中使用参数化

在官方文档中没有详细的说明文档,可参看github中的的例子github examples

  • 使用@pytest.mark.parametrize实现
# NOTE: Generated By HttpRunner v3.1.4
# FROM: request_methods/request_with_parameters.yml


import pytest
from httprunner import Parameters


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


class TestCaseRequestWithParameters(HttpRunner):
    @pytest.mark.parametrize(
        "param",
        Parameters(
            {
                "user_agent": ["iOS/10.1", "iOS/10.2"],
                "username-password": "${parameterize(request_methods/account.csv)}",
                "app_version": "${get_app_version()}",
            }
        ),
    )
    def test_start(self, param):
        super().test_start(param)

    config = (
        Config("request methods testcase: validate with parameters")
        .variables(**{"app_version": "f1"})
        .base_url("https://postman-echo.com")
        .verify(False)
    )

    teststeps = [
        Step(
            RunRequest("get with params")
            .with_variables(
                **{
                    "foo1": "$username",
                    "foo2": "$password",
                    "sum_v": "${sum_two(1, $app_version)}",
                }
            )
            .get("/get")
            .with_params(**{"foo1": "$foo1", "foo2": "$foo2", "sum_v": "$sum_v"})
            .with_headers(**{"User-Agent": "$user_agent,$app_version"})
            .extract()
            .with_jmespath("body.args.foo2", "session_foo2")
            .validate()
            .assert_equal("status_code", 200)
            .assert_string_equals("body.args.sum_v", "${sum_two(1, $app_version)}")
        ),
    ]


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

运行测试用例


  • 运行指定路径的用例:hrun path/to/testcase1
  • 指定多个测试用例路径执行:hrun path/to/testcase1 path/to/testcase2
  • 运行测试项目中所有测试用例(指定测试项目的绝对路径):hrun path/to/testcase_folder/
  • hrun包装了pytest,因此pytest的功能都可以在hrun中使用
$ hrun -h  # 查看帮助文档及详细的参数说明

测试报告reports


这里仅介绍allure测试报告,内置html报告请参考官方文档

allure测试报告

  • 安装allure-pytest,使用pip install allure-pytest
  • 安装好allure-pytest后就可以在运行用例时添加如下参数
    • --alluredir=DIR: 指定目录生成报告
    • --clean-alluredir: 清理alluredir文件夹
    • --allure-no-capture: 不将pytest捕获的日志记录/stdout/stderr附加到测试报告

更多使用方式可参看之前的文章pytest 生成测试报告或官方文档allure-pytest

扩展阅读