HTTPRunner


  • 概要
  • 安装
  • 启动demo server
  • 使用流程
  • 支持脚本录制
  • 目录结构(分层原理)
    • 约定大于配置的原则
    • 结构关系
  • 常用命令
  • 脚本结构详细
    • 测试用例文件说明
    • 脚本详细说明
      • extract(结果提取器)
  • Validator详细
  • 脚本参数化–未完待续
    • 列表参数化
    • CSV参数化
    • 自定义函数参数化
  • 热加载机制
  • Skip机制(分组执行控制测试用例)
    • unittest单元测试框架中的三种常用装饰器
    • httprunner中的用法
  • 使用技巧
    • 关于报错
    • 关于yml和json格式
  • 未解决问题
  • 使用中的一些问题和解决方案
  • Version History
    • Ver.1.5.11中的一些变化
  • 那些坑!
    • 坑1
    • 坑2
    • 不算坑,但一点小注意

 

概要

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

安装

pip install httprunner(推荐使用python3版本)

启动demo server

  1. cd到httprunner的目录,set FLASK_APP=/httprunnerWorkspace/xxxapi_server.py文件地址(非win系统用export)
  2. flask run命令执行后,即可启动demo server并通过browser访问

使用流程

  1. 网页访问时,通过postman interceptor/Charles/firefox或者chrome的开发者工具来捕获相关的请求详情。
  2. 用json/yaml编写测试用例。
  3. cmd执行测试用例hrun xxxx.json
  4. reports目录下自动生成测试后的结果报告

支持脚本录制

  1. 使用Charles>Sequence录制并导出为.har格式
  2. 转换生成测试用例 har2case file_path/xxxx.har   new_name.json(也可指定为.yml格式),如果第二个参数省略则默认为json格式
  3. 执行如上脚本即可生成测试报告

目录结构(分层原理)

约定大于配置的原则

在组织测试用例描述的文件目录结构时,遵循约定大于配置的原则。通过hrun --startproject project_name创建测试项目目录后,目录结构如下:

  • api:API接口定义必须放置在api目录下
  • suite:模块定义必须放置在suite目录下
  • testcases:测试场景文件必须放置在testcases目录下
  • debugtalk.py: 相关的函数定义放置在debugtalk.py中
接下来,我们就可以按照测试用例的接口-模块-场景分层原则往里面添加描述信息了。

结构关系

  • api>suite>testcases(在api中定义基本接口及操作,suite中定义suite和调用的api,testcases中直接调用api或者是suite使用)
格式如下:

 

api
[
   {
     "api": {
       "def": "event_add($eid, $name)",
       "validate": [
         {"eq": ["status_code", 200]},
         {"eq": ["content.message", "add event success"]}
       ],
       "request": {
         "url": "/api/add_event/",
         "method": "POST",
         "data": {
           "status": 1,
           "limit": 1000,
           "name": "$name",
           "address": "testemail@email.com",
           "start_time": "2018-09-18 16:00:00",
           "eid": "$eid"
         }
       }
     }
   },
   {
     "api": {
       "def": "event_get($eid, $name)",
       "validate": [
         {"eq": ["status_code", 200]},
         {"eq": ["content.message", "success"]}
       ],
       "request": {
         "url": "/api/get_event_list/",
         "method": "GET",       
         "params": {
           "eid": "$eid",
           "name": "$name"
         }
       }
     }
   }
]

 

suite
[
   {
     "config": {
       "request": {
         "base_url": "http://127.0.0.1:8000"
       },
       "name": "event add get test",
       "def": "event_add_get_suite($eid, $name)"
     }
   },
   {
     "test": {
       "validate": [
         {"eq": ["status_code", 200]},
         {"eq": ["content.message", "add event success"]}
         ],
       "api": "event_add($eid, $name)",
       "name": "event add"
     }
   },
   {
     "test": {
       "validate": [
         {"eq": ["status_code", 200]},
         {"eq": ["content.message", "success"]}],
       "api": "event_get($eid, $name)",
       "name": "event get"
     }
   }
]

 

testcases
[
   {
     "config": {
       "name": "event test",
       "request":{
           "base_url": "http://127.0.0.1:8000/"
       },
       "parameters": [
         {"eid-name": "${P(testcases/eventInfo.csv)}"}
       ]
     }
   },
   {
     "test": {
       "suite": "event_add_get_suite($eid, $name)",
       "name": "event add get details test"
     }
   }
]

 

 

常用命令

