序言
httprunner的使用和其中的一些概念在官方的文檔中已經(jīng)詳細說明赎瑰,寫這個學習記錄是為了記錄下自己的學習歷程羊瘩,能夠在今后快速的深入學習和實踐俩檬。沒有提及的部分要糊,請查看官方文檔
版本:2.2.5
參考
skip機制
skip類型
-
skip
: 無條件跳過測試 -
skipIf
: 判斷結(jié)果返回為true時纲熏,跳過測試 -
skipUnless
:判斷結(jié)果返回為false時,跳過測試
使用方法
在api
中增加skip
節(jié)點或在testcase
的teststeps
增加skip
節(jié)點杨耙。
-
skip
跳過測試,值為跳過測試的說明
-
api: api/sale/order_products.yml
skip: 無條件跳過
-
skipIf
跳過測試赤套,值為驗證方法,方法在debugtalk.py
中定義
debugtalk.py
def varIsNormal(var):
if var:
return False
return True
testcase
-
api: api/sale/order_list.yml
skipIf: ${varIsNormal($token)}
-
skipUnless
跳過測試珊膜,值為驗證方法容握,方法在debugtalk.py
中定義
-
api: api/sale/order_detail.yml
skipUnless: ${varIsNormal($token)}
運行機制,如果判斷test中有skip
车柠,skipIf
或skipUnless
剔氏,拋出SkipTest
異常。
def _handle_skip_feature(self, test_dict):
""" handle skip feature for test
- skip: skip current test unconditionally
- skipIf: skip current test if condition is true
- skipUnless: skip current test unless condition is true
Args:
test_dict (dict): test info
Raises:
SkipTest: skip test
"""
# TODO: move skip to initialize
skip_reason = None
if "skip" in test_dict:
skip_reason = test_dict["skip"]
elif "skipIf" in test_dict:
skip_if_condition = test_dict["skipIf"]
if self.session_context.eval_content(skip_if_condition):
skip_reason = "{} evaluate to True".format(skip_if_condition)
elif "skipUnless" in test_dict:
skip_unless_condition = test_dict["skipUnless"]
if not self.session_context.eval_content(skip_unless_condition):
skip_reason = "{} evaluate to False".format(skip_unless_condition)
if skip_reason:
raise SkipTest(skip_reason)
hook機制 ?
類型
setup_hooks
:請求前調(diào)用的鉤子函數(shù)竹祷,對請求進行預處理
teardown_hooks
:請求之后調(diào)用的鉤子函數(shù)谈跛,對請求進行后置處理
場景
對testcase生效的鉤子
- 在
testcase
的config
中使用的hook函數(shù),函數(shù)在debugtalk.py
中自由定義塑陵,他的運行是相對于整個測試用例的
setup_hooks
: 在整個用例開始執(zhí)行前觸發(fā) hook 函數(shù)感憾,主要用于準備工作
teardown_hooks
: 在整個用例結(jié)束執(zhí)行后觸發(fā) hook 函數(shù),主要用于測試后的清理工作
config:
name: 開發(fā)者登錄后注冊
base_url: https://api.apiopen.top
variables:
developer_name: o_chen
passwd: 123456
success_code: 200
setup_hooks:
- ${sleep(30)}
teardown_hooks:
- ${sleep(30)}
output:
- apikey
teststeps:
-
name: 開發(fā)者登錄
api: api/apiopen/developer_login.yml
def sleep(n_secs):
time.sleep(n_secs)
查看運行日志
- 在
teststeps
引用testcase
,也可以在節(jié)點中定義hook
令花,是對引用的testcase
生效的
config:
name: 開發(fā)者登錄后注冊
base_url: https://api.apiopen.top
variables:
developer_name: o_chen
passwd: 123456
success_code: 200
teststeps:
-
name: 開發(fā)者登錄
testcase: testcases/apiopen/login.yml
setup_hooks:
- ${sleep(30)}
teardown_hooks:
- ${sleep(30)}
對api生效的鉤子
在 api
文件中添加hooks節(jié)點阻桅,將在測試之前和測試之后運行
運行測試時,httprunner
會把當前執(zhí)行的請求和返回儲存當變量中兼都,取名request
和 response
.當需要對請求和返回做處理時傳入對應的變量名稱即可.當然嫂沉,也可以自由的執(zhí)行其他的函數(shù),在debugtalk.py
中編寫函數(shù)扮碧,在hooks中添加調(diào)用就行了趟章。
name: 開發(fā)者登錄
base_url: https://api.apiopen.top
variables:
developer_name: dev
passwd: 123456
success_code: 200
request:
method: POST
url: /developerLogin
data:
name: $developer_name
passwd: $passwd
extract:
apikey: content.result.apikey
validate:
- eq: [content.code,$success_code]
setup_hooks:
- ${hook_print($request)}
- ${sum_two(1,2)}
teardown_hooks:
- ${hook_print($response)}
運行結(jié)果:
在testcase
中對引入的api
文件添加hooks,將會與原來的文件中的hooks進行合并慎王、覆蓋
config:
name: 開發(fā)者登錄
base_url: https://api.apiopen.top
variables:
developer_name: o_chen
passwd: 123456
success_code: 200
teststeps:
-
name: 開發(fā)者登錄
api: api/developer_login.yml
setup_hooks:
- ${sum_two(1,2)}
teardown_hooks:
- ${sum_two(3,4)}
運行結(jié)果:
引用
編寫 hook 函數(shù) ?
hook 函數(shù)的定義放置在項目的
debugtalk.py
中蚓土,在 YAML/JSON 中調(diào)用 hook 函數(shù)仍然是采用${func($a, $b)}
的形式。setup_hooks ?
在測試步驟層面的 setup_hooks 函數(shù)中赖淤,除了可傳入自定義參數(shù)外北戏,還可以傳
入$request
,該參數(shù)對應著當前測試步驟 request 的全部內(nèi)容漫蛔。因為 request 是可變
參數(shù)類型(dict)嗜愈,因此該函數(shù)參數(shù)為引用傳遞,當我們需要對請求參數(shù)進行預處理時尤其有用莽龟。teardown_hooks ?
在測試步驟層面的 teardown_hooks 函數(shù)中蠕嫁,除了可傳入自定義參數(shù)外,還可以傳入
$response
毯盈,該參數(shù)對應著當前請求的響應實例(requests.Response)
另外剃毒,在 teardown_hooks 函數(shù)中還可以對 response 進行修改。當我們需要先對響應內(nèi)容進行處理(例如加解密搂赋、參數(shù)運算)赘阀,再進行參數(shù)提取(extract)和校驗(validate)時尤其有用脑奠。
例:
定義hooks函數(shù)修改請求參數(shù)和返回數(shù)據(jù)
def setupRequest(request):
if request:
if "data" in request:
request["data"]["name"] = "O_cheng"
def teardownResponse(response):
if response:
# print(response.json)
# print(response.content)
# print(response.status_code)
# print(response.cookies)
# print(response.elapsed)
# print(response.encoding)
# print(response.headers)
# print(response.history)
# print(response.ok)
# print(response.text)
# print(response.url)
# print(response.request)
response.json["code"] = -10000
在api
腳本中設(shè)置hooks函數(shù)調(diào)用
name: 開發(fā)者登錄
base_url: https://api.apiopen.top
variables:
developer_name: dev
passwd: 123456
success_code: 200
request:
method: POST
url: /developerLogin
data:
name: $developer_name
passwd: $passwd
extract:
apikey: content.result.apikey
validate:
- eq: [content.code,$success_code]
setup_hooks:
- ${setupRequest($request)}
teardown_hooks:
- ${teardownResponse($response)}
運行腳本文件基公,查看輸出的日志:
- 運行
setup_hooks
修改請求參數(shù)
- 運行
teardown_hooks
修改返回參數(shù)
擴展
在瀏覽源碼的時候,發(fā)現(xiàn)hooks支持將函數(shù)的運行結(jié)果賦值給變量宋欺。源碼如下:
def do_hook_actions(self, actions, hook_type):
""" call hook actions.
Args:
actions (list): each action in actions list maybe in two format.
format1 (dict): assignment, the value returned by hook function will be assigned to variable.
{"var": "${func()}"}
format2 (str): only call hook functions.
${func()}
hook_type (enum): setup/teardown
"""
logger.log_debug("call {} hook actions.".format(hook_type))
for action in actions:
if isinstance(action, dict) and len(action) == 1:
# format 1
# {"var": "${func()}"}
var_name, hook_content = list(action.items())[0]
hook_content_eval = self.session_context.eval_content(hook_content)
logger.log_debug(
"assignment with hook: {} = {} => {}".format(
var_name, hook_content, hook_content_eval
)
)
self.session_context.update_test_variables(
var_name, hook_content_eval
)
else:
# format 2
logger.log_debug("call hook function: {}".format(action))
# TODO: check hook function if valid
self.session_context.eval_content(action)
當定義hooks的格式為dict時可以實現(xiàn)將函數(shù)返回值添加到變量中
setup_hooks:
- var: ${func($request)}
但在實際運行過程中轰豆,在httprunner
檢查case時發(fā)生異常。具體問題如下: Issues
如果需要使用該功能齿诞,可以修改源碼酸休,以下修改僅供參考。
單獨運行api
在api
中編寫hook將返回值賦值給變量并引用變量報錯:httprunner.exceptions.VariableNotFound
修改源碼中parser.py
,在第958行添加:
hooks_mapping = {}
if "setup_hooks" in test_dict:
for hooks in test_dict["setup_hooks"]:
if isinstance(hooks, dict):
hooks_mapping.update(hooks)
if "teardown_hooks" in test_dict:
for hooks in test_dict["teardown_hooks"]:
if isinstance(hooks, dict):
hooks_mapping.update(hooks)
session_variables_set |= set(hooks_mapping.keys())
將hook
中定義的變量名稱添加到teststep_variables_set
中,teststep_variables_set
用于校驗testcase中引用的變量是否存在祷杈,在測試之前檢驗testcase是否是完整的斑司,避免在測試過程中出現(xiàn)異常,檢查testcase完整性的操作將在運行test之前完成
運行testcase
如果在hook將函數(shù)的返回值賦值給變量但汞,那么他在hook中將以dict的形式定義如:
setup_hooks:
- var: ${getRequest($request)}
- req: ${getRequest($request)}
- ${setupRequest($request)}
teardown_hooks:
- ${teardownResponse($response)}
- ${hook_print($var)}
- ${hook_print($req)}
在運行測試之前宿刮,httprunner
會將testcase
中定義的hooks與api
中定義的hooks進行合并去重,如果hooks中存在dict特占,將會報錯:TypeError: unhashable type: 'dict'
.原因是合并hooks使用的是set()
方法,在parser.py
的第816行
# merge & override setup_hooks
def_setup_hooks = api_def_dict.pop("setup_hooks", [])
ref_setup_hooks = test_dict.get("setup_hooks", [])
extended_setup_hooks = list(set(def_setup_hooks + ref_setup_hooks))
if extended_setup_hooks:
test_dict["setup_hooks"] = extended_setup_hooks
修改之前首先要對合并hooks的方法重新編寫糙置,區(qū)分dict和普通的hooks,普通的hook合并后去重是目,dict形式的谤饭,相同key名稱時,testcase中定義的覆蓋api中原因的定義懊纳,順序為dict在前揉抵,執(zhí)行方法并返回值到變量,之后在執(zhí)行普通hook,他們有可能應用之前定義的變量嗤疯。
在validator.py
中添加方法
def extend_hooks(raw_hooks, override_hooks):
""" extend raw_hooks with override_hooks.
override_hooks will merge and override raw_hooks.
Args:
raw_hooks (list):
override_hooks (list):
Returns:
list: extended hooks
Examples:
>>> def_setup_hooks = ["${hook_print($token)}", {"sign": "${getSign($token)}"}]
>>> ref_setup_hooks = [{"sign": "${getRandomSign($token)}"}, "${hook_print($sign)}"]
>>> extend_hooks(def_setup_hooks, ref_setup_hooks)
[
{'sign': '${getRandomSign($token)}'},
'${hook_print($token)}',
'${hook_print($sign)}'
]
"""
extended_hooks_dict = {}
extended_hooks_list = []
def separation(hooks):
for hook in hooks:
if isinstance(hook, dict):
extended_hooks_dict.update(hook)
else:
extended_hooks_list.append(hook)
separation(raw_hooks)
separation(override_hooks)
extended_hooks = [{k: v} for k, v in extended_hooks_dict.items()]
extended_hooks.extend(list(set(extended_hooks_list)))
return extended_hooks
之后將parser.py
中合并hooks的地方使用自定義的方法即可
# merge & override setup_hooks
def_setup_hooks = api_def_dict.pop("setup_hooks", [])
ref_setup_hooks = test_dict.get("setup_hooks", [])
# extended_setup_hooks = list(set(def_setup_hooks + ref_setup_hooks))
extended_setup_hooks = validator.extend_hooks(def_setup_hooks, ref_setup_hooks)
if extended_setup_hooks:
test_dict["setup_hooks"] = extended_setup_hooks
# merge & override teardown_hooks
def_teardown_hooks = api_def_dict.pop("teardown_hooks", [])
ref_teardown_hooks = test_dict.get("teardown_hooks", [])
# extended_teardown_hooks = list(set(def_teardown_hooks + ref_teardown_hooks))
extended_teardown_hooks = validator.extend_hooks(def_teardown_hooks, ref_teardown_hooks)
if extended_teardown_hooks:
test_dict["teardown_hooks"] = extended_teardown_hooks
將上述兩處修改完成后就可以在用例文件中將hooks返回的參數(shù)賦值給變量冤今,并在測試中使用這些變量了。