httprunner4详解

httpruuner官方文档:https://httprunner.com/docs/introduction/overview/

1.Httprunner环境搭建

HttpRunner v4.0 同时采用了 Golang/Python 两种编程语言,底层会有两套相对独立的执行引擎,兼具 Golang 的高性能和 pytest 的丰富生态。

方式一:直接部署(以Windows系统为例)

你可以在 GitHub Releases 页面中,自行选择版本进行下载,下载地址:https://github.com/httprunner/httprunner/releases,下载完成后,解压到自己电脑任意目录, 如下:D:\soft\httpruner_exe

 

将解压的目录,添加到系统环境变量:在「我的电脑=>属性=>高级系统设置=>环境变量」配置中,在 PATH 下新增系统变量,将 解压目录写入 PATH。配置成功后cmd输入hrp,结果如下:

安装配置完成后能使用hrp命令

方式二:安装依赖包(开发者模式)

HttpRunner 除了可以作为命令行工具提供给用户进行使用,还可以作为库函数,供开发者调用进行二次开发。

安装httprunner库:

pip install -i https://pypi.tuna.tsinghua.edu.cn/simple httprunner #安装httprunner,安装后,就可以使用hrun命令
pip install har2case#用于将har文件转化成yaml/json/pytest测试用例

安装完成后,使用以下命令检验

C:\Users>har2case -V
0.3.1
C:\Users>httprunner -V
v4.3.5

安装Allure命令行:下载Allure命令行,下载地址:https://github.com/allure-framework/allure2/releases/,将下载到本地的allure压缩包解压到指定目录,并添加相应的环境变量Path=D:\Program Files\Allure\allure-2.16.0\bin,在命令行中输入allure,不出现报错即可

安装allure-pytest插件

pip install -i https://pypi.tuna.tsinghua.edu.cn/simple allure-pytest#安装python的allure插件,可生成漂亮的测试报告

>>>待续

2. Httprunner快速上手

2.1创建脚手架

 使用命令:hrp startproject dmo

 新建项目的层级目录如下:

2.2 录制脚本,导出.har文件

使用fiddler用录制 Gitee上的开源项目:http://erp2.hzb-it.com/为例:

step1:打开fidddler设置过滤域名(erp2.hzb-it.com)、过滤js、css静态文件等[    使用正则表达式:REGEX:(?insx)/[^\?/]*\.(css|ico|jpg|png|gif|bmp|wav|js|jpeg)(\?.*)?$    ],设置如下图:

 设置后,点击Actions->Run Fliterset now,设置的过滤器正式生效

Step2:抓取一条业务用例: 登录—>组织和人员—>职位管理—>新增职位为:测试工程师

Step3:点击File ->Exprot Sessions ->HTTPArchive v1.2 ->将导出的.var文件放到项目的har目录

2.3 har文件转成测试用例

方式一:使用hrp命令转换

 (1)使用命令,将目标目录har下的.har文件转成 yaml文件

hrp convert --from-har E:\work5-2\HttpRunner_demo\har\test_login.har --to-yaml --output-dir E:\work5-2\HttpRunner_demo\testcases

注意:如果不标注--from-har会失败

 (2)使用命令,将目标目录testcase下的.yaml文件转成 json文件

hrp convert --from-yaml E:\work5-2\HttpRunner_demo\testcases\test_login_test.yaml --to-json --output-dir E:\work5-2\HttpRunner_demo\testcases

 (3)使用命令,将目标目录testcase下的.json文件转成 pytest文件

hrp convert --from-har E:\work5-2\HttpRunner_demo\har\test_login.har --to-pytest --output-dir E:\work5-2\HttpRunner_demo\testcases

方式二:使用har2case转换

使用har2case可以直接将har文件转换为yaml/json格式的测试用例

har2case -h
usage: har2case [-h] [-V] [--log-level LOG_LEVEL] [-2y] [-fmt FMT_VERSION] [--filter FILTER] [--exclude EXCLUDE]
                [har_source_file]

Convert HAR(HTTP Archive) to YAML/JSON testcases for HttpRunner.

positional arguments:
  har_source_file       Specify HAR source file

options:
  -h, --help            show this help message and exit
  -V, --version         show version
  --log-level LOG_LEVEL
                        Specify logging level, default is INFO.
  -2y, --to-yml, --to-yaml
                        Convert to YAML format, if not specified, convert to JSON format by default.
  -fmt FMT_VERSION, --format FMT_VERSION
                        Specify YAML/JSON testcase format version, v2 corresponds to HttpRunner 2.2.0+.
  --filter FILTER       Specify filter keyword, only url include filter string will be converted.
  --exclude EXCLUDE     Specify exclude keyword, url that includes exclude string will be ignored, multiple keywords
                        can be joined with '|'

方式三:使用httprunner 的make命令转换

 如果直接使用hrp命令转换yaml/json为pytest格式失败,可以使用httprunner make命令转换为pytest测试用例

httprunner make E:\work5-2\HttpRunner_demo\testcases\test_login_test.yaml

 2.4 使用pytest 运行测试用例

import pytest
import os
if __name__=="__main__":

    pytest.main(['-s', '-v','./testcases/test_login_test_test.py','--alluredir', './report/tmp', "--clean-alluredir"])
    os.system(r"allure generate ./report/tmp -o ./report/html --clean")

 2.5  v4 版本的 Go & Python 功能对比

3. httprunner详解

3.1 测试用例