命令
详情
备注
httprunner/hrun hrun -h 核心命令, 通过-h查看命令详情
  hrun --validate xxxx.json json格式文件的正确性检测
  hrun --validate xxxx1.json xxxx2.json xxx3.yml 也可以同时指定多个文件,并同时支持json/yaml类型的正确性检测
  hrun xxx.json --failfast 默认情况会执行所有指定用例集中的所有测试用例,--failfast命令遇到失败时不再执行后续用例
  hrun xxx.json --log-level debug 查看请求的参数和相应的详细内容,可将日志级别设置为debug
  hrun xxx.json --html-report-name new_name 指定生成的报告名称
     
     
har2case ... 格式转换命令,将har格式转换成json/yaml格式
     

 

脚本结构详细

yml
json
- config: name: "xxxxxxx" parameters: - user_agent: ["iOS/10.1", "iOS/10.2", "iOS/10.3"] //列表参数化 - app_version: ${P(app_version.csv)} //csv参数化 - os_platform: ${get_os_platform()} //自定义函数参数化 variables: - user_agent: 'iOS/10.3' - device_sn: ${gen_random_string(15)} - os_platform: 'ios' - app_version: '2.8.6' request: base_url: http://127.0.0.1:5000 headers: Content-Type: application/json device_sn: $device_sn - test: name: get token with $user_agent, $os_platform, $app_version request: url: /api/get-token method: POST headers: app_version: $app_version os_platform: $os_platform user_agent: $user_agent json: sign: ${get_sign($user_agent, $device_sn, $os_platform, $app_version)} extract: - token: content.token validate: - eq: [status_code, 200] - eq: [headers.Content-Type, application/json] - eq: [content.success, true]
[
  {
    "config": {...} }, { "test": {...} }, { "test": {...} } ]
config
"config": {
     "name": "test report title",                  //required; string; 测试用例集的名称,测试报告的标题
     "parameters": [                                          //optional; list of dict; 全局参数,用于实现数据化驱动,作用域为整个测试用例集
         {"user_agent": ["ios/10.1", "ios/10.2", "ios/10.3"]},
         {"app_version": "${P(app_version.csv)}"},
         {"os_platform": "${get_os_platform()}"}
     ],
     "variables": [                                //optional; list of dict; 定义的全局变量,作用域为整个测试用例集          
         {"user_agent": "ios/10.3"},
         {"device_sn": "${gen_random_string(15)}"},
         {"os_platform": "ios"}
     ],
     "request":{                             //optional; dict of dict; request的公共参数,常用参数包括base_url和headers
         "base_url": "http://127.0.0.1:5000",
         "headers": {
             "Content-Type": "application/json",
             "device_sn": "$device_sn"
         }
     },
     "output": [                      //optional; list of string; 整个用例集输出的参数列表,可输出的参数包括variable和extract的参数; 在log-leve为debug模式下,会在terminal中打印出参数内容                  
         "token"
     ]
}
test
"test": {
     "name": " test case title and support $os_platformxxxx",      //required; string; 测试用例的名称,测试报告中将作为每一项测试的标题
     "request": {                            //required; dict of dict; HTTP请求的详细内容
         "url": "/api/get-token",
         "method": "POST",
         "headers": {
             "app_version": "$app_version",
             "os_platform": "$os_platform",
             "user_agent": "$user_agent"
         },
         "json": {
             "sign": "$user_agentxxxx"
         },
         "extract": [                    //optional; list of dict; 从当前HTTP请求的相应结果中提取参数,并保存到参数变量中(如token),后续测试用例可以通过&token的形式进行引用
             {"token": "content.token"}
         ],
         "validate": [                 //optional; list of dict; 测试用例中定义的结果校验项。
             {"eq": ["status_code": 200]},
             {"eq": ["headers.Content-Type", "application/json"]},
             {"eq": ["content.success", true]}
         ],
         "setup_hooks": [],     //optional; list of string; 在HTTP请求发送前执行hook函数,主要用于准备工作。hook函数放置于debugtalk.py中,必须包含三个参数method,url,kwargs(request的参数字典)
         "teardown_hooks": []   //optional; list of string; 在HTTP请求发送后执行hook函数,主要用于测试后的清理工作。hook函数放置于debugtalk.py中,必须包含一个参数,resp_obj:requests.Response实例
     }
}


测试用例文件说明

  • 测试用例集:单个测试用例或多个测试用例的集合,表现形式为一个json文件
  • 测试用例:单次http请求和响应过程,表现形式为json文件中的一个test
  • config:全局配置项,作用于整个测试用例集
  • test:作用于单个测试用例
  • 如果一个变量在config中定义了,在test中没有定义,则test会继承该变量
  • 如果一个变量在config和test中都定义了,则test会使用自己定义的变量值
  • 各个test的空间相互独立,互不影响
  • 如果在多个test之前传递参数值,则需要使用extract关键字,并且只能从前向后传递 
  • 测试用例存在顺序关系,运行时从前往后一次运行

