今天來介紹一個python的一個開源項目:httprunner,接口自動化工具。第一次輸出,難免有不周到的地方,輕噴~
httprunner初探
-
- 介紹:
- 主要特征:
- 工作流程
-
- 調試技巧
- 工作流
- 用例管理
-
- 關鍵概念:
- V2.0
-
- 測試用例分層模型
- V2.0案例學習
- V3.0
-
- 測試用例分層模型
- v3.0案例學習
- hook機制
-
- 背景
- 使用
- 環境(.env)
- 參數化:
-
-
- 實作
- 使用
-
- extract/export
- 測試報告
- 總結
介紹:
HttpRunner是一個簡單優雅但功能強大的 HTTP(S) 測試架構。以YAML或JSON格式定義測試用例,保障測試用例描述的統一性和可維護性。程式執行的時候,會處理使用者輸入的yml/json檔案并基于模闆生成測試檔案。最終通過pytest.main([])的方式去執行生成的用例檔案。使用者隻需要通過json/yml檔案去維護用例即可,不需要關心程式如何處理json/yml檔案,如何生成測試檔案等,簡單快速通過pytest運作用例,并擷取詳細的測試報告。
主要特征:
- 以YAML或JSON格式定義測試用例,保障測試用例描述的統一性和可維護性
- testsuite > testcase > teststep(api)
- 支援設計一系列的測試場景,每個測試場景可包含多個teststep。
- 支援參數化設計
- 支援variables/ extract/ validate/hooks機制(使用jmespath ,提取和驗證json響應)
- 支援添加邏輯運算輔助函數(debugtalk.py),在測試腳本中實作複雜的動态邏輯
- 在HAR 支援下記錄并生成測試用例。(使用charles去抓取請求,生成的用例檔案可能還需要手動處理)
- 使用pytest執行測試檔案 ,數百個插件随時可用。使用allure ,測試報告可以非常強大。
- run_testcase():處理請求前的資料
- __run_step() > __run_step_request(使用requests發起api請求并導出其他用例引用的變量(__step_datas))
- 通過重複使用locust ,您可以進行性能測試,而無需進行額外的工作。
- 支援CLI指令,與CI/CD完美結合。
工作流程
想要了解其工作流程,最好的辦法就是使用debug模式,那在這裡應該如何使用debug來參透httprunner的執行流程呢?跟着我來看下:
調試技巧
使用pycharm進行調試,且python環境使用Virtualenv進行管理
- 安裝:pip install httprunner
- 在
目錄下找到hrun檔案/venv/bin/