一条测试用例(testcase)应该是为了测试某个特定的功能逻辑而精心设计的,并且至少包含如下几点:

  • 明确的测试目的(achieve a particular software testing objective)
  • 明确的输入数据(inputs)
  • 明确的运行环境(execution conditions)
  • 明确的测试步骤描述(testing procedure)
  • 明确的预期结果(expected results)

按照上述的测试用例定义,HttpRunner 的测试用例应该保证是完整并且可以独立运行的。

从测试用例的组成结构来看,一个测试用例可以分为「测试脚本」和「测试数据」两部分:

  • 测试脚本:重点是描述测试的业务功能逻辑,包括预置条件、测试步骤、预期结果等,并且可以结合辅助函数(debugtalk.go/debugtalk.py)实现复杂的运算逻辑
  • 测试数据:重点是对应测试的业务数据逻辑,例如数据驱动文件中的定义的 UUID、用户名等等,以及环境配置文件中定义的 base_url 环境变量等等

3.2 httprunner的YAML/JSON格式测试用例手工编写

httprunner 4.x 版本,YAML/JSON 格式用例(testcase)结构延续了之前的config 和 teststeps 两个部分

3.1.2 config配置部分

config的modle定义如下:

class TConfig(BaseModel):
    name: Name
    verify: Verify = False
    base_url: BaseUrl = ""
    # Text: prepare variables in debugtalk.py, ${gen_variables()}
    variables: Union[VariablesMapping, Text] = {}
    parameters: Union[VariablesMapping, Text] = {}
    # setup_hooks: Hooks = []
    # teardown_hooks: Hooks = []
    export: Export = []
    path: Text = None
    # configs for other protocols
    thrift: TConfigThrift = None
    db: TConfigDB = TConfigDB()
Tconfig

每个测试用例都应该有一个config部分,您可以在其中配置测试用例级别的设置,有以下属性

属性名称      是否必填        作用
name          必填         指定测试用例名称。这将显示在执行日志和测试报告中。
base_url      可选         如果base_url指定,则 teststep 中的 url 可以设置相对路径部分
verify        可选         https请求时,是否校验证书,默认True,忽略证书校验可以设置为False
headers       可选         公共请求头部
variables     可选    指定测试用例的公共变量。每个测试步骤都可以引用未在步骤变量中设置的配置变量。换句话说,步骤变量比配置变量具有更高的优先级。
export        可选    (早期版本用的output)指定导出的测试用例会话变量,把变量暴露出来,设置为全局变量
parameters    可选    参数化设置,对整个文件生效

#除了上面的一些自动化会用到的参数,4.x 版本新增了一些关键字
属性名称     是否必填         作用
parameters_setting    可选    配置参数驱动的具体策略
think_time            可选    设置思考时间,性能测试用到
websocket             可选    设置 WebSocket 断开重连的最大次数和间隔等(todo)
weight                可选    性能测试用到,分配给当前测试用例的虚拟用户权重
environs              可选    配置环境变量(如果未指定则会从 .env 文件导入)
path                  可选    当前测试用例所在路径(通常不需要手工填写)

config部分示例

config:
    name: "request methods testcase with functions"
    variables:
        foo1: config_bar1
        foo2: config_bar2
        expect_foo1: config_bar1
        expect_foo2: config_bar2
    headers:
        User-Agent: ${get_user_agent()}
    verify: False
    export: ["foo3"]
config_demo.yaml

3.1.2 teststeps测试步骤部分

每个用例可以有多个测试步骤,每个步骤可以看成是一个接口的请求,发送 http 协议接口,可以用到request 关键字,相关参数和requests 库的参数完全一致。

teststep定义模型如下:

class TStep(BaseModel):
    name: Name
    request: Union[TRequest, None] = None
    testcase: Union[Text, Callable, None] = None
    variables: VariablesMapping = {}
    setup_hooks: Hooks = []
    teardown_hooks: Hooks = []
    # used to extract request's response field
    extract: VariablesMapping = {}
    # used to export session variables from referenced testcase
    export: Export = []
    validators: Validators = Field([], alias="validate")
    validate_script: List[Text] = []
    retry_times: int = 0
    retry_interval: int = 0  # sec
    thrift_request: Union[TThriftRequest, None] = None
    sql_request: Union[TSqlRequest, None] = None
View Code

测试步骤 teststep 常用的一些基本关键字

#测试步骤 teststep 常用的一些基本关键字
测试步骤类型    含义
name        步骤名称
request    用于发起 HTTP 请求的步骤类型
api        用于引用 API 的步骤类型
testcase   用于引用其他测试用例的步骤类型
#每个步骤可以加变量,前置/后置,以及 提取和校验相关操作
测试步骤类型         作用    适用的测试步骤
variables         局部变量    通用
setup_hooks       前置函数    request/api/websocket
teardown_hooks    后置函数    request/api/websocket
extract           参数提取    request/api/websocket
validate          结果校验    request/api/websocket
export            导出变量    testcase
#httprunner4.x 版本新增的一些关键字
测试步骤类型       含义
transaction       用于定义一个事务
rendezvous        集合点
think_time        思考时间
websocket         于发起 WebSocket 请求的步骤类型

teststep部分示例

teststeps:
-
    name: get with params
    variables:
        foo1: ${ENV(USERNAME)}
        foo2: bar21
        sum_v: "${sum_two_int(10000000, 20000000)}"
    request:
        method: GET
        url: $base_url/get
        params:
            foo1: $foo1
            foo2: $foo2
            sum_v: $sum_v
    extract:
        foo3: "body.args.foo2"
    validate:
        - eq: ["status_code", 200]
        - eq: ["body.args.foo1", "debugtalk"]
        - eq: ["body.args.sum_v", "30000000"]
        - eq: ["body.args.foo2", "bar21"]
