HttpRunner使用技巧

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

二、HttpRunner特征 (yml文件和json配置文件)
1)支持以YAML/JSON格式定义测试用例
2)支持响应验证
3)支持初始化清除机制
4)支持套件级别的用例管理
5)支持pytest命令(hrun底层封装,h3新特性)
6)支持allure生成测试报告
7)支持性能测试(底层Locust)

三、HttpRunner版本差异

2.x3.x
推荐格式yml.py
命令行项目实现复用pytest命令
报告独立实现复用pytest报告生成
分层api、case、suiteRunTestCase、RunRequest
特点代码和case分离链式调用,简化结构

四、HttpRunner原理
在这里插入图片描述

五、早期框架
1)requests基于urllib3
2)自动化–requests<–性能–locust
3)locust基于requests
在这里插入图片描述

六、安装使用
环境说明:
HttpRunner使用python开发,支持python3.6以上版本和大多数操作系统。
安装:
pip install httprunner
验证:
httprunner -V 或 hrun -V

七、五大核心命令
1)httprunner:主命令,用于所有功能。
2)hrun:别名httprunner run,用于运行YAML/JSON/pytest测试用例。
3)hmake:别名httprunner make,用于将YAML/JSON测试案例转换为pytest文件。
4)har2case:别名httprunner har2case,用于将Har转换为YAML/JSON测试用例。
5)locusts:用于运行性能测试。
主要是hrun命令**

八、使用httprunner
1)方式1:录制生成用例(浏览器要默认系统代理,fiddler才抓得到)
1.fiddler抓包导出har格式文件

2.生成用例
har2case listcode.har