- 編輯hrun檔案,為sys.argv指派。第一個是hrun的絕對路徑,第二個是用例的絕對路徑
- 在
這裡打個斷點,那麼就可以開始愉快的調試之旅了~sys.exit(main_hrun_alias())
# -*- coding: utf-8 -*-
import re
import sys
from httprunner.cli import main_hrun_alias
if __name__ == '__main__':
sys.argv = ['/Users/boyizhang/PycharmProjects/apitest/venv/bin/hrun', '/Users/boyizhang/PycharmProjects/apitest/hruntests/testcases/testheader.yml']
sys.argv[0] = re.sub(r'(-script\.pyw|\.exe)?$', '', sys.argv[0])
sys.exit(main_hrun_alias())
工作流
調試日志:
- 進入main_hrun_alias()中,并為sys.argv的清單插入新的一項“run”(因為我們是執行用例)
- 進入main(),因為我們含有“run”,是以現在開始執行用例。
- 進入main_run(extra_args),根據extra_args(yml/json路徑)将yml/json檔案生成python檔案(測試檔案)
- 進入main_make(),判斷路徑是否存在
- 進入 __make() ,加載yml/json檔案,并準備資料(按照資料的優先級進行複制,從低到高)
- 進入make_testcase(),将yml/json檔案基于模闆生成對應的py檔案
- 最後回到main_run()方法,繼續執行main_make()以下的代碼,可以發現,程式最終将建立的py檔案交給pytest.main()來執行
- 上面示範了用例檔案從yml/json到py檔案的生成流程。接下來可以對生成的py檔案繼續調試。
- 這裡我做了一個比較簡單的示範,小夥伴們如果有興趣,可以根據我的思路繼續你的調試之旅哦。
用例管理
關鍵概念:
概括來說,測試用例分層機制的核心是将接口定義、測試步驟、測試用例、測試場景進行分離,單獨進行描述和維護,進而盡可能地減少自動化測試用例的維護成本。
- 測試用例(testcase)應該是完整且獨立的,每條測試用例應該是都可以獨立運作的
- 測試用例(測試場景)是測試步驟(teststep)的 **有序 **集合,每一個測試步驟對應一個 API 的請求描述
- 測試用例集(testsuite)是測試用例的 無序 集合,集合中的測試用例應該都是互相獨立,不存在先後依賴關系的;如果确實存在先後依賴關系,那就需要在測試用例中完成依賴的處理
- 多個測試場景組成一個測試套件,運作一個測試套件,可以同時執行多個測試場景。
- 接口定義(api definition):為了更好地對接口描述進行管理,使用獨立的檔案對接口描述進行存儲,即每個檔案對應一個接口描述。
V2.0
建議大家用最新版本的,但是有興趣了解httprunner的架構變化的同學,可以繼續讀V2.0子產品的内容。
測試用例分層模型
V2.0的版本中,存在api definition的概念。
- 所有的api都置于api檔案夾,每個api建立一個yml/json檔案進行管理
- testcase中的teststep引用對應的api
優點是:api管理友善;缺點是:在testcase中的teststep可以直接引用api definition,不管testcase是單個步驟的簡單場景還是多個步驟的複雜場景,都需要進行引用api definition。這樣的話,對于單個步驟的簡單場景而言,又和api definition很相似,這樣就會造成重複描述,而且容易混淆。
**
那麼,我們如何在testcase>teststep中引用api呢?如何引用testcase呢?
**
V2.0案例學習
在teststep中可以通過api字段引用api definition 、通過testcase字段引用testcase。下面我們根據腳手架快速建立一個項目
httprunner --startproject hruntest2.0
(需要確定安裝的是httpruner3.0以下的版本)來說明:
$ httprunner --startproject hruntest2.0 && tree hruntest2.0
Start to create new project: hruntest2.0
CWD: /Users/boyizhang/PycharmProjects/apitest
created folder: hruntest2.0
created folder: hruntest2.0/api
created folder: hruntest2.0/testcases
created folder: hruntest2.0/testsuites
created folder: hruntest2.0/reports
created file: hruntest2.0/api/demo_api.yml
created file: hruntest2.0/testcases/demo_testcase.yml
created file: hruntest2.0/testsuites/demo_testsuite.yml
created file: hruntest2.0/debugtalk.py
created file: hruntest2.0/.env
created file: hruntest2.0/.gitignore
hruntest2.0
├── api
│ └── demo_api.yml
├── debugtalk.py
├── reports
├── testcases
│ └── demo_testcase.yml
└── testsuites
└── demo_testsuite.yml
- api definition
# api/demo_api.yml
name: demo api
variables:
var1: value1
var2: value2
request:
url: /api/path/$var1
method: POST
headers:
Content-Type: "application/json"
json:
key: $var2
validate:
- eq: ["status_code", 200]
- testcase
config:
name: "demo testcase"
variables:
device_sn: "ABC"
username: ${ENV(USERNAME)}
password: ${ENV(PASSWORD)}
base_url: "http://127.0.0.1:5000"
teststeps:
-
name: demo step 1
api: path/to/api1.yml
variables:
user_agent: 'iOS/10.3'
device_sn: $device_sn
extract:
- token: content.token
validate:
- eq: ["status_code", 200]
在teststep中使用api選項來引用api definition,執行testcase/demo_testcase.yml用例檔案。(注:api definition不屬于testcase範疇,無法直接執行api/下的yml/json檔案)。在teststep中傳的字段的優先級會比api definition下的高,也就是說,如果teststep傳某個字段,那麼會有限使用teststep傳的那個,如果沒有,則再使用api definition的字段。
V3.0
為了簡單,在HttpRunner v2.x中的API概念已經被取消了。可以将API定義為隻有一個請求步驟的測試用例**(需要重點注意一下這裡)**。
測試用例分層模型
- 去掉api的概念,把api的概念轉為測試用例的概念。将API定義為隻有一個請求步驟的測試用例。以統一的概念:測試用例,使得維護用例的工作更加友善。
- 按照V2.0的邏輯,需要維護api definition,也需要維護隻有一個測試步驟的testcase,而V3.0中,我們隻需要維護testcase即可。v2->v3的轉變,使得可以在避免重複描述的同時,解決測試用例的依賴關系,進而保證每個測試用例都是獨立可運作的。
那麼,問題來了,我們應該如何在某個testcase的teststep中引用其他testcase呢?
v3.0案例學習
在測試步驟(teststep)中,可通過 testcase 字段引用其它測試用例,引用方式為對應測試用例檔案的路徑,絕對路徑或相對路徑均可。推薦使用相對路徑,路徑基準為項目根目錄,即 debugtalk.py 所在的目錄路徑。
**通過 **
httprunner startproject hruntest3.0
快速建立項目。
$ tree hruntest3.0
hruntest3.0
├── debugtalk.py
├── har
├── reports
└── testcases
├── demo_testcase_ref.yml
└── demo_testcase_request.yml
# demo_testcase_request.yml
config:
name: "request methods testcase with functions"
variables:
foo1: config_bar1
foo2: config_bar2
expect_foo1: config_bar1
expect_foo2: config_bar2
base_url: "https://postman-echo.com"
verify: False
export: ["foo3"]
teststeps:
-
name: post form data
variables:
foo2: bar23
request:
method: POST
url: /post
headers:
User-Agent: HttpRunner/${get_httprunner_version()}
Content-Type: "application/x-www-form-urlencoded"
data: "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"]
# demo_testcase_ref.yml
config:
name: "request methods testcase: reference testcase"
variables:
foo1: testsuite_config_bar1
expect_foo1: testsuite_config_bar1
expect_foo2: config_bar2
base_url: "https://postman-echo.com"
verify: False
teststeps:
-
name: request with functions
variables:
foo1: testcase_ref_bar1
expect_foo1: testcase_ref_bar1
testcase: testcases/demo_testcase_request.yml
export:
- foo3
-
name: post form data
variables:
foo1: bar1
request:
method: POST
url: /post
headers:
User-Agent: HttpRunner/${get_httprunner_version()}
Content-Type: "application/x-www-form-urlencoded"
data: "foo1=$foo1&foo2=$foo3"
validate:
- eq: ["status_code", 200]
- eq: ["body.form.foo1", "bar1"]
- eq: ["body.form.foo2", "bar21"]
在teststep中使用testcase字段來引用,執行demo_testcase_ref.yml用例檔案,當執行到name = request with functions這個步驟的時候會先執行其引用的testcase:
testcases/demo_testcase_request.yml
。
在根目錄下,執行
hrun testcases/demo_testcase_ref.yml
,可以看到,程式也生成了該case:
testcases/demo_testcase_request.yml
的測試檔案:
hook機制
涉及hook處理
背景
在自動化測試中,執行用例前,需要執行一些預處理操作,執行用例後,需要做一些清理工作。如果手動去操作的話,就不是很合适。是以就需要用到hook機制,在執行用例前後執行hook函數。
使用
hook 機制分為兩個層級:
- 測試用例層面(testcase)
- 在測試用例層面,主要在config字段新增兩個關鍵字 setup_hooks 和 teardown_hooks。在這裡,其主要目的就是用于測試前的準備工作以及測試後的清理工作。
- 測試步驟層面(teststep)
- 在每個teststep中新增關鍵字 setup_hooks 和 teardown_hooks。實作對請求的 request 内容進行預處理以及實作對響應的 response 進行修改。
編寫hook函數
- hook 函數的定義放置在項目的 debugtalk.py 中,在 YAML/JSON 中調用 hook 函數仍然是采用 KaTeX parse error: Expected '}', got 'EOF' at end of input: {func(a, $b)} 的形式。
- 可傳自定義參數:對于測試用例層面的 hook 函數,與 YAML/JSON 中自定義的函數完全相同,可通過自定義參數傳參的形式來實作靈活應用。
- 可傳request與response參數:對于單個測試用例層面的 hook 函數,除了可傳入自定義參數外,還可以傳入與目前測試用例相關的資訊,包括請求的 $request(請求頭,請求體,請求方法等)和響應的 r e s p o n s e ( 對 于 r e q u e s t s . R e s p o n s e , 也 就 是 響 應 體 ) , 用 于 實 現 更 複 雜 場 景 的 靈 活 應 用 。 如 ‘ response(對于requests.Response,也就是響應體),用于實作更複雜場景的靈活應用。如` response(對于requests.Response,也就是響應體),用于實作更複雜場景的靈活應用。如‘{print_req(
)} `。$request
# ${print_request($request)}
2021-07-18 09:30:46.740 | DEBUG | httprunner.runner:__call_hooks:121 - call hook function: ${print_reqeust($request)}
{'method': 'GET', 'url': '/get', 'params': {'foo1': 'bar11', 'foo2': 'bar21', 'sum_v': 3}, 'headers': {'User-Agent': 'HttpRunner/3.1.5', 'HRUN-Request-ID': 'HRUN-7768261f-0abf-4ce5-abf2-06327de85fd7-846739'}, 'req_json': None, 'data': None, 'cookies': {}, 'timeout': 120, 'allow_redirects': True, 'verify': False}
# ${print_req($response)}
2021-07-18 09:36:03.019 | DEBUG | httprunner.runner:__call_hooks:121 - call hook function: ${print_req($response)}
<httprunner.response.ResponseObject object at 0x109c87e50>
環境(.env)
涉及env的處理,文檔
# parser.py
def get_mapping_function(
function_name: Text, functions_mapping: FunctionsMapping
) -> Callable:
#省略
if function_name in functions_mapping:
return functions_mapping[function_name]
elif function_name in ["environ", "ENV"]:
return utils.get_os_environ
#省略
raise exceptions.FunctionNotFound(f"{function_name} is not found.")
# utils.py
def set_os_environ(variables_mapping):
""" set variables mapping to os.environ
"""
for variable in variables_mapping:
os.environ[variable] = variables_mapping[variable]
logger.debug(f"Set OS environment variable: {variable}")
def get_os_environ(variable_name):
try:
return os.environ[variable_name]
except KeyError:
raise exceptions.EnvNotFound(variable_name)
加載用例之前,會先把.env檔案中的變化加載到環境中,如果用例用含有ENV或者environ,則通過
os.environ
去讀取相應的值。
參數化:
實作
HttpRunner 實作參數化資料驅動機制:https://debugtalk.com/post/httprunner-data-driven/
HttpRunner 再議參數化資料驅動機制:https://debugtalk.com/post/httprunner-data-driven-refactor/
使用
需要注意的是,從v2.0開始,參數化隻支援在 testsuite 中實作。不再支援在測試用例檔案中進行參數化配置。
參數配置概述
- 獨立參數:指的是不與其他參數有任何關聯關系,與其他參數是互相獨立的。
- 關聯參數:假如測試用例中定義了多個參數,那麼測試用例在運作時會對參數進行笛卡爾積組合,覆寫所有參數組合情況。
具體使用
https://v2.httprunner.org/prepare/parameters/
extract/export
底層使用jmespath ,提取和驗證json響應,使得提取更簡單。
extract用來抓取響應體中的字段,export導出目前用例抓取的字段,供引用該用例的用例使用。
測試報告
- pytest内置報告
- hrun /path/to/testcase --html=report.html
- allure報告
- pip install allure-pytest
- 執行:hrun /path/to/testcase --alluredir=/tmp/my_allure_results
- 線上打開報告:allure serve /tmp/my_allure_results 或者 生成html報告:allure generate reports/allure -o reports/allure/html
總結
好好利用httprunner,可以覆寫80%的場景,可以說是一個不錯的工具,當然我們除了學習應該如何使用之外,更應該學習人家設計的思想,這樣自己之後才能有機會做好一點的工具。
另外推薦一個Java版本的接口自動化工具大家可以參考rest-assured。httprunner與rest-assured實作方式大同小異,不過rest-assured并沒有通過json/yaml去管理用例,而是直接寫測試檔案(類似httprunner生成的py檔案)。大家可以對比學習下~。