-
    name: post raw text
    variables:
        foo1: "bar12"
        foo3: "bar32"
    request:
        method: POST
        url: $base_url/post
        headers:
            Content-Type: "text/plain"
        body: "This is expected to be sent back as part of response body: $foo1-$foo2-$foo3."
    validate:
        - eq: ["status_code", 200]
        - eq: ["body.data", "This is expected to be sent back as part of response body: bar12-$expect_foo2-bar32."]
-
    name: post form data
    variables:
        foo2: bar23
    request:
        method: POST
        url: $base_url/post
        headers:
            Content-Type: "application/x-www-form-urlencoded"
        body: "foo1=$foo1&foo2=$foo2&foo3=$foo3"
    validate:
        - eq: ["status_code", 200]
        - eq: ["body.form.foo1", "$expect_foo1"]
        - eq: ["body.form.foo2", "bar23"]
        - eq: ["body.form.foo3", "bar21"]
teststep_demo.yaml

3.3 变量声明与引用

在 HttpRunner 中,支持变量声明(variables)和引用($var 或 ${var} )的机制。
在 config 和 step 中均可以通过 variables 关键字定义变量,然后在测试步骤中可以通过 $变量名称的方式引用变量。
区别在于

    • 在 config 中定义的变量为全局的,整个测试用例(testcase)的所有地方均可以引用;
    • 在 step 中定义的变量作用域仅局限于当前测试步骤(teststep)

3.3.1 config 全局变量

在config下声明变量(variables)是全局变量,这样就在整个.yml文件生效

config:
    name: logincase
    variables:
        user: test
        psw: "123456"

通过 ${} 或 $ 的形式来引用变量, 例如:$var 或 ${var}

config:
  name: 引用变量
  variables:
    username: test123

teststeps:
-
  name: post
  request:
    method: POST
    url: http://httpbin.org/post
    json:
      username: ${username}
      password: "123456"
  validate:
    - eq: [status_code, 200]

3.3.2 teststeps局部变量

如下用例有2个步骤,其中第二个步骤post请求设置了局部变量, 那么它的作用范围只在第二个步骤中生效。

config:
  name: 引用变量

teststeps:
-
  name: get
  request:
    method: GET
    url: http://httpbin.org/get
  validate:
    - eq: [status_code, 200]
-
  name: post
  variables:
    username: test123
  request:
    method: POST
    url: http://httpbin.org/post
    json:
      username: ${username}
      password: "123456"
  validate:
    - eq: [status_code, 200]

3.3.3 变量优先级

原则上 config 变量和 step 变量名称尽量不要重复, 当config和step中都用同一个变量时,step变量优先级大于config变量

config:
  name: 引用变量
  variables:
    username: test123

teststeps:
-
  name: get
  request:
    method: GET
    url: http://httpbin.org/get
  validate:
    - eq: [status_code, 200]
-
  name: post
  variables:
    username: test456
  request:
    method: POST
    url: http://httpbin.org/post
    json:
      username: ${username}
      password: "123456"
  validate:
    - eq: [status_code, 200]
View Code

${username} 引用的结果是局部变量中的值 test456

3.4 提取返回结果与校验

3.4.1 extract提取返回结果

HttpRunner4.x 支持 2 种响应结果字段提取方式:jmespath 表达式和 正则表达式(regex)

  • 响应结果为 JSON 结构,支持采用 jmespath 表达式进行参数提取。jmespath表达式,有关更多详细信息,请参考JMESPath教程https://jmespath.org/tutorial.html
  • 返回的非json格式,可以用正则表达式(regex) 提取

extract 提取返回结果:

extract 的对象仅有 5 种类型:

  • status_code:提取响应状态码,例如 200、404
  • proto:提取协议类型,例如 “HTTP/2.0”、“HTTP/1.1”
  • headers:从响应 headers 中提取字段,例如 headers.name
  • cookies:从响应 cookies 中提取字段,例如 cookies.Token
  • body:从响应 body 中提取字段,例如 body.args.foo1

如果表达式中存在 - 的情况,那么需要加引号处理。

headers."Content-Type"

响应结果为 JSON 结构,支持采用 jmespath 表达式进行参数提取。

使用示例1:

config:
  name: 引用变量
  variables:
    username: test123

teststeps:
-
  name: post
  variables:
    username: test456
  request:
    method: POST
    url: http://httpbin.org/post
    json:
      username: ${username}
      password: "123456"
  extract:
    url: body.url
    origin: body.origin
    agent: headers."Content-Type"
  validate:
    - eq: [status_code, 200]
test_extract.yaml

使用pytest 命令行运行

httprunner make E:\work5-2\HttpRunner_demo\testcases\test_extract.yml#使用httprunner转换yaml格式的测试用例为py格式

  pytest -vs E:\work5-2\HttpRunner_demo\testcases\test_extract_test.py#使用pytest命令行运行转换后的测试用例

运行日志可以看到提取的结果:

2023-11-28 11:06:28.893 | INFO     | httprunner.response:extract:151 - extract mapping: {'url': 'http://httpbin.org/post', 'origin': '36.231.82.225', 'agent': 'application/json'}
2023-11-28 11:06:28.896 | INFO     | httprunner.response:validate:230 - assert status_code equal 200(int)       ==> pass

