HttpRunner 實作 hook 機制:
https://www.dazhuanlan.com/2019/11/23/5dd8ce9e262a7/
可以在請求前和請求後調用鈎子函數,相當于unittest的setup和teardown,測試前置,測試後置
hook 機制分為兩個層級:測試用例層面(testcase)、測試步驟層面(teststep)
(1)測試步驟層面:
# NOTE: Generated By HttpRunner v3.1.4
# FROM: hooks.yml
from httprunner import HttpRunner, Config, Step, RunRequest, RunTestCase
class TestCaseHooks(HttpRunner):
config = Config("basic test").base_url("${get_httpbin_server()}")
teststeps = [
Step(
RunRequest("headers")
.with_variables(**{"a": 123})
.setup_hook("${setup_hook_add_kwargs($request)}")
.get("/headers")
.teardown_hook("${teardown_hook_sleep_N_secs($response, 1)}")
.validate()
.assert_equal("status_code", 200)
.assert_contained_by("body.headers.Host", "${get_httpbin_server()}")
),
]
if __name__ == "__main__":
TestCaseHooks().test_start()
debugtalk.py
import requests
import os, time
from bs4 import BeautifulSoup
from httprunner import __version__
from libs.model_001 import *
from libs.model_002 import *
import json
# 可以實作根據請求方法和請求的 Content-Type 來對請求的 data 進行加工處理;
def setup_hook_prepare_kwargs(request):
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')
# 可以實作 HttpNtlmAuth 權限授權。
def setup_hook_httpntlmauth(request):
if "httpntlmauth" in request:
from requests_ntlm import HttpNtlmAuth
auth_account = request.pop("httpntlmauth")
request["auth"] = HttpNtlmAuth(
auth_account["username"], auth_account["password"])
# 根據接口響應的狀态碼來進行不同時間的延遲等待。
def teardown_hook_sleep_N_secs(response, n_secs):
""" sleep n seconds after request
"""
if response.status_code == 200:
time.sleep(0.1)
else:
time.sleep(n_secs)
# 當我們需要先對響應内容進行處理(例如加解密、參數運算),再進行參數提取(extract)和校驗(validate)時尤其有用
# 将響應結果的狀态碼和 headers 進行了修改,然後再進行了校驗。
def alter_response(response):
response.status_code = 500
response.headers["Content-Type"] = "html/text"
def get_execution(host):
# 從get登入的html中擷取post登入的變量值,傳遞給登入
res = requests.get(url=f"https://{host}:8888/cas/login", verify=False)
execution = BeautifulSoup(res.text, "lxml").find(attrs={"name":"execution"})["value"]
return execution
def get_httprunner_version():
return __version__
# 傳回一個清單
def get_user_id():
return [
{"user_id": 1001},
{"user_id": 1002},
{"user_id": 1003},
{"user_id": 1004}
]
# 具有關聯性的多個參數
def get_account(num):
accounts = []
for index in range(1, num+1):
accounts.append(
{"username": "user%s" % index, "password": str(index) * 6},
)
return accounts
if __name__ == '__main__':
get_execution()
(1)用例級别的測試前置
class TestBaiduRequestTestCase(HttpRunner):
def setup(self):
print("運作于測試用例之前")
def teardown(self):
print("運作于測試用例之後")
config = (
Config("get user list")
.base_url("https://www.baidu.com")
.verify(False)
)
teststeps = [
Step(
RunRequest("get info")
.get("/")
.validate()
.assert_equal("status_code", 200)
)
]
if __name__ == "__main__":
TestBaiduRequestTestCase().test_start()
結果為:
Process finished with exit code 0
運作于測試用例之前
PASSED [100%]2020-08-20 13:50:53.306 | INFO | httprunner.loader:load_dot_env_file:127 - Loading environment variables from D:\TestScriptDir\httprunner\interfaceDemo\.env
.
.
.
D:\TestScriptDir\httprunner\interfaceDemo\logs\a3872c1b-dedf-4485-bd95-3f31947bfae0.run.log
運作于測試用例之後
(1)用例級别的測試前置confest.py
import uuid
from typing import List
import pytest
from httprunner import Config, Step
from loguru import logger
@pytest.fixture(scope="session", autouse=True)
def session_fixture(request):
"""setup and teardown each task"""
total_testcases_num = request.node.testscollected
testcases = []
for item in request.node.items:
testcase = {
"name": item.cls.config.name,
"path": item.cls.config.path,
"node_id": item.nodeid,
}
testcases.append(testcase)
logger.debug(f"collected {total_testcases_num} testcases: {testcases}")
yield
logger.debug(f"teardown task fixture")
# teardown task
# TODO: upload task summary
@pytest.fixture(scope="function", autouse=True)
def testcase_fixture(request):
"""setup and teardown each testcase"""
config: Config = request.cls.config
teststeps: List[Step] = request.cls.teststeps
logger.debug(f"setup testcase fixture: {config.name} - {request.module.__name__}")
def update_request_headers(steps, index):
for teststep in steps:
if teststep.request:
index += 1
teststep.request.headers["X-Request-ID"] = f"{prefix}-{index}"
elif teststep.testcase and hasattr(teststep.testcase, "teststeps"):
update_request_headers(teststep.testcase.teststeps, index)
# you can update testcase teststep like this
prefix = f"HRUN-{uuid.uuid4()}"
update_request_headers(teststeps, 0)
yield
logger.debug(
f"teardown testcase fixture: {config.name} - {request.module.__name__}"
)
summary = request.instance.get_summary()
logger.debug(f"testcase result summary: {summary}")
# TODO: upload testcase summary
# NOTICE: Generated By HttpRunner.
import json
import os
import time
import pytest
from loguru import logger
from httprunner.utils import get_platform, ExtendJSONEncoder
@pytest.fixture(scope="session", autouse=True)
def session_fixture(request):
"""setup and teardown each task"""
logger.info(f"start running testcases ...")
start_at = time.time()
yield
logger.info(f"task finished, generate task summary for --save-tests")
summary = {
"success": True,
"stat": {
"testcases": {"total": 0, "success": 0, "fail": 0},
"teststeps": {"total": 0, "failures": 0, "successes": 0},
},
"time": {"start_at": start_at, "duration": time.time() - start_at},
"platform": get_platform(),
"details": [],
}
for item in request.node.items:
testcase_summary = item.instance.get_summary()
summary["success"] &= testcase_summary.success
summary["stat"]["testcases"]["total"] += 1
summary["stat"]["teststeps"]["total"] += len(testcase_summary.step_datas)
if testcase_summary.success:
summary["stat"]["testcases"]["success"] += 1
summary["stat"]["teststeps"]["successes"] += len(
testcase_summary.step_datas
)
else:
summary["stat"]["testcases"]["fail"] += 1
summary["stat"]["teststeps"]["successes"] += (
len(testcase_summary.step_datas) - 1
)
summary["stat"]["teststeps"]["failures"] += 1
testcase_summary_json = testcase_summary.dict()
testcase_summary_json["records"] = testcase_summary_json.pop("step_datas")
summary["details"].append(testcase_summary_json)
summary_path = "/Users/debugtalk/MyProjects/HttpRunner-dev/HttpRunner/examples/postman_echo/logs/request_methods/hardcode.summary.json"
summary_dir = os.path.dirname(summary_path)
os.makedirs(summary_dir, exist_ok=True)
with open(summary_path, "w", encoding="utf-8") as f:
json.dump(summary, f, indent=4, ensure_ascii=False, cls=ExtendJSONEncoder)
logger.info(f"generated task summary: {summary_path}")
日志庫:loguru
解讀:https://www.jb51.net/article/182175.htm
# 日志庫基礎應用
from loguru import logger
logger.add("interface_log_{time}.log", rotation="500MB", encoding="utf-8", enqueue=True, compression="zip",
retention="10 days")
logger.info("中文test")
logger.debug("中文test")
logger.error("中文test")
logger.warning("中文test")