{"log":{"pages":[], "comment":"exported @ 2021/8/25 22:56:24", "entries":[{"time":76, "serverIPAddress":"120.55.190.222", "connection":"ClientPort:32736;EgressPort:32738", "request":{"headersSize":501, "postData":{"text":"", "mimeType":""}, "queryString":[{"name":"action", "value":"list_course"}, {"name":"pagenum", "value":"1"}, {"name":"pagesize", "value":"20"}], "headers":[{"name":"Host", "value":"120.55.190.222:7080"}, {"name":"Connection", "value":"keep-alive"}, {"name":"Accept", "value":"application/json, text/plain, */*"}, {"name":"User-Agent", "value":"Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/92.0.4515.107 Safari/537.36"}, {"name":"Referer", "value":"http://120.55.190.222:7080/mgr/ps/mgr/index.html"}, {"name":"Accept-Encoding", "value":"gzip, deflate"}, {"name":"Accept-Language", "value":"zh-CN,zh;q=0.9"}, {"name":"Cookie", "value":"sessionid=ej5157gqs5z1ukombfgaxkbmdi4khx2j"}], "bodySize":0, "url":"http://120.55.190.222:7080/api/mgr/sq_mgr/?action=list_course&pagenum=1&pagesize=20", "cookies":[{"name":"sessionid", "value":"ej5157gqs5z1ukombfgaxkbmdi4khx2j"}], "method":"GET", "httpVersion":"HTTP/1.1"}, "timings":{"blocked":-1, "ssl":0, "receive":0, "wait":56, "dns":0, "send":0, "connect":20}, "response":{"headersSize":172, "bodySize":1697, "statusText":"OK", "redirectURL":"", "status":200, "httpVersion":"HTTP/1.1", "cookies":[], "content":{"compression":0, "text":"{\"retcode\": 0, \"retlist\": [{\"id\": 3645, \"name\": \"111\", \"desc\": \"111\", \"display_idx\": 1}, {\"id\": 3648, \"name\": \"\\u82f1\\u8bed\", \"desc\": \"\\u9ad8\\u4e2d\\u82f1\\u8bed\", \"display_idx\": 1}, {\"id\": 3650, \"name\": \"\\u677e\\u52e4123456\", \"desc\": \"123467\", \"display_idx\": 1}, {\"id\": 3652, \"name\": \"\\u677e\\u52e412\", \"desc\": \"1234455\", \"display_idx\": 1}, {\"id\": 3654, \"name\": \"dge\", \"desc\": \"gegheh\", \"display_idx\": 1}, {\"id\": 3655, \"name\": \"g4h4\", \"desc\": \"y4y\", \"display_idx\": 1}, {\"id\": 3656, \"name\": \"g4444\", \"desc\": \"y1234\", \"display_idx\": 1}, {\"id\": 3657, \"name\": \"g4454\", \"desc\": \"y1234\", \"display_idx\": 1}, {\"id\": 3658, \"name\": \"11\", \"desc\": \"11\", \"display_idx\": 1}, {\"id\": 3659, \"name\": \"1122\", \"desc\": \"1111\", \"display_idx\": 1}, {\"id\": 3660, \"name\": \"112233\", \"desc\": \"1111\", \"display_idx\": 1}, {\"id\": 3661, \"name\": \"112233445566\", \"desc\": \"1111\", \"display_idx\": 1}, {\"id\": 3662, \"name\": \"xiaoyi12345\", \"desc\": \"1111\", \"display_idx\": 1}, {\"id\": 3664, \"name\": \"oyitu\\u513f\\u7ae5\", \"desc\": \"\\u70ed\\u70ed\", \"display_idx\": 1}, {\"id\": 3630, \"name\": \"\\u5316\\u5b66\", \"desc\": \"\\u5316\\u5b66\\u8bfe\\u7a0b\", \"display_idx\": 4}, {\"id\": 3631, \"name\": \"\\u521d\\u4e2d\\u5316\\u5b66\", \"desc\": \"\\u521d\\u4e2d\\u6570\\u5b66\\u8bfe\\u7a0b\", \"display_idx\": 4}, {\"id\": 3633, \"name\": \"\\u521d\\u4e2d\\u7269\\u7406\", \"desc\": \"\\u521d\\u4e2d\\u7269\\u7406\\u8bfe\\u7a0b666888\", \"display_idx\": 4}, {\"id\": 3641, \"name\": \"\\u521d\\u4e2d\\u97f3\\u4e50\\u559c\\u5267\\u4eba\", \"desc\": \"\\u521d\\u4e2d\\u97f3\\u4e50\\u559c\\u5267\\u4eba\\u8bfe\\u7a0b\", \"display_idx\": 4}, {\"id\": 3643, \"name\": \"\\u6570\\u5b66\", \"desc\": \"\\u521d\\u4e2d\\u6570\\u5b66\", \"display_idx\": 4}, {\"id\": 3649, \"name\": \"\\u6570\\u5b6611\", \"desc\": \"\\u521d\\u4e2d\\u6570\\u5b66\", \"display_idx\": 4}], \"total\": 27}", "size":1697, "mimeType":"application/json"}, "headers":[{"name":"Content-Type", "value":"application/json"}, {"name":"X-Frame-Options", "value":"SAMEORIGIN"}, {"name":"Content-Length", "value":"1697"}, {"name":"Vary", "value":"Cookie"}, {"name":"Date", "value":"Wed, 25 Aug 2021 14:54:54 GMT"}, {"name":"Server", "value":"0.0.0.0"}]}, "startedDateTime":"2021-08-25T22:54:54.9723173+08:00", "cache":{}}], "creator":{"name":"Fiddler", "comment":"https://fiddler2.com", "version":"5.0.20204.45441"}, "version":"1.2"}}
# NOTE: Generated By HttpRunner v3.1.6
# FROM: listcourse.json


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