使用示例2:

config:
  name: 测试get请求

teststeps:
  -
    name: get
    request:
      method: GET
      url: https://www.baidu.com/
      params:
        wd: "python"
    extract:
      agent: headers."Content-Type"
    validate:
      - eq: [ "status_code", 200 ]
test_extract.yaml

使用hrp命令运行:

hrp run E:\work5-2\HttpRunner_demo\testcases\extract.yaml --gen-html-report

运行日志可以看到提取结果:

3:33PM INF extract value from="headers.\"Content-Type\"" value=application/x-gzip
3:33PM INF set variable value=application/x-gzip variable=agent

返回的非json格式,可以用正则表达式(regex) 提取.

使用示例:

config:
  name: 引用变量
  variables:
    username: test123

teststeps:
-
  name: blog
  request:
    method: GET
    url: https://www.cnblogs.com/yoyoketang/
  extract:
    title: "<title>(.*)</title>"
  validate:
    - eq: [status_code, 200]
    - eq: ["${title}", 200]
test_extract.yaml
hrp run E:\work5-2\httprunner_demo\testcases\regex_extract.yml -s -g -c --log-plugin --continue-on-failure

运行日志可以看到提取结果:

--------------------------------------------------
4:54PM INF extract value from=<title>(.*)</title> value="上海-悠悠 - 博客园"
4:54PM INF set variable value="上海-悠悠 - 博客园" variable=title
4:54PM INF validate status_code assertMethod=eq checkExpr=status_code checkValue=200 checkValueType=int64 expectValue=200 expectValueType=int64 result=true
4:54PM INF validate ${title} assertMethod=eq checkExpr=${title} checkValue="上海-悠悠 - 博客园" checkValueType=string expectValue="上海-悠悠 - 博客园" expectValueType=string result=true

 注意最新版4.3.6的python代码块是没有regex提取功能的

可将httprunner.response.py代码做如下修改,实现功能:

class ResponseObject(ResponseObjectBase):类加如下类 方法

  def _search_regexp(self,expr: Text) ->Any:

        try:
            encoding_way = self.resp_obj.encoding
            if encoding_way:
                body_str = self.resp_obj.content.decode(encoding_way)
            else:
                body_str = self.resp_obj.content.decode()#默认utf-8解码
            if not isinstance(body_str,str):
                logger.error(f"convert body to string failed: {self.resp_obj.text}")
            regexp_compile = re.compile(expr)
            match= regexp_compile.search(body_str)
            if match and match.groups():
                check_value = match.group(1)
                return check_value  # return first matched result in parentheses
            logging.error(f"search regexp failed: {expr}")
            return expr

        except Exception as e:
            logging.error(f"An error occurred: {e}")
            return expr

extract修改如下:
 def extract(
        self,
        extractors: Dict[Text, Text],
        variables_mapping: VariablesMapping = None,
    ) -> Dict[Text, Any]:
        if not extractors:
            return {}

        extract_mapping = {}
        for key, field in extractors.items():
            if "$" in field:
                # field contains variable or function
                field = self.parser.parse_data(field, variables_mapping)
            if textExtractorSubRegexp in field:
                field_value = self._search_regexp(field)
            else:
                field_value = self._search_jmespath(field)
            extract_mapping[key] = field_value

        logger.info(f"extract mapping: {extract_mapping}")
        return extract_mapping

添加如下方法:
    def _search_regexp(self,expr: Text) ->Any:

        try :
            regexp_compile = re.compile(expr)
            match = regexp_compile.search(self.resp_obj.text)
            if match and match.groups():
                check_value = match.group(1)
                return check_value  # return first matched result in parentheses
            logging.error(f"search regexp failed: {expr}")
            return expr

        except Exception as e:
            logging.error(f"An error occurred: {e}")
            return expr
response.py

3.4.2 Validate校验结果

使用jmespath提取 JSON 响应正文(text/html 格式用正则表达式提取)并使用预期值进行验证。

  • 提取结果:jmespath 表达式或正则表达式提取,更多细节参考JMESPath 教程
  • 预期值:这里也可以使用指定的预期值、变量或函数引用

 示例:

config:
  name: 校验结果

teststeps:
-
  name: post
  variables:
    username: test456
  request:
    method: POST
    url: http://httpbin.org/post
    json:
      username: ${username}
      password: "123456"
  validate:
    - eq: [status_code, 200]
    - eq: [body.url, "http://httpbin.org/post"]
    - eq: [body.origin, "183.193.27.78"]
    - eq: [headers."Content-Type", "application/json"]
test_validate.yaml

validate 也可以支持引用提取的结果

示例:

config:
  name: 校验结果

teststeps:
-
  name: post
  variables:
    username: test456
  request:
    method: POST
    url: http://httpbin.org/post
    json:
      username: ${username}
      password: "123456"
  extract:
    url: body.url
    origin: body.origin
    type: headers."Content-Type"
  validate:
    - eq: [status_code, 200]
    - eq: ["${url}", "http://httpbin.org/post"]
    - eq: ["${origin}", "183.193.27.78"]
    - eq: ["${type}", "application/json"]
test_validate.yaml

validate 可以支持的校验方式