脚本详细说明

  • extract(结果提取器)
      参数提取功能关键字extract。如token权限验证问题,想让后续的test的token也使用前面test中的token信息时,及可通过参数提取extract。

     

    extract
    "extract": [
            {"token": "content.token"}
       ],
     
     
    ......
    "headers": {
         "token": "$token",
          ........
    },

    只要返回结果是json类型,就可以将其中的任意字段进行提取,并保存到一个变量中,方便后续接口请求进行引用。

*http://cn.httprunner.org/testcase/structure/

 

Validator详细

No.
Comparator
Description
A(check), B(expect)
1 eq value is equal A == B
2 lt less than A < B
3 le less than or equals A <=B
4 gt greater than A >B
5 ge greater than or equals A >=B
6 ne not equals A != B
7 str_eq string equals str(A) == str(B)
8 len_eq, count_eq length or count equals len(A) == B
9 len_gt,count_gt length greater than len(A) > B
10 len_ge,count_ge length greater than or equals len(A) >= B
11 len_lt, count_lt length less than len(A) < B
12 len_le, count_le length less than or equals len(A) <= B
13 contains contains

[1,2] contains 1

14 contained_by contained by A in B
15 type_match A is instance of B isinstance(A, B)
16 regex_match regex matches re.match(B, A)
17 startwith start with A startwith(B) is True
('abc' startwith 'ab')
18 endswith ends with A endswith(B) is True
('abc' endswith 'bc')

 

脚本参数化–未完待续

定义参数时,需要使用parameters关键字。

  • $变量转义符
  • ${}函数转义符,可以直接填写函数名称及调用参数,还可以包含变量

列表参数化

yml
有关联的yml列表参数化
json
有关联的json列表参数化
- config: parameters: - var1: ["iOS/10.1", "iOS/10.2", "iOS/10.3"] 
- test:
request:
method:POST
data:
var1:$var1
validate:
- eq:
- content.body.param.var1
- $var1
- config: parameters: - var1-var2:
- ["var1stri","var2stri"]
- ["var2stri","var2stri"]
- test:
request:
method:POST
data:
var1:$var1
var2:$var2
validate:
- eq:
- content.body.param.var1
- $var1

 

[
  {
    "config": {
"parameters": ["varxxx","varxxx"]} }, { "test": {...} } ]

 

[
  {
    "config": {
"parameters": "[
{"var1-var2":[["var1str","var2str"],["var1stri","var2stri"]]}
]"} }, { "test": {
"request":{
"data":{
"var1":"$var1",
"var2":"$var2"
}
}
} } ]

CSV参数化

yml
-   config:
         parameters:
             - eid-name: ${P(add.csv)}//关联参数
 
 
-   test:
         request:
             data:
                 address: dalian
                 eid: '$eid'
                 limit: '1000'
                 name: '$name'
                 start_time: '2018-07-17 18:00:00'
                 status: '1'
             method: POST
             url: http://127.0.0.1:8000/api/add_event/
         validate:
         -   eq:
             - status_code
             - 200
         -   eq:
             - content.message
             - add event success
json
"config": {                       
             "parameters": [
                 {
                     "eid-name": "${P(add.csv)}" //关联参数
                 }
             ]
         }
.......
"test": {
             "validate": [
                 {
                     "eq": ["status_code",200]
                 },
                 {
                     "eq": [
                         "content.status",
                         200
                     ]
                 },
                 {
                     "eq": [
                         "content.message",
                         "add event success"
                     ]
                 }
             ],
             "request": {
                 "data": {
                     "status": "1",
                     "limit": "1000",
                     "name": "$name",
                     "address": "dalian",
                     "start_time": "2018-07-17 18:00:00",
                     "eid": "$eid"
                 },
                 "method": "POST"
             },
             "name": "/api/add_event/"
         }


自定义函数参数化

debugtalk.py中自定义参数,脚本中使用参数后,报错

httprunner.exceptions.FunctionNotFound: get_parame_info not found in debugtalk.py module!
module mapping: {'variables': {}, 'functions': {}}

关注这个问题,需要持续关注https://testerhome.com/opensource_projects/httprunner上对DarkLi的回答

 

热加载机制

某些校验功能没有实现时,需要使用自定义的函数。