class TestCaseListcourse(HttpRunner):

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

    teststeps = [
        Step(
            RunRequest("/api/mgr/sq_mgr/")
            .get("http://120.55.190.222:7080/api/mgr/sq_mgr/")
            .with_params(**{"action": "list_course", "pagenum": "1", "pagesize": "20"})
            .with_headers(
                **{
                    "Host": "120.55.190.222:7080",
                    "Connection": "keep-alive",
                    "Accept": "application/json, text/plain, */*",
                    "User-Agent": "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/92.0.4515.107 Safari/537.36",
                    "Referer": "http://120.55.190.222:7080/mgr/ps/mgr/index.html",
                    "Accept-Encoding": "gzip, deflate",
                    "Accept-Language": "zh-CN,zh;q=0.9",
                    "Cookie": "sessionid=ej5157gqs5z1ukombfgaxkbmdi4khx2j",
                }
            )
            .with_cookies(**{"sessionid": "ej5157gqs5z1ukombfgaxkbmdi4khx2j"})
            .validate()
            .assert_equal("status_code", 200)
            .assert_equal('headers."Content-Type"', "application/json")
            .assert_equal("body.retcode", 0)
            .assert_equal("body.total", 27)
        ),
    ]


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

3.执行测试用例
1、hrun运行:
hrun listcode_test.py

2、pytest+allure运行:

  • allure下载–bin目录配置到环境变量
  • 验证:allure --version
  • 下载模块:allure-pytest
  • 生成临时文件:
    pytest listcourse_test.py --alluredir=reports/tmp
    或 hrun listcourse_test.py --alluredir=reports/tmp
    或 hrun listcourse.yml --alluredir=reports/tmp
    hrun可以运行py、yml和json文件,pytest只能运行py文件
  • 生成报告:allure serve reports/tmp

har文件转换成yml文件:
har2case -2y listcourse.har

config:
    name: testcase description
    variables: {}
    verify: false
teststeps:
-   name: /api/mgr/sq_mgr/
    request:
        cookies:
            sessionid: ej5157gqs5z1ukombfgaxkbmdi4khx2j
        headers:
            Accept: application/json, text/plain, */*
            Accept-Encoding: gzip, deflate
            Accept-Language: zh-CN,zh;q=0.9
            Connection: keep-alive
            Cookie: sessionid=ej5157gqs5z1ukombfgaxkbmdi4khx2j
            Host: 120.55.190.222:7080
            Referer: http://120.55.190.222:7080/mgr/ps/mgr/index.html
            User-Agent: Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36
                (KHTML, like Gecko) Chrome/92.0.4515.107 Safari/537.36
        method: GET
        params:
            action: list_course
            pagenum: '1'
            pagesize: '20'
        url: http://120.55.190.222:7080/api/mgr/sq_mgr/
    validate:
    -   eq:
        - status_code
        - 200
    -   eq:
        - headers.Content-Type
        - application/json
    -   eq:
        - body.retcode
        - 0
    -   eq:
        - body.total
        - 27

har文件转换成json格式文件:
har2case -2j listcourse.har

{
    "config": {
        "name": "testcase description",
        "variables": {},
        "verify": false
    },
    "teststeps": [
        {
            "name": "/api/mgr/sq_mgr/",
            "request": {
                "url": "http://120.55.190.222:7080/api/mgr/sq_mgr/",
                "params": {
                    "action": "list_course",
                    "pagenum": "1",
                    "pagesize": "20"
                },
                "method": "GET",
                "cookies": {
                    "sessionid": "ej5157gqs5z1ukombfgaxkbmdi4khx2j"
                },
                "headers": {
                    "Host": "120.55.190.222:7080",
                    "Connection": "keep-alive",
                    "Accept": "application/json, text/plain, */*",
                    "User-Agent": "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/92.0.4515.107 Safari/537.36",
                    "Referer": "http://120.55.190.222:7080/mgr/ps/mgr/index.html",
                    "Accept-Encoding": "gzip, deflate",
                    "Accept-Language": "zh-CN,zh;q=0.9",
                    "Cookie": "sessionid=ej5157gqs5z1ukombfgaxkbmdi4khx2j"
                }
            },
            "validate": [
                {
                    "eq": [
                        "status_code",
                        200
                    ]
                },
                {
                    "eq": [
                        "headers.Content-Type",
                        "application/json"
                    ]
                },
                {
                    "eq": [
                        "body.retcode",
                        0
                    ]
                },
                {
                    "eq": [
                        "body.total",
                        27
                    ]
                }
            ]
        }
    ]
}