assert                             缩写                                            功能
equal                         "eq", "equals", "equal"                            相等
less_than                     "lt", "less_than"                                  小于
less_or_equals                "le", "less_or_equals"                             小于或等于
greater_than                  "gt", "greater_than"                               大于
greater_or_equals             "ge", "greater_or_equals"                          大于或等于
not_equal                     "ne", "not_equal"                                  不等于
string_equals                 "str_eq", "string_equals"                          转字符串相等
length_equal                  "len_eq", "length_equal"                           长度相等
length_greater_than           "len_gt","length_greater_than"                     长度大于
length_greater_or_equals      "len_ge","length_greater_or_equals"                长度大于或等于
length_less_than              "len_lt", "length_less_than"                       长度小于
length_less_or_equals         "len_le", "length_less_or_equals"                  长度小于或等于
contains                                                                        check_value 包含 expect_value
contained_by                                                                    expect_value 包含check_value
type_match                                                                      type类型匹配
regex_match                                                                     正则匹配re.match(expect_value, check_value)
startswith                                                                      字符串以xx开头
endswith                                                                        字符串以xx结尾
validate

yaml 中可以写2种格式校验

  • {"assert_name": [check_value, expect_value]}
  • {"check": check_value, "assert": assert_name, "expect": expect_value, "message": check status code}

第一种校验方式,也就是前面常用到的

 validate:
    - eq: [status_code, 200]
    - eq: [body.url, "http://httpbin.org/post"]
    - eq: [body.origin, "183.193.27.78"]
    - eq: [headers."Content-Type", "application/json"]

第二种校验方式

 validate:
    - check: status_code            # 检查点,支持 jmespath 和正则提取
      assert: eq                    # 断言方式
      expect: 200                   # 期望结果
      message: check status code    # 可选项,当校验失败时的提示语

contains 校验包含

关于 contains 和 contained_by 函数定义

  • check_value 是需要校验的返回结果
  • expect_value 是期望结果,可以是这几种类型:list, tuple, dict, basestring

使用示例:

config:
  name: 校验结果

teststeps:
-
  name: post
  variables:
    username: test456
  request:
    method: POST
    url: http://httpbin.org/post
    json:
      username: ${username}
      password: "123456"
  extract:
    url: body.url
  validate:
    - eq: [status_code, 200]
    - contains: ["${url}", "httpbin.org/post"]
validata_contain.yaml

3.5 debugtalk辅助函数

httprunner 4.x 可以支持go语言和python语言写辅助函数,本篇主要介绍python语言写辅助函数。

3.5.1 debugtalk函数编写示例

在项目根目录新建 debugtalk.py 文件写辅助函数可以实现自动化生成动态参数。

如下:生成时间戳 和 随机字符串示例

# debugtalk.py

import time
import uuid


def current_time():
    """获取时间戳"""
    return time.strftime("%Y-%m-%d %H:%M:%S")


def rand_str():
    """生成随机字符串"""
    return str(uuid.uuid4())[:6]
debugtalk

3.5.2 在yaml 用例中引用debugtalk文件的函数

示例1:

config:
  name: 引用变量

teststeps:
-
  name: post
  request:
    method: POST
    url: http://httpbin.org/post
    json:
      username: ${rand_str()}
      password: "123456"
  validate:
    - eq: [status_code, 200]
debugtalk_test.yaml

变量中也可以引用函数

示例2:

config:
  name: 引用变量
  variables:
    user: ${rand_str()}

teststeps:
-
  name: post
  request:
    method: POST
    url: http://httpbin.org/post
    json:
      username: ${user}
      password: "123456"
  validate:
    - eq: [status_code, 200]
debugtalk_test.yaml

使用pytest运行测试用例:

httprunner make E:\work5-2\httprunner_demo\testcases\extract.yaml
pytest -vs E:\work5-2\httprunner_demo\testcases\extract_test.py

 3.6 环境变量的使用

一般来说,在进行实际应用的开发过程中,应用会拥有不同的运行环境,通常会有以下环境:

  • 本地开发环境
  • 测试环境
  • 生产环境

在不同环境中,我们可能会使用不同的数据库或邮件发送等配置,这时候则需要通过 .env 文件来针对不同的运行环境作不同的设置。

3.6.1 环境变量基础知识

在自动化测试中,有时需要借助环境变量实现某些特定的目的,常见的场景包括:

  • 切换测试环境
  • 切换测试配置
  • 存储敏感数据(从信息安全的角度出发)

Windows 系统中使用 set 命令设置环境变量和值,接下来设置以下变量

  • base_url = http://127.0.0.1:8000 设置base_url值,可以一键切换运行的环境
  • username = test 设置登陆账号和密码,切换账号测试
  • password = 123456 设置登陆账号和密码,切换账号测试

 打开cmd,使用 set key=value 格式设置环境变量(linux使用 export 命令)

C:\Users\dell>set base_url=http://127.0.0.1:8000
C:\Users\dell>set username=test
C:\Users\dell>set password=123456

查看环境变量值使用 set keyname 查看对应的值

C:\Users\dell>set base_url
base_url=http://127.0.0.1:8000

C:\Users\dell>set username
username=test

C:\Users\dell>set password
password=123456

在windows系统里面,命令行引用变量用 %var%

C:\Users\dell>echo 账号:%username% 账号:test

linux 系统中使用 export 命令设置环境变量和值, 引用变量$keyname

[root@VM_0_2_centos ~]# export base_url=http://127.0.0.1:8000
[root@VM_0_2_centos ~]# export username=test
[root@VM_0_2_centos ~]# echo $base_url
http://127.0.0.1:8000
[root@VM_0_2_centos ~]# echo $username
test