从热加载的顺序可以看出,查找变量或函数的顺序是从测试用例所在目录开始,沿着父路径逐层往上,直到系统的根目录。
因此,我们可以利用这个优先级原则来组织我们的用例和依赖的Python函数模块。将不同模块的测试用例集文件放在不同的文件夹下:
针对各个模块独有的依赖函数和变量,可以放置在对应文件夹的debugtalk.py文件中;而整个项目公共的函数和变量,就可以放置到项目文件夹的debugtalk.py中。

注意:因为我们在框架运行过程中需要将debugtalk.py作为函数模块进行导入,因此我们首先要保障debugtalk.py满足Python模块的要求,
         也就是在对应的文件夹中要包含__init__.py文件

  • 导入python模块的关键词:import_module_items
  • 不需要显示指定导入的python模块路径,热加载机制会自动发现。

Skip机制(分组执行控制测试用例)

unittest单元测试框架中的三种常用装饰器

  • @unittest.skip(reason) :无条件跳过当前测试用例
  • @unittest.skipIf(condition,reason) : 当条件表达式的值为true时跳过当前测试用例
  • @unittest.skipUnless(condition,reason): 当条件表达式的值为false时跳过当前测试用例

httprunner中的用法

因为httprunner同样也是采用unittest来组织和驱动测试用例执行的,那么使用方法如下。

  • skip 无条件的:在测试用例中新增skip字段(skip:"skip this test unconditionally")
    skipIf和skipUnless的condition这样的函数表达式调用会稍微麻烦一些,在debug.py中定义了skip_test_in_prodection_env()。
  • skifIf 有条件的: 在测试用例中新增 skipIf: ${skip_test_in_production_env()}
  • skipUnless 有条件的: 在测试用例集中新增 skipUnless: ${skip_test_in_production_env()}

使用技巧

关于报错

  • 报错详情里提示错误详情时,不要首先怀疑源码,debug查看详情后再做进一步判断(hrun xxx.json --log-level debug)
  • 如果想保留debug level的信息输入为文件,可以两个命令组合使用(hrun xxx.json --log-level debug --log-file log_name.txt)

关于yml和json格式

httprunner网上多用yml格式编辑,如果case做成时json脚本执行不通过的情况,可以采用如下方式转换为正确的json类型。

  1. 参照规范做成yml格式的脚本并保证执行ok
  2. 用在线工具将yml转成json格式http://yaml-online-parser.appspot.com/
  3. 执行json格式脚本

*同时可用--prettify命令来美化json格式

未解决问题

  • python操作csv文件
  • 结合jenkins使用
  • 报告自定义模板
  • 性能测试Locusts应用需要时,再研究用法(目前停留在启动locusts服务后web localhost:8089页面css乱)

使用中的一些问题和解决方案

  • 在报错Error: test_0000_0000(httprunner.api,TestSequense), TypeError: request() got an unexpected keyword argument 'validate'时
    优先check一下脚本层级是否有问题。有的时候虽然用–validate命令check ok,但实际上脚本仍然有问题。

Version History

Ver.1.5.11中的一些变化

  • 一个项目只能有一个debugtalk.py文件。运行时,首先定位debugtalk.py,将其所在目录作为项目的根目录
  • 在YAML/JSON测试用例中,CSV文件的路径是相对于根目录的路径。如,引用csv参数的测试用例中的路径写法应为${P(testcases/eventInfo.csv)}

那些坑!

坑1

是的,怎么能少了那些坑呢,果然又一次趴坑了。趴坑里一天多,终于解决了!

现象:json csv参数化时,脚本执行一直报错KeyError: 'name'。于是乎一直以为参数传递的方式有问题,其实不是,不是,不是!!!!!

原因:json脚本格式不正确。一定要注意脚本中各关键字之间的层级关系。如name关键字并不在request中;validate也不在request中。

坑2

测试用例分层执行时,如果依然在.../tests/testcases目录下执行,会报api not found的error。

原因:分层测试case之前,需要先加载api和suite的内容

解决方案:分层测试用例执行时,必须先cd到根目录下(testproject), 再用hrun tests/testcases/xxx.json的命令形式来执行。

如,hrun F:/Python3Workspace/testproject/tests/testcases/usersigntest.json

不算坑,但一点小注意

get方法时,request中的参数数据关键字不是json也不是data,而是params。当不明确request中请求的关键字时可以用charles录制api,转成json查看关键字。

 


免责声明!

本站转载的文章为个人学习借鉴使用,本站对版权不负任何法律责任。如果侵犯了您的隐私权益,请联系本站邮箱yoyou2525@163.com删除。



 
粤ICP备18138465号  © 2018-2025 CODEPRJ.COM