json转yaml:

import yaml
import json


def json_to_yaml(data):
    stra = json.dumps(data)
    dyaml = yaml.load(stra, Loader=yaml.FullLoader)
    stream = open("login.yml", 'w')
    yaml.safe_dump(dyaml, stream, default_flow_style=False)


json_to_yaml([{"A": 1, "B": 3, "C": [{"D": 3}, {"d": 4}]}, {"E": {"e": [1, 2, 3]}}])

yaml转json:

import yaml

def yaml_to_json():
    with open('login.yml', encoding='utf-8') as file:
        data = yaml.load(file)  
    print(data)
yaml_to_json()

2)方式2:手写用例
1.步骤:
1、熟悉hr测试用例规则
2、根据规则编写yaml/json/python格式的测试用例文件
3、执行测试用例

2.用例的整体结构
1、config(每个测试用例都必须有config部分,可以配置用例)
必填项:
name:测试用例的名称,将在log和报告中展示。

选填项:
base_url ip+port
variables
parameters
verify
export

2、teststeps(包含测试步骤相关信息,其中步骤可以引用其他测试用例)
必填项:
name:测试步骤的名称,将在log和报告中展示。
request:嵌套字段,包含请求信息。
url可以填写相对路径,与base_url拼接

选填项:
extract 提取返回值
validate
hooks

3、yaml书写规范
注意:
key和value之间要加空格
‘-’表示在列表里面
运行:
hrun testcase/demo1.yml
直接运行所有用例:hrun testcase

九、httprunner参数化
1)用例变量类型划分
1.配置变量(variables):
用于数据解耦
variables参数定义在config或teststeps中(优先级高)

2.参数变量(parameters)
用于参数化—列表类型
parameters参数定义在config中

2)按级别划分
1.用例级别
字典形式
方式1:直接指定参数方式
方式2:引用自定义函数–返回字典–创建debugtalk的py文件,定义函数(文件放在第一层目录)

#! /usr/bin/python3
# -*- coding:utf-8 -*-
# Author:ChenBin
# time:2021/8/291:08
# file:debugtalk.py
import requests


def login_variables():
    return {"user": "auto", "psw": "sdfsdfsdf"}


def login_parameter():
    # 可以从文件获取参数
    return [
        {"user": "auto", "psw": "sdfsdfsdf", "code": 0},
        {"user": "auto1", "psw": "sdfsdfsdf", "code": 1},
        {"user": "auto", "psw": "sdfsdf", "code": 1}
    ]


def hook_setup():
    with open('setup.txt', 'w', encoding='utf8') as f:
        f.write('执行初始化')


def hook_teardown():
    with open('teardown.txt', 'w', encoding='utf8') as f:
        f.write('执行清除')


url = 'http://120.55.190.222:7080'
cookie = {}


def login_course():
    global sessionid
    if 'sessionid' not in cookie:
        payload = {"username": "auto", "password": "sdfsdfsdf"}
        res = requests.post(url + '/api/mgr/loginReq', data=payload)
        sessionid = res.cookies.get_dict()['sessionid']
        cookie['sessionid'] = sessionid
    else:
        sessionid = cookie['sessionid']
    return sessionid


def add_course():
    sessionid = login_course()
    payload = {"action": "add_course",
               "data": '{"name": "测试课程", "desc": "初中测试课程", "display_idx": "1"}'
               }
    cookies = {'sessionid': sessionid}
    resp = requests.post(url + '/api/mgr/sq_mgr/', data=payload, cookies=cookies)
    return resp.json()['id']


def delete_course():
    course_id = add_course()
    payload = {"action": "delete_course", "id": course_id}
    resp = requests.delete(url + '/api/mgr/sq_mgr/', data=payload, cookies=cookie)
    return resp.json()


print(delete_course())

login.yml

