概要
HttpRunner 是一款面向 HTTP(S) 協議的通用測試框架,只需編寫維護一份YAML/JSON
腳本,即可實現自動化測試、性能測試、線上監控、持續集成等多種測試需求。
安裝
pip install httprunner(推薦使用python3版本)
啟動demo server
- cd到httprunner的目錄,set FLASK_APP=/httprunnerWorkspace/xxxapi_server.py文件地址(非win系統用export)
- flask run命令執行后,即可啟動demo server並通過browser訪問
使用流程
- 網頁訪問時,通過postman interceptor/Charles/firefox或者chrome的開發者工具來捕獲相關的請求詳情。
- 用json/yaml編寫測試用例。
- cmd執行測試用例hrun xxxx.json
- reports目錄下自動生成測試后的結果報告
支持腳本錄制
- 使用Charles>Sequence錄制並導出為.har格式
- 轉換生成測試用例 har2case file_path/xxxx.har new_name.json(也可指定為.yml格式),如果第二個參數省略則默認為json格式
- 執行如上腳本即可生成測試報告
目錄結構(分層原理)
約定大於配置的原則
在組織測試用例描述的文件目錄結構時,遵循約定大於配置的原則。通過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": {
"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"
}
}
}
}
]
|
[
{
"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"
}
}
]
|
[
{
"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": {
"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": {
"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: |
- config: parameters: - var1-var2: - test: |
[ {
"config": {
|
[ {
"config": { |
CSV參數化
- 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
validate:
- eq:
- status_code
- 200
- eq:
- content.message
- add event success
|
"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類型。
- 參照規范做成yml格式的腳本並保證執行ok
- 用在線工具將yml轉成json格式http://yaml-online-parser.appspot.com/
- 執行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查看關鍵字。