PS.本系列的內(nèi)容主要來自HttpRunner官方文檔,因?yàn)閭€(gè)人習(xí)慣,在學(xué)習(xí)的時(shí)候會寫筆記用以加深記憶(cv大法),所以有本系列的筆記。
https://cn.httprunner.org/
測試用例結(jié)構(gòu)
一、概念
- 測試用例集/
testsuite:對應(yīng)一個(gè)包含單個(gè)或多個(gè)測試用例文件的文件夾(測試用例可以是json、yaml) - 測試用例/
testcase:對應(yīng)一個(gè)包含單個(gè)或多個(gè)測試步驟的文件(文件可以是json、yaml) - 測試步驟/
teststep:對應(yīng)測試用例文件中的一個(gè)test,其中描述單個(gè)接口測試的全部內(nèi)容:包括請求內(nèi)容、解析響應(yīng)結(jié)果、校驗(yàn)結(jié)果等
PS.在單個(gè)測試用例,它的數(shù)據(jù)存儲結(jié)構(gòu)是list of dict的形式,里面可能包含一個(gè)全局配置項(xiàng)config和若干個(gè)測試步驟test
二、變量空間作用域/context作用域
在一個(gè)測試用例中,劃分了兩層變量空間作用域:
-
config:整個(gè)測試用例的全局配置項(xiàng),作用域?yàn)樗诘臏y試用例 -
test:對應(yīng)的是測試步驟,運(yùn)行時(shí)從前到后,依次執(zhí)行各個(gè)測試步驟
1.測試步驟中的變量空間,會繼承或覆蓋config中定義的內(nèi)容
2.如果變量在config定義了,在test中卻沒有定義,那么該test會繼承config所定義的變量值
3.如果變量在config和test中都定義了,那么當(dāng)前test會使用自己所定義的變量值 - 各個(gè)
test的變量空間是相互獨(dú)立,互不影響的 - 如果需要在多個(gè)
test中傳遞參數(shù)值,就需要用到extract關(guān)鍵字,并且只能從前往后進(jìn)行傳遞(解決了數(shù)據(jù)依賴問題)
三、全局變量config的字段介紹
| key | 必需? | 類型 | 介紹 |
|---|---|---|---|
| name | 是 | string | 測試用例的名稱,會在測試報(bào)告中作為標(biāo)題顯示 |
| variables | 否 | list / dict | 定義的全局變量,作用域是當(dāng)前測試用例 |
| parameters | 否 | list / dict | q全局參數(shù),作用域是當(dāng)前測試用例,用于實(shí)現(xiàn)數(shù)據(jù)化驅(qū)動 |
| request | 否 | dict | request的公共參數(shù),作用域是當(dāng)前測試用例,常用參數(shù)有base_url、headers |
官方例子(json):
"config": {
"name": "testcase description",
"parameters": [
{"user_agent": ["iOS/10.1", "iOS/10.2", "iOS/10.3"]},
{"app_version": "${P(app_version.csv)}"},
{"os_platform": "${get_os_platform()}"}
],
"variables": [
{"user_agent": "iOS/10.3"},
{"device_sn": "${gen_random_string(15)}"},
{"os_platform": "ios"}
],
"request": {
"base_url": "http://127.0.0.1:5000",
"headers": {
"Content-Type": "application/json",
"device_sn": "$device_sn"
}
},
"output": [
"token"
]
}
四、測試步驟test的字段介紹
| Key | 必需? | 類型 | 備注 |
|---|---|---|---|
| name | 是 | string | 測試步驟的名稱,會在測試報(bào)告中座位測試步驟的名稱顯示 |
| request | 是 | dict | http請求的詳細(xì)內(nèi)容,具體參數(shù)可見python.request |
| variables | 否 | list / dict | 變量,作用域?yàn)樗?code>test |
| extract | 否 | list | 從當(dāng)前請求的響應(yīng)結(jié)果中提取參數(shù),并保存在參數(shù)變量中(比如token),在后續(xù)測試用例就可以通$變量(比如$token)進(jìn)行引用 |
| validate | 否 | list | 結(jié)果校驗(yàn)項(xiàng),對當(dāng)前請求的響應(yīng)結(jié)果進(jìn)行判斷,作為當(dāng)前測試用例是否通過的依據(jù) |
| setup_hooks | 否 | list | 在發(fā)送請求之前先執(zhí)行hook函數(shù),主要用于準(zhǔn)備工作 |
| teardown_hooks | 否 | list | 在發(fā)送請求值后執(zhí)行hook函數(shù),主要用于測試完畢后的清理工作 |
| api | 否 | str | 引用接口定義,填寫對應(yīng)接口定義文件的絕對路徑或相對路徑,推薦使用相對路徑,根路徑是 debugtalk.py所在的目錄路徑。 |
| testcase | 否 | str | 引用其它測試用例,填寫測試用例的絕對路徑或相對路徑,推薦使用相對路徑,根路徑是debugtalk.py所在的目錄路徑。 |
| output | 否 | str / 其他? | 當(dāng)前測試步驟輸出的值,比如輸出token:output: - session_token
|
1.extract
根據(jù)響應(yīng)結(jié)果的數(shù)據(jù)結(jié)果,再采用不同的提取方式:
- 響應(yīng)結(jié)果為
json結(jié)構(gòu),則采用.來表示層級關(guān)系,比如說headers.Content-Type - 響應(yīng)結(jié)果為
text/html結(jié)構(gòu),則采用正則表達(dá)式來提取 - 詳情見:ApiTestEngine
2.validate
支持下面兩種格式(yaml):
# 1
- 判斷規(guī)則:"需判斷的屬性key","預(yù)期中的屬性value"
# 2
-{check:"需判斷的屬性key", comparator:"判斷規(guī)則", expect:"預(yù)期中的屬性value"}
- 判斷規(guī)則/
compatator
1.eq/equals/==/is:等于
2.lt/less_than:小于
3.le/less_than_or_equals:小于等于
4.gt/greater_than:大于
5.ge/greater_than_or_equals:大于等于
6.str_eq/string_equals:字符串相等
7.len_eq/count_eq/length_equals:長度等于
8.len_gt/length_greater_than:長度大于
9.len_ge/length_greater_than_or_equals:長度大于等于
10.len_lt:長度小于
11.len_le:長度小于等于
12.contains:包含
13.ne/not_equals:結(jié)果不相同
14.type_match:類型不相等
15.startswith:以xx開頭
16endswith:以xx結(jié)尾
3.預(yù)期結(jié)果/expect
顧名思義,填寫預(yù)期結(jié)果即可。是json層級關(guān)系,比如說請求的響應(yīng)內(nèi)容中code的值是0,那么“需判斷的屬性key是”content.code,“預(yù)期中的屬性value”是:0
五、hooks/鉤子
HttpRunner的鉤子機(jī)制分為兩個(gè)層面:
- 測試用例層面/
testcase - 測試步驟層面/
teststep
1.測試用例層面/testcase
在測試用例的config中提供了setup_hooks和teardown_hooks這兩個(gè)關(guān)鍵字
-
setup_hooks:在整個(gè)測試用例開始執(zhí)行前,先執(zhí)行setup_hooks所指定的函數(shù),用于測試開始前的準(zhǔn)備工作 -
teardown_hooks:在整個(gè)測試用例結(jié)束之后,再執(zhí)行teardown_hooks所指定的函數(shù),用于測試結(jié)束后的清理工作
- config:
name: basic test with httpbin
request:
base_url: http://127.0.0.1:3458/
setup_hooks:
- ${hook_print(setup)}
teardown_hooks:
- ${hook_print(teardown)}
2.測試步驟層面/teststep
同樣的,在測試步驟的test中提供了setup_hooks和teardown_hooks這兩個(gè)關(guān)鍵字
-
setup_hooks:在當(dāng)前測試步驟開始執(zhí)行前,先執(zhí)行setup_hooks所指定的函數(shù),用于準(zhǔn)備工作;也可以實(shí)現(xiàn)對請求的request內(nèi)容進(jìn)行預(yù)處理(比如添加一些請求頭之類的) -
teardown_hooks:在當(dāng)前測試步驟結(jié)束之后,再執(zhí)行teardown_hooks所指定的函數(shù),用于清理工作;也可以實(shí)現(xiàn)對響應(yīng)的response進(jìn)行修改(比如進(jìn)行解密處理)
"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)}"
}
},
"validate": [
{"eq": ["status_code", 200]}
],
"setup_hooks": [
"${setup_hook_prepare_kwargs($request)}",
"${setup_hook_httpntlmauth($request)}"
],
"teardown_hooks": [
"${teardown_hook_sleep_N_secs($response, 2)}"
]
}
3.hook函數(shù)/勾子函數(shù)的編寫
hook函數(shù)需要在debugtalk.py文件中,依舊是采用$(func($a, $b))的形式去調(diào)用hook函數(shù)
- 測試用例層面/
testcase的hook函數(shù)
在這層面的自定義鉤子函數(shù),可以通過自定義參數(shù)的方式來實(shí)現(xiàn)(就是和普通函數(shù)一樣,無要求) - 測試步驟層面/
teststep的hook函數(shù)
在這層面的自定義鉤子函數(shù),除了可以傳入自定義參數(shù)以外,還可以傳入請求$request和響應(yīng)$response
def hook_print(msg):
print(msg)
4.測試步驟層面的setup_hooks
除了可以傳入自定義參數(shù)以外,還可以傳入$request,對應(yīng)著當(dāng)前測試步驟$request的所有內(nèi)容。并且因?yàn)?code>$request是可變參數(shù)類型/dict,所以可以通過request["key"]的方式,靈活獲取request的信息。這讓在對請求參數(shù)進(jìn)行預(yù)處理時(shí)更加方便。
- 官網(wǎng)例子:(疑問:無需返回處理后的請求么?)
def setup_hook_prepare_kwargs(request):
"""
根據(jù)請求的Content-Type來對請求的data進(jìn)行加工處理
"""
if request["method"] == "POST":
content_type = request.get("headers", {}).get("content-type")
if content_type and "data" in request:
# if request content-type is application/json, request data should be dumped
if content_type.startswith("application/json") and isinstance(request["data"], (dict, list)):
request["data"] = json.dumps(request["data"])
if isinstance(request["data"], str):
request["data"] = request["data"].encode('utf-8')
def setup_hook_httpntlmauth(request):
"""
HttpNtlmAuth權(quán)限授權(quán)。
"""
if "httpntlmauth" in request:
from requests_ntlm import HttpNtlmAuth
auth_account = request.pop("httpntlmauth")
request["auth"] = HttpNtlmAuth(
auth_account["username"], auth_account["password"])
5.測試步驟層面的teardown_hooks
同樣的,在測試步驟層面的teardown_hooks函數(shù)中,除了可以傳入自定義參數(shù)以外,還可以傳入response,這個(gè)參數(shù)對應(yīng)當(dāng)前測試步驟的請求的響應(yīng),也就是requests.resposne。
經(jīng)常用于對響應(yīng)內(nèi)容進(jìn)行處理,比如說解密、參數(shù)運(yùn)算等,然后再進(jìn)行參數(shù)提取extract和參數(shù)校驗(yàn)validate
- 比如:首先通過下面這個(gè)函數(shù),把響應(yīng)結(jié)果的狀態(tài)碼和頭部信息進(jìn)行修改;然后再進(jìn)行校驗(yàn)
def alter_response(response):
response.status_code = 500
response.headers["Content-Type"] = "html/text"
- test:
name: alter response
request:
url: /headers
method: GET
teardown_hooks:
- ${alter_response($response)}
validate:
- eq: ["status_code", 500]
- eq: ["headers.content-type", "html/text"]