config:  # 用例配置区
  name: 列出课程测试
  base_url: http://120.55.190.222:7080
  verify: false  # 非https模式
  export:  # 返回测试步骤中提取的变量--数据类型--列表
  -
    cookie
teststeps:  # 测试步骤--对应数据类型:列表
-
  name: 登录
  request:
    method: POST
    url: /api/mgr/loginReq
    data:
      username: auto
      password: sdfsdfsdf
  extract:  # 对应数据格式--字典
    cookie: cookies.sessionid
    length: headers.Content-Length  # jmespath

add_course.yml

config:  # 用例配置区
  name: 添加课程
  base_url: http://120.55.190.222:7080
  verify: false  # 非https模式
  export:
    - course_id

teststeps:
  -
    name: 登录
    testcase: testcase/login.yml
  -
    name: 添加课程
    request:
      method: POST
      url: /api/mgr/sq_mgr/
      data:
        action: add_course
        data: '{"name": "初中化学", "desc": "初中化学课程", "display_idx": "1"}'
    validate:
      -
        eq: ['status_code', 200]
      -
        eq: ['body.retcode', 0]
    extract:
      course_id: body.id

update_course.yml

config:  # 用例配置区
  name: 修改课程
  base_url: http://120.55.190.222:7080
  verify: false  # 非https模式
  export:
    - course_id

teststeps:
  -
    name: 添加课程
    testcase: testcase/add_course.yml
  -
    name: 修改课程
    request:
      method: PUT
      url: /api/mgr/sq_mgr/
      data:
        action: modify_course
        id: ${course_id}
        newdata: '{"name": "初中计算机", "desc": "初中计算机课程", "display_idx": "1"}'
    validate:
      -
        eq: ['status_code', 200]
      -
        eq: ['body.retcode', 0]

delete_course.yml

config:  # 用例配置区
  name: 删除课程
  base_url: http://120.55.190.222:7080
  verify: false  # 非https模式

teststeps:
  -
    name: 添加并修改课程
    testcase: testcase/update_course.yaml

  -
    name: 删除课程
    request:
      method: DELETE
      url: /api/mgr/sq_mgr/
      data:
        action: delete_course
        id: $course_id
    validate:
      -
        eq: ['status_code', 200]
      -
        eq: ['body.retcode', 0]

2.步骤级别
1、直接在teststeps加参数variables,且引用函数不支持

demo_variables_login.yml

config:  # 用例配置区
  name: 列出课程测试
  base_url: http://120.55.190.222:7080
  verify: false  # 非https模式
#  variables: ${login_variables()} # 用例变量--字典类型
#    {"user": "auto", "psw": "sdfsdfsdf"}
#    user: auto  # 方式1.直接指定参数方式
#    psw: sdfsdfsdf  # 方式2:引用自定义函数--返回字典
  variables:
    user: ${ENV(username)}
    psw: ${ENV(password)}

teststeps:  # 测试步骤--对应数据类型:列表
-
  name: 登录
#  variables:
#    user: auto
#    psw: sdfsdfsdf
#  variables: ${login_variables()}  # 不支持???
  request:
    method: POST
    url: /api/mgr/loginReq
    data:
      username: ${user}
      password: ${psw}
  extract:  # 对应数据格式--字典
    cookie: cookies.sessionid
    length: headers.Content-Length  # jmespath
  validate:
    - eq: [ 'status_code', 200]
    - eq: [ 'body.retcode', 0 ]

update_course.yml

config:  # 用例配置区
  name: 修改课程
  base_url: http://120.55.190.222:7080
  verify: false  # 非https模式
  variables:
    course_id: ${add_course('高级课程')}

teststeps:
  -
    name: 登录
    testcase: setup_teardown/login.yml
  -
    name: 修改课程
    setup_hooks:
      - ${add_course()}
    teardown_hooks:
      - ${delete_course()}
    request:
      method: PUT
      url: /api/mgr/sq_mgr/
      data:
        action: modify_course
        id: ${course_id}
        newdata: '{"name": "初中计算机", "desc": "初中计算机课程", "display_idx": "1"}'
    validate:
      -
        eq: ['status_code', 200]
      -
        eq: ['body.retcode', 0]