在cmd设置的环境变量只是临时的环境变量,不会保存到电脑本地,关闭窗口后自动清除。

3.6.2 使用.env 文件设置环境变量

 winodws无法直接创建 .env 的文件,会提示文件名不能为空,这里使用 pycharm 新建用过 .env 文件
.env 文件必须放到项目根目录(debugtalk.py同一层级)

# .env
base_url=http://127.0.0.1:8000
username=test
password=123456
.env

在 HttpRunner 4.x版本中内置了函数 ENV 函数读取环境变量的值

3.6.3 用例引用环境变量

引用环境变量使用ENV函数 ${ENV(keyname)

config:
    name: 引用env文件
    base_url: ${ENV(base_url)}
    variables:
      user: ${ENV(username)}
      psw: ${ENV(password)}

teststeps:
-
  name: post
  request:
    method: POST
    url: /post
    json:
      username: ${user}
      password: ${psw}
  validate:
    - eq: [status_code, 200]
env_test.yaml

3.7 base_url 环境地址变量的使用

config 中有个 base_url 关键字可以设置环境地址,这样其它接口就只需写相对地址了

比如我要测试的API接口如下

如果一个测试用例,有多个步骤中,测试环境地址不变http://httpbin.org, 只是接口地址不一样/get 和 /post
于是可以把公共的环境地址单独拿出来

示例1:

config:
  name: 引用变量
  base_url: http://httpbin.org
  variables:
    username: test123

teststeps:
-
  name: get
  request:
    method: GET
    url: /get
  validate:
    - eq: [status_code, 200]
-
  name: post
  variables:
    username: test456
  request:
    method: POST
    url: /post
    json:
      username: ${username}
      password: "123456"
  validate:
    - eq: [status_code, 200]
base_url.yaml

 如果多个yaml 文件公用base_url ,可以写到环境变量.env

# .env
base_url=http://127.0.0.1:8000
username=test
password=123456
.env
config:
    name: 引用env文件
    base_url: ${ENV(base_url)}
    variables:
      user: ${ENV(username)}
      psw: ${ENV(password)}

teststeps:
-
  name: post
  request:
    method: POST
    url: /post
    json:
      username: ${user}
      password: ${psw}
  validate:
    - eq: [status_code, 200]
env_test.yaml

3.8 parameters参数化

httprunner 4.x 实现参数化使用parameters 关键字,数据源有三种方式

  • 1.在yaml 文件中直接写测试数据源
  • 2.测试数据源写到csv文件
  • 3.自定义函数,函数返回列表形式数据

3.8.1 参数配置

参数名称的定义分为两种情况:

  • 独立参数单独进行定义;
  • 多个参数具有关联性的参数需要将其定义在一起,采用短横线(-)进行连接。

数据源指定支持三种方式:

  • 在 YAML/JSON 中直接指定参数列表:该种方式最为简单易用,适合参数列表比较小的情况
  • 通过内置的 parameterize(可简写为P)函数引用 CSV 文件:该种方式需要准备 CSV 数据文件,适合数据量比较大的情况
  • 调用 debugtalk.py 中自定义的函数生成参数列表:该种方式最为灵活,可通过自定义 Python 函数实现任意场景的数据驱动机制,当需要动态生成参数列表时也需要选择该种方式

三种方式可根据实际项目需求进行灵活选择,同时支持多种方式的组合使用。
假如测试用例中定义了多个参数,那么测试用例在运行时会对参数进行笛卡尔积组合,覆盖所有参数组合情况。

3.8.2 单个参数的参数化

先准备测试数据,准备四组登录用的账号和密码,账号为test1,test2,test3,test4,密码统一设置为123456。

参数user账号数据,设置对应的值 ["test1", "test2", "test3", "test4"],定义单个参数用variables,定义参数化用parameters

config:
    name: login case
    parameters:
        user: [test1, test2, test3, test4]

teststeps:
-
  name: post
  request:
    method: POST
    url: http://httpbin.org/post
    json:
      username: ${user}
      password: "123456"
  validate:
    - eq: [status_code, 200]
login_params.yaml

如果参数化里面的数据只有一个,比如psw对应的值只有一个,parameters 也可以设置 password的值

config:
    name: login case
    parameters:
        user: [test1, test2, test3, test4]
        password: 123456

variables 和 parameters 设置相同名称变量时,parameters 优先级大于variables

完整的 login_params.yaml 脚本如下

config:
    name: login case
    variables:
        user: test
        password: 123456
    parameters:
        user: [test1, test2, test3, test4]


teststeps:
-
  name: post
  request:
    method: POST
    url: http://httpbin.org/post
    json:
      username: ${user}
      password: ${password}
  validate:
    - eq: [status_code, 200]
login_params.yaml

使用命令行转换为py格式的用例,使用pytest运行

httprunner make E:\work5-2\httprunner_demo\testcases\login_params.yaml
import pytest
import os
if __name__=="__main__":

    pytest.main(['-s', '-v','./testcases/extract_test.py','--alluredir', './report/tmp', "--clean-alluredir"])
    os.system(r"allure generate ./report/tmp -o ./report/html --clean")

生成的报告如下:

3.8.2 关联性参数的参数化

针对于一个账号对应一个密码,这种一一对应的关系,可以用关联性的参数化

config:
    name: login case
    parameters:
        user-password:
        - [test1, "123456"]
        - [test2, "123456"]
        - [test3, "123456"]
        - [test4, "123456"]

teststeps:
-
  name: post
  request:
    method: POST
    url: http://httpbin.org/post
    json:
      username: ${user}
      password: ${password}
  validate:
  - eq: [status_code, 200]
login_params.yaml

3.8.3 笛卡尔集参数化

比如测试账号有四种["test1", "test2", "test3", "test4"],密码也有四种 ["123456", "123456", "123456", "123456"]
用笛卡尔积组合的话,就是4*4=16种组合

config:
    name: login case
    parameters:
        user: [test1, test2, test3, test4]
        password: ["123456", "123456", "123456", "123456"]

teststeps:
-
  name: post
  request:
    method: POST
    url: http://www.example.com/
    params:
      username: ${user}
      password: ${password}
  validate:
  - eq: [status_code, 200]
login_params.yaml

这样运行会生成16组用例

3.8.4 使用csv文件参数化

对于已有参数列表,并且数据量比较大的情况,比较适合的方式是将参数列表值存储在 CSV 数据文件中。
对于 CSV 数据文件,需要遵循如下几项约定的规则:

  • CSV 文件中的第一行必须为参数名称,从第二行开始为参数值,每个(组)值占一行;
  • 若同一个 CSV 文件中具有多个参数,则参数名称和数值的间隔符需实用英文逗号;
  • 在 YAML/JSON 文件引用 CSV 文件时,文件路径为基于项目根目录(debugtalk.py 所在路径)的相对路径。

例如,user 的参数取值范围为 test1,test2,test3, test4,那么我们就可以创建 user.csv,放到项目根目录下的data目录下,并且在文件中按照如下形式进行描述。
data/ user.csv

user
test1
test2
test3
test4
login.csv

然后在 YAML/JSON 测试用例文件中,就可以通过内置的 parameterize(可简写为 P)函数引用 CSV 文件

config:
    name: login case
    parameters:
        user: ${P(data/user.csv)}

teststeps:
-
  name: post
  request:
    method: GET
    url: http://www.example.com/
    params:
      username: ${user}
      password: "12345"
  validate:
  - eq: [status_code, 200]
login_csv.yaml

 3.8.5 使用debugtalk函数参数化用例

用 debugtalk.py 中自定义的函数生成参数列表,生成的参数列表必须为 list of dict 的数据结构。

需对 user_id 进行参数化数据驱动,参数取值范围为 1001~1004,那么就可以在 debugtalk.py 中定义一个函数,返回参数列表。
debugtalk.py

def get_user_id():
    return [
        {"user_id": 1001},
        {"user_id": 1002},
        {"user_id": 1003},
        {"user_id": 1004}
    ]
debugtalk.py

然后,在 YAML/JSON 的 parameters 中就可以通过调用自定义函数的形式来指定数据源。

config:
    name: "demo"
    parameters:
        user_id: ${get_user_id()}

另外,通过函数的传参机制,还可以实现更灵活的参数生成功能,在调用函数时指定需要生成的参数个数。

例如,在 debugtalk.py 中定义函数 get_account,生成指定数量的账号密码参数列表。

def get_account(num):
    accounts = []
    for index in range(1, num+1):
        accounts.append(
            {"user": "test%s" % index, "password": "123456"},
        )

    return accounts

那么在 YAML/JSON 的 parameters 中就可以调用自定义函数生成指定数量的参数列表。

config:
    name: "demo"
    parameters:
        username-password: ${get_account(4)}

在 debugtalk.py 中定义 get_user_password 函数,返回 4 个用户名和密码数据。

def get_account(num):
    accounts = []
    for index in range(1, num+1):
        accounts.append(
            {"user": "test%s" % index, "password": "123456"},
        )
    return accounts

if __name__ == '__main__':
    print(get_account(4))

生成 list of dict 数据格式:

[ {'user': 'test1', 'password': '123456'}, {'user': 'test2', 'password': '123456'}, {'user': 'test3', 'password': '123456'}, {'user': 'test4', 'password': '123456'} ]
config:
    name: login case
    parameters:
      user-password: ${get_account(4)}

teststeps:
-
  name: post
  request:
    method: GET
    url: http://www.example.com/
    params:
      username: ${user}
      password: ${password}
  validate:
  - eq: [status_code, 200]
login_params_func.yaml

3.9 hook机制

httprunner 4.x可以支持hook机制,在发送请求前做一些预处理或在请求完成后后置处理

  • setup_hooks :主要用于处理接口的前置的准备工作,也可以对请求 request 参数签名加密等操作
  • teardown_hooks:主要用于后置清理工作,也可以对返回 respone 解密等操作

3.9.1 测试步骤添加hook

在项目根目录新建 debugtalk.py,名称一定要一样

def hook_up():
    print("前置操作:setup!")

def hook_down():
    print("后置操作:teardown!")
debugtalk.py

只在第一个步骤添加 setup_hooks 和 teardown_hooks

config:
    name: logincase

teststeps:
-
    name: step1 login
    request:
        url: http://www.example.com
        method: GET
    setup_hooks:
      - ${hook_up()}
    teardown_hooks:
      - ${hook_down()}
test_hook.yaml

 3.9.2 request预处理

 针对请求request 发出去的参数预处理,也可以用到 setup_hooks,需传一个内置 request 参数,debugtalk.py代码如下

# debugtalk.py
def request_sign(request):
    """请求sign签名"""
    print("请求body:",request.get("req_json"))
    # 新增 sign 参数
    request["req_json"]["sign"] = "sign xxxxxxxxxxxxxxx"
    print("sign 签名后请求body:", request.get("req_json"))
    return request
debugtalk.py
config:
    name: logincase

teststeps:
-
    name: step1 login
    request:
        url: http://www.example.com
        method: POST
        json:
           user: test
           psw: "123456"
    setup_hooks:
      - ${request_sign($request)}
test_hook.yaml

 通过打印报告可以看出request预处理生效

================== request details ==================
method   : MethodEnum.POST
url      : http://www.example.com/
headers  : {
    "User-Agent": "python-requests/2.31.0",
    "Accept-Encoding": "gzip, deflate, br",
    "Accept": "*/*",
    "Connection": "keep-alive",
    "HRUN-Request-ID": "HRUN-f3452b5a-1080-4f51-8fec-3ac81ef62c07-032632",
    "Content-Length": "65",
    "Content-Type": "application/json"
}
cookies  : {}
body     : {
    "user": "test",
    "psw": "123456",
    "sign": "sign xxxxxxxxxxxxxxx"
test_hook.log

 3.9.3 返回response处理

如果需要在接口返回后,对返回的结果处理,可以添加respone请求参数,比如我把返回的body改成了"python"

def response_containt(response):
    """修改返回body"""
    print("返回response body:", response["body"])
    response["body"] = "python"
    print("修改后返回response body:", response["body"])
    return response

这对于返回的body是加密的数据,可以自己去解密后返回解密内容很有帮助

config:
    name: logincase
teststeps:
-
    name: step1 login
    request:
        url: http://www.example.com
        method: POST
        json:
           user: test
           psw: "123456"
    teardown_hooks:
      - ${response_status($response)}
    validate:
      - eq: [status_code, 200]
      - eq: [body, "python"]
hook_response.yaml

3.10 接口关联实战

当登录用例写完后,后面想继续写其他用例,可以导入前面的login用例,当成下个用例的步骤使用
导入前一个用例之前,需先 export 导出变量,变成全局变量。

案例1:使用电商开源项目演示:

项目地址:https://github.com/macrozheng/mall

config:
  name: 测试登录关联
  variables:
    username: admin
    password: macro123
  export:
    - token

teststeps:
-
  name: 账号密码成功登录
  request:
      url: "https://admin-api.macrozheng.com/admin/login"
      method: POST
      headers:
        User-Agent: "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/119.0.0.0 Safari/537.36"
        Content-Type: "application/json"
      json:
        username: ${username}
        password: ${password}
  extract:
      token: "body.data.token"
  validate:
      - eq: ["status_code", 200]
      - eq: ["body.message", "操作成功"]
login.yaml

 成功拿到token鉴权码

2023-11-30 17:20:17.931 | INFO     | httprunner.response:extract:156 - extract mapping: {'token': 'eyJhbGciOiJIUzUxMiJ9.eyJzdWIiOiJhZG1pbiIsImNyZWF0ZWQiOjE3MDEzMzYwMTc5OTEsImV4cCI6MTcwMTk0MDgxN30.RHqCipsb3uLUzVS1j0ZtooHGKSv_Yty-Kz8lIjVsP_b8o74gu7PoWkxvKQPm0ntCSb6-C3cyfnK4ocCrxF1bOg'}
2023-11-30 17:20:17.931 | INFO     | httprunner.response:validate:251 - assert status_code equal 200(int)    ==> pass
2023-11-30 17:20:17.932 | INFO     | httprunner.response:validate:251 - assert body.message equal 操作成功(str)    ==> pass

从登录接口返回数据结果中得到鉴权参数后,传递给其他接口放到请求头中。

 其他接口测试用例在teststeps中引入登录用例即可。

config:
  name: 广告搜索功能
  verify: False
teststeps:
-
  name:  账号密码成功登录
  testcase: testcases/login.yaml
-
  name: 广告搜索
  variables:
    pageNum: 1
    pagesize: 5
    name: "电影推荐"

  request:
      url: "https://admin-api.macrozheng.com/home/advertise/list"
      method: GET
      headers:
        User-Agent: "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/119.0.0.0 Safari/537.36"
        Content-Type: "application/json"
        Authorization: " Bearer $token"
      params:
        pageNum: $pageNum
        pagesize: $pagesize
        name: $name
  validate:
      - eq: ["status_code", 200]
      - contains : ["body.data.list[0].name", "电影推荐"]
search_ad.yaml
import pytest
import os
if __name__=="__main__":

    pytest.main(['-s', '-v','./testcases/','--alluredir', './report/tmp', "--clean-alluredir"])
    os.system(r"allure generate ./report/tmp -o ./report/html --clean")

生成的测试报告如下:

 3.11 upload上传文件

requests 库里面上传文件会用到 requests_toolbelt, 可以很方便的解决 multipart/form-data 类型的文件上传相关接口。
HttpRunner4.x 集成了 requests_toolbelt,可以使用内置 upload 关键字来上传文件相关操作。

 使用此功能需要安装额外的库

pip3 install requests-toolbelt filetype

在项目根目录新建data目录,放需要传的文件up.jpg

新建 test_up.yaml

config:
    name: file
teststeps:
-
    name: upload file
    request:
        url: http://124.70.221.221:8201/api/v1/upfile/
        method: POST
        upload:
            file: data/up.jpg
            title: xxx
    validate:
        - eq: [body.code, 0]
test_up.yaml

 

>>>待续

 

posted @ 2023-10-13 16:56  enjoyzier  阅读(621)  评论(1编辑  收藏  举报