httprunner3 优化allure报告
前言
httprunner自带allure报告,由于项目个性化需求,原生报告不足够满足实际需要,故对httprunner3源码进行allure注解的添加,让你的报告更加丰满。
一、原生报告
直接执行用例,生成allure报告。
注:网上已经很多文章说明如何生成allure报告,在此不在赘述。
二、用例中添加注解
httprunner添加allure方式同pytest,如@allure.feature(“testcase with feature”)。通过@pytest.mark.parametrize参数化的方式,httprunner自动将参数打印在报告中。
@allure.feature("testcase with feature")
class TestCaseDemoTestcaseRef(HttpRunner):
@pytest.mark.parametrize(
"param",
Parameters(
{"foo1": ["bar1", "bar2"]}
),
)
def test_start(self, param):
super().test_start(param)
config = (
Config("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 = [
# allure.step("step 1"),
Step(
RunTestCase("request with functions")
.with_variables(
**{"foo1": "testcase_ref_bar1", "expect_foo1": "testcase_ref_bar1"}
)
.call(DemoTestcaseRequest)
.export(*["foo3"])
),
# allure.step("step 2"),
Step(
RunRequest("post form data")
.with_variables(**{"foo1": "bar1"})
.post("/post")
.with_headers(
**{
"User-Agent": "HttpRunner/${get_httprunner_version()}",
"Content-Type": "application/x-www-form-urlencoded",
}
)
.with_data("foo1=$foo1&foo2=$foo3")
.validate()
.assert_equal("status_code", 200)
.assert_equal("body.form.foo1", "$foo1")
.assert_equal("body.form.foo2", "bar21")
),
]
- 效果如下
三、源码添加注解
1.添加日志
添加用例执行日志,仅包含该用例执行的日志。在runner.py的test_start方法中添加:
allure.attach.file(self.__log_path, name='log', attachment_type=allure.attachment_type.TEXT)
- 代码如下
# runner.py
def test_start(self, param: Dict = None) -> "HttpRunner":
"""main entrance, discovered by pytest"""
self.__init_tests__()
self.__project_meta = self.__project_meta or load_project_meta(
self.__config.path
)
self.__case_id = self.__case_id or str(uuid.uuid4())
self.__log_path = self.__log_path or os.path.join(
self.__project_meta.RootDir, "logs", f"{self.__case_id}.run.log"
)
log_handler = logger.add(self.__log_path, level="DEBUG")
# parse config name
config_variables = self.__config.variables
if param:
config_variables.update(param)
config_variables.update(self.__session_variables)
self.__config.name = parse_data(
self.__config.name, config_variables, self.__project_meta.functions
)
if USE_ALLURE:
# update allure report meta
allure.dynamic.title(self.__config.name)
allure.dynamic.description(f"TestCase ID: {self.__case_id}")
logger.info(
f"Start to run testcase: {self.__config.name}, TestCase ID: {self.__case_id}"
)
try:
return self.run_testcase(
TestCase(config=self.__config, teststeps=self.__teststeps)
)
finally:
allure.attach.file(self.__log_path, name='log', attachment_type=allure.attachment_type.TEXT)
logger.remove(log_handler)
logger.info(f"generate testcase log: {self.__log_path}")
- 实际效果:
2.step展示请求数据
在每个step下打印请求与返回的数据。step为RunTestCase类型则遍历打印引用用例中的step。在runner.py的__run_step_request方法中添加:
# log request
allure_msg += "======================== request details ========================\n"
allure_msg += f"➩url: {url}\n\n"
allure_msg += f"➩method: {method}\n\n"
headers = parsed_request_dict.pop("➩headers", {})
allure_msg += f"➩headers: {headers}\n\n"
for k, v in parsed_request_dict.items():
v = utils.omit_long_data(v)
allure_msg += f"➩{k}: {repr(v)}\n"
allure_msg += "\n"
# log response
allure_msg += "======================== response details ========================\n"
allure_msg += f"➩status_code: {resp.status_code}\n\n"
allure_msg += f"➩headers: {resp.headers}\n\n"
allure_msg += f"➩body: {repr(resp.text)}\n\n"
allure.attach(allure_msg, name="requests info", attachment_type=allure.attachment_type.TEXT)
注:需要先定义allure_msg=’’,打印的据及展示方式根据实际需求设置。
- 修改后的源码如下
# runner.py
def __run_step_request(self, step: TStep) -> StepData:
"""run teststep: request"""
allure_msg = ''
step_data = StepData(name=step.name)
# parse
prepare_upload_step(step, self.__project_meta.functions)
request_dict = step.request.dict()
request_dict.pop("upload", None)
parsed_request_dict = parse_data(
request_dict, step.variables, self.__project_meta.functions
)
parsed_request_dict["headers"].setdefault(
"HRUN-Request-ID",
f"HRUN-{self.__case_id}-{str(int(time.time() * 1000))[-6:]}",
)
step.variables["request"] = parsed_request_dict
# setup hooks
if step.setup_hooks:
self.__call_hooks(step.setup_hooks, step.variables, "setup request")
# prepare arguments
method = parsed_request_dict.pop("method")
url_path = parsed_request_dict.pop("url")
url = build_url(self.__config.base_url, url_path)
parsed_request_dict["verify"] = self.__config.verify
parsed_request_dict["json"] = parsed_request_dict.pop("req_json", {})
# request
resp = self.__session.request(method, url, **parsed_request_dict)
resp_obj = ResponseObject(resp)
step.variables["response"] = resp_obj
# teardown hooks
if step.teardown_hooks:
self.__call_hooks(step.teardown_hooks, step.variables, "teardown request")
def log_req_resp_details():
err_msg = "\n{} DETAILED REQUEST & RESPONSE {}\n".format("*" * 32, "*" * 32)
# log request
err_msg += "====== request details ======\n"
err_msg += f"url: {url}\n"
err_msg += f"method: {method}\n"
headers = parsed_request_dict.pop("headers", {})
err_msg += f"headers: {headers}\n"
for k, v in parsed_request_dict.items():
v = utils.omit_long_data(v)
err_msg += f"{k}: {repr(v)}\n"
err_msg += "\n"
# log response
err_msg += "====== response details ======\n"
err_msg += f"status_code: {resp.status_code}\n"
err_msg += f"headers: {resp.headers}\n"
err_msg += f"body: {repr(resp.text)}\n"
logger.error(err_msg)
# extract
extractors = step.extract
extract_mapping = resp_obj.extract(extractors)
step_data.export_vars = extract_mapping
variables_mapping = step.variables
variables_mapping.update(extract_mapping)
# validate
validators = step.validators
session_success = False
try:
resp_obj.validate(
validators, variables_mapping, self.__project_meta.functions
)
session_success = True
except ValidationFailure:
session_success = False
log_req_resp_details()
# log testcase duration before raise ValidationFailure
self.__duration = time.time() - self.__start_at
raise
finally:
self.success = session_success
step_data.success = session_success
if hasattr(self.__session, "data"):
# httprunner.client.HttpSession, not locust.clients.HttpSession
# save request & response meta data
self.__session.data.success = session_success
self.__session.data.validators = resp_obj.validation_results
# save step data
step_data.data = self.__session.data
# log request
allure_msg += "======================== request details ========================\n"
allure_msg += f"➩url: {url}\n\n"
allure_msg += f"➩method: {method}\n\n"
headers = parsed_request_dict.pop("➩headers", {})
allure_msg += f"➩headers: {headers}\n\n"
for k, v in parsed_request_dict.items():
v = utils.omit_long_data(v)
allure_msg += f"➩{k}: {repr(v)}\n"
allure_msg += "\n"
# log response
allure_msg += "======================== response details ========================\n"
allure_msg += f"➩status_code: {resp.status_code}\n\n"
allure_msg += f"➩headers: {resp.headers}\n\n"
allure_msg += f"➩body: {repr(resp.text)}\n\n"
allure.attach(allure_msg, name="requests info", attachment_type=allure.attachment_type.TEXT)
return step_data
- 效果如下
总结
初学httprunner3,文章会持续更新,欢迎大神的指正和指导,感谢!