2、前置和后置
步骤级别:hook函数
setup_hooks,teardown_hooks

login.yml

config:  # 用例配置区
  name: 列出课程测试
  base_url: http://120.55.190.222:7080
  verify: false  # 非https模式
  export:  # 返回测试步骤中提取的变量--数据类型--列表
  -
    cookie
teststeps:  # 测试步骤--对应数据类型:列表
-
  name: 登录
  setup_hooks:
    - ${hook_setup()}
  teardown_hooks:
    - ${hook_teardown()}
  request:
    method: POST
    url: /api/mgr/loginReq
    data:
      username: auto
      password: sdfsdfsdf
  extract:  # 对应数据格式--字典
    cookie: cookies.sessionid
    length: headers.Content-Length  # jmespath

用例级别:未提供

3)环境变量
创建ENV文件
注意:顶行不要空,直接写变量=变量值

login.env

username=auto
password=sdfsdfsdf

login.yml

config:  # 用例配置区
  name: 列出课程测试
  base_url: http://120.55.190.222:7080
  verify: false  # 非https模式
#  variables: ${login_variables()} # 用例变量--字典类型
#    {"user": "auto", "psw": "sdfsdfsdf"}
#    user: auto  # 方式1.直接指定参数方式
#    psw: sdfsdfsdf  # 方式2:引用自定义函数--返回字典
  variables:
    user: ${ENV(username)}
    psw: ${ENV(password)}

teststeps:  # 测试步骤--对应数据类型:列表
-
  name: 登录
#  variables:
#    user: auto
#    psw: sdfsdfsdf
#  variables: ${login_variables()}  # 不支持???
  request:
    method: POST
    url: /api/mgr/loginReq
    data:
      username: ${user}
      password: ${psw}
  extract:  # 对应数据格式--字典
    cookie: cookies.sessionid
    length: headers.Content-Length  # jmespath
  validate:
    - eq: [ 'status_code', 200]
    - eq: [ 'body.retcode', 0 ]

十、测试套运行
在这里插入图片描述
config配置
独立参数:
运用用例数=独立参数的笛卡尔积

关联参数:
运行用例数=关联参数组

test_suite.yml

config:
  name: test suite demo

testcases:
  -
    name: case1
    testcase: testcase/demo1.yml  # 定义测试用例文件路径
  -
    name: case2
    testcase: testcase/login.yml

login.yml

config:  # 用例配置区
  name: 列出课程测试
  base_url: http://120.55.190.222:7080
  verify: false  # 非https模式
  export:  # 返回测试步骤中提取的变量--数据类型--列表
  -
    cookie
teststeps:  # 测试步骤--对应数据类型:列表
-
  name: 登录
  setup_hooks:
    - ${hook_setup()}
  teardown_hooks:
    - ${hook_teardown()}
  request:
    method: POST
    url: /api/mgr/loginReq
    data:
      username: auto
      password: sdfsdfsdf
  extract:  # 对应数据格式--字典
    cookie: cookies.sessionid
    length: headers.Content-Length  # jmespath

demo1.yml

config:  # 用例配置区
  name: 列出课程测试
  base_url: http://120.55.190.222:7080
  verify: false  # 非https模式

teststeps:  # 测试步骤--对应数据类型:列表
-
  name: 登录
  testcase: testcase/login.yml

-
  name: 步骤2-列出课程
  request:
    method: GET  # 请求方法  GET POST PUT DELETE
    url: /api/mgr/sq_mgr/
    params:
      action: list_course
      pagenum: 1
      pagesize: 20
    cookies:
      sessionid: ${cookie}  # 引用变量
  validate:  # 校验 对应列表数据
    -
      equal:  # 判断实际值和预期是否相等,对应数据类型--列表
        - status_code
        - 200
    -
      equal:
        - body.retcode
        - 0
    -
      equal:
        - body.total
        - 30

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

妍婧

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值