pytest -- allure-pytest插件的介绍和使用
简介:
allure-pytest
插件能将 pytest
测试框架生成的结果转换为 Allure Report 框架所需的原始数据文件,进而生成美观、详细、交互性强的测试报告。
特点:
- 交互式可视化 – 美观的动态图表,支持测试趋势分析。
- 步骤分层展示 – 清晰展示测试步骤、子步骤和附件(日志/截图)。
- 丰富上下文 – 支持环境信息、分类标签、优先级标记。
- 需配合Allure工具 – 需额外安装命令行工具生成HTML报告。
- 适合复杂项目 – 提供企业级详细报告,但配置略重。
准备:
安装插件
# 安装 Python 插件
pip install allure-pytest
安装 Allure 命令行工具 (必须!)
- Windows: 使用 Scoop 或 Chocolatey (scoop install allure)
- macOS: 使用 Homebrew (brew install allure)
- Linux: 使用 SDKMAN! (sdk install allure)
windows安装:
1、临时允许脚本运行(仅当前会话,避免安装scoop失败)
Set-ExecutionPolicy Bypass -Scope Process -Force
2、安装 Scoop
iex (new-object net.webclient).downloadstring('https://get.scoop.sh')
😕 要翻墙!否则一直downloading......
3、安装 Allure 命令行工具
scoop install allure
4、验证:
allure --version
装饰器(注解):
(1)测试用例描述类
注解 | 作用 | 示例 |
---|---|---|
@allure.title |
自定义测试用例标题 | @allure.title("用户登录:测试无效密码") |
@allure.description |
添加详细描述(支持Markdown) | @allure.description(""\"验证当密码为空时,系统返回400错误""\") |
@allure.description_html |
添加HTML格式描述 | @allure.description_html("<h3>测试前端组件</h3>") |
(2)测试分层结构类
注解 | 作用 | 示例 |
---|---|---|
@allure.epic |
定义宏观业务模块(顶层分类) | @allure.epic("电商平台") |
@allure.feature |
定义功能模块 | @allure.feature("购物车") |
@allure.story |
定义用户故事/子功能 | @allure.story("添加商品到购物车") |
@allure.parent_suite /@allure.suite /@allure.sub_suite |
自定义测试套件层级 | @allure.suite("API测试") |
(3)测试步骤类
注解 | 作用 | 示例 |
---|---|---|
@allure.step |
定义测试步骤(可嵌套) | 见下方详细示例 |
allure.dynamic.step |
动态生成步骤(运行时确定名称) | allure.dynamic.step(f"Step {i}") |
(4)其他增强类
注解 | 作用 | 示例 |
---|---|---|
@allure.severity |
标记用例优先级 | @allure.severity(allure.severity_level.CRITICAL) |
@allure.tag /@allure.label |
添加标签/自定义标签 | @allure.tag("smoke", "API") |
@allure.link /@allure.issue /@allure.testcase |
添加链接/问题追踪 | @allure.link(url="https://example.com", name="需求文档") |
allure.dynamic 方法
allure.dynamic
提供了动态设置 Allure 特性的能力,可以在测试执行期间根据实际运行情况来调整报告中的信息,而装饰器通常只用于配置固定不变的内容。
常用方法:
1、动态设置测试用例的标题
allure.dynamic.title("根据条件生成的测试标题")
2、动态设置测试用例的描述信息
allure.dynamic.description("这是一个详细的描述,解释了测试的具体内容。")
3、动态设置测试用例的严重性级别
allure.dynamic.severity(allure.severity_level.CRITICAL)
Allure 提供了 5 个内置的严重性等级,按从高到低排序如下:
级别 | 中文 | 说明 |
---|---|---|
BLOCKER |
阻塞 | 阻塞性别:最高优先级,通常指导致系统无法继续使用的缺陷 |
CRITICAL |
严重 | 关键级别:核心功能、主流程、必须通过的测试(如登录、支付) |
NORMAL |
一般 | 普通级别:常规功能测试,重要但不致命 |
MINOR |
较小 | 轻微级别:次要功能、UI 问题、不影响主流程 |
TRIVIAL |
轻微 | 琐碎级别:非常轻微的问题,如拼写错误、提示语优化 |
4、动态创建一个步骤,并在其内部执行代码块
with allure.step("执行某个操作"):
# 执行具体操作
pass
5、动态添加链接到报告中
allure.dynamic.link("http://example.com", "相关文档", link_type=allure.link_type.LINK)
6、动态关联外部测试用例管理系统中的用例
allure.dynamic.testcase("http://example.com/testcase/123")
allure.attach() 方法
allure.attach()
允许我们在测试报告中附加任意内容,比如文本、图片、日志、请求/响应数据等,从而让报告更加丰富、直观和具有可读性。
语法:
allure.attach(
body, # 要附加的内容
name=None, # 显示名称(可选)
attachment_type=None, # 附件类型(如文本、图片、JSON 等)
extension=None # 文件扩展名(可选)
)
示例:
import allure
import json
data = {
"url": "https://api.example.com/login",
"method": "POST",
"headers": {"Content-Type": "application/json"},
"body": {"username": "admin", "password": "123456"}
}
allure.attach(
json.dumps(data, indent=2, ensure_ascii=False),
"请求信息",
allure.attachment_type.JSON
)
environment.properties
environment.properties
是Allure 报告中用于展示测试环境信息的一个特殊文件,它可以让 Allure 报告首页显示当前测试运行的环境详情。
方式1:手动生成
import os
from datetime import datetime
from pathlib import Path
def generate_environment_properties():
env_data = {
"Environment": os.getenv("TEST_ENV", "Local"), # 从系统环境变量读取
"Browser": "Chrome",
"Browser.Version": "125.0.6422.78",
"OS": os.name,
"Test.Start.Time": datetime.now().strftime("%Y-%m-%d %H:%M:%S"),
"Python.Version": os.sys.version.split()[0],
"Allure.Version": "2.24.0"
}
# 确保结果目录存在
results_dir = Path(__file__).parent.parent / "reports" / "results"
os.makedirs(results_dir, exist_ok=True)
file_path = os.path.join(results_dir, "environment.properties")
with open(file_path, 'w', encoding='utf-8') as f:
for key, value in env_data.items():
f.write(f"{key}={value}\n")
print(f"Generated environment.properties at {file_path}")
if __name__ == "__main__":
generate_environment_properties()
方式2:结合jenkins设置变量
略,后续补充。
🚀️ 使用:
1、yml文件示例:
login_tests:
- id: "login_smoke"
description: "冒烟测试——账密登录"
severity: 5
request_data:
username: zhangsan
password: zs123456
packageName: com.test.a
expected_response:
code: 200
message: 登录成功
data:
userId: 10001
token: 1234567890
nike_name: 张三
- id: "login_password_error"
description: "异常验证——密码错误"
severity: 4
request_data:
username: zhangsan
password: zs1234566666
packageName: com.test.a
expected_response:
code: 101
message: 密码校验错误
2、编写allure工具类
# allure_utils.py Allure 报告工具
import allure
import json
from typing import Any
class AllureUtils:
@staticmethod
def attach_screenshot(name: str, png_data: bytes):
allure.attach(png_data, name, allure.attachment_type.PNG)
@staticmethod
def attach_json(name: str, data: Any):
allure.attach(
json.dumps(data, indent=2, ensure_ascii=False), # 格式化为标准 JSON
name,
allure.attachment_type.JSON
)
# 将请求参数、预期响应、实际响应封装一起
@staticmethod
def attach_request_expect_actual(request_data: dict, expected_result: dict, actual_result: dict):
"""
附加请求参数、预期结果、实际结果
:param request_data: 请求参数
:param expected_result: 预期结果
:param actual_result: 实际结果
"""
allure.attach(
json.dumps(request_data, indent=2, ensure_ascii=False),
"请求参数",
allure.attachment_type.JSON
)
allure.attach(
json.dumps(expected_result, indent=2, ensure_ascii=False),
"预期结果",
allure.attachment_type.JSON
)
allure.attach(
json.dumps(actual_result, indent=2, ensure_ascii=False),
"实际结果",
allure.attachment_type.JSON
)
@staticmethod
def attach_api_metadata(url: str, method: str, headers: dict):
"""
附加API元数据:包括接口地址、请求方式、请求头
:param url: 接口地址
:param method: 请求方式(GET, POST等)
:param headers: 请求头
"""
metadata = {
"URL": url,
"Method": method,
"Headers": headers
}
# 使用json.dumps()美化输出,并确保中文字符不乱码
metadata_str = json.dumps(metadata, indent=2, ensure_ascii=False)
allure.attach(
metadata_str,
"API 元数据",
allure.attachment_type.JSON
)
@staticmethod
def attach_api_time(start_time: float, end_time: float):
"""
将接口请求的开始时间、结束时间、耗时展示出来
:param start_time: 开始时间(time.time()值)
:param end_time: 结束时间(time.time()值)
"""
# 转为本地时间字符串
start_time_str = time.strftime("%Y-%m-%d %H:%M:%S", time.localtime(start_time))
end_time_str = time.strftime("%Y-%m-%d %H:%M:%S", time.localtime(end_time))
data = {
"请求开始时间": start_time_str,
"请求结束时间": end_time_str,
"总计耗时": f"{end_time - start_time: .3f}秒"
}
allure.attach(
json.dumps(data, indent=2, ensure_ascii=False),
"接口耗时分析",
allure.attachment_type.JSON
)
@staticmethod
def attach_title_description_severity(title: str, description: str, severity: int):
"""
附加标题、描述、严重级别
:param title: 标题
:param description: 描述
:param severity: 严重级别,支持 1~5 的整数
1=trivial, 2=minor, 3=normal, 4=critical, 5=blocker
"""
# 数字到 Allure severity 的映射
severity_map = {
1: allure.severity_level.TRIVIAL,
2: allure.severity_level.MINOR,
3: allure.severity_level.NORMAL,
4: allure.severity_level.CRITICAL,
5: allure.severity_level.BLOCKER,
}
allure.dynamic.title(title)
allure.dynamic.description(description)
allure.dynamic.severity(severity_map[severity])
3、编写全局fixture
# conftest.py
import httpx
import pytest
from utils.allure_utils import AllureUtils
@pytest.fixture()
def allure_report_supplement():
"""
目的:封装一些allure报告的内容,利用fixture减少测试用例中的冗余代码
嵌套函数:外层函数(fixture)返回一个内层函数(_report)
主要是为了“延迟执行”,简单地说就是等待被调用后才执行!
避免上下文缺失,如当前这个函数就要等client.sync_request()后,有了response参数再调用!
:return: _report函数
"""
def _report(
case_id: str,
case_description: str,
severity_level: str,
request_data: dict,
expected_response: dict,
response: httpx.Response,
actual_response: dict,
):
"""
封装 Allure 报告附加逻辑
"""
# 1. 标题、描述、严重级别
AllureUtils.add_title_description_severity(case_id, case_description, severity_level)
# 2. API 元数据(URL、Method、Headers)
AllureUtils.attach_api_metadata(
str(response.request.url),
response.request.method,
dict(response.request.headers)
)
# 3、请求开始时间、请求结束时间
AllureUtils.attach_api_time(response.request.start_time, response.end_time)
# 4. 请求、预期、实际响应
AllureUtils.attach_request_expect_actual(request_data, expected_response, actual_response)
return _report
4、HttpClient封装
# HTTP 请求封装
import time
import httpx
from httpx import Response
async def async_start_time(request):
request.start_time = time.time()
async def async_end_time(response):
response.end_time = time.time()
def sync_start_time(request):
request.start_time = time.time()
def sync_end_time(response):
response.end_time = time.time()
class HttpClient:
def __init__(self, base_url: str = "", timeout: int = 10):
self.base_url = base_url
self.timeout = timeout
# 初始化异步客户端
self._async_client = httpx.AsyncClient(
base_url=self.base_url,
timeout=self.timeout,
event_hooks={
"request": [async_start_time],
"response": [async_end_time]
}
)
# 初始化同步客户端
self._sync_client = httpx.Client(
base_url=self.base_url,
timeout=self.timeout,
event_hooks={
"request": [sync_start_time],
"response": [sync_end_time]
}
)
async def async_request(self, method: str, path: str, **kwargs) -> Response:
return await self._async_client.request(method, path, **kwargs)
def sync_request(self, method: str, path: str, **kwargs) -> Response:
return self._sync_client.request(method, path, **kwargs)
async def aclose(self):
await self._async_client.aclose()
def close(self):
self._sync_client.close()
async def __aenter__(self):
return self
async def __aexit__(self, exc_type, exc_val, exc_tb):
await self.aclose()
def __enter__(self):
return self
def __exit__(self, exc_type, exc_val, exc_tb):
self.close()
5、在测试用例中使用
import allure
import pytest
from utils.config_loader import ConfigLoader
from utils.http_client import HttpClient
@allure.epic("SDK3")
@allure.feature("sdk登录接口3")
class TestLogin:
# 提前加载测试数据
config_loader = ConfigLoader("data/test_sdk_login.yml")
login_tests = config_loader.get("login_tests", [])
@allure.story("用户登录3")
@pytest.mark.parametrize("test_case", login_tests, ids=[tc.get("id") for tc in login_tests])
def test_login_single(self, test_case, allure_report_supplement):
case_id = test_case.get("id", "unknown")
case_description = test_case.get("description", "unknown")
request_data = test_case["request_data"]
expected = test_case["expected_response"]
severity_level = test_case["severity"]
client = HttpClient("http://127.0.0.1:8080")
response = client.sync_request("POST", "/login", json=request_data)
json_response = response.json()
# 为 allure 报告补充内容
allure_report_supplement(
case_id=case_id,
case_description=case_description,
severity_level=severity_level,
request_data=request_data,
expected_response=expected,
response=response,
actual_response=json_response,
)
with allure.step("断言1:判断状态码是否相同"):
assert json_response["code"] == expected.get("code")
with allure.step("断言2:当状态码为200时,判断data不为空"):
if json_response["code"] == 200:
assert json_response["data"] is not None
with allure.step("断言3:判断message是否相同 或 message是否在details中存在"):
assert json_response.get("message") == expected.get("message") or expected.get("message") in str(
json_response.get("details"))
运行指令:
方式1:
①、运行测试并生成结果
pytest --alluredir=./reports/results [其他参数]
这会执行测试并在 ./reports/results
目录生成原始数据。
参数 | 说明 |
---|---|
--alluredir=PATH |
指定 Allure 结果文件的输出目录(生成 .json 文件) |
--clean-alluredir |
清空 --alluredir 指定的目录(避免旧结果干扰),建议和 --alluredir 一起使用 |
--allure-no-severity |
不生成 severity 标签 |
--allure-features=FEAT |
按 feature 过滤测试用例(执行指定 feature 的用例) |
--allure-stories=STORY |
按 story 过滤 |
--allure-epics=EPIC |
按 epic 过滤 |
--allure-ids=IDS |
按 allure ID 过滤 |
--allure-link-type=TYPE |
自定义 link 类型 |
②、生成HTML报告
allure generate ./reports/results -o ./reports/html --clean
-o
指定报告输出到./reports/html
目录--clean
清空已有报告目录
③、打开报告查看
allure open ./reports/html
这会启动本地Web服务器并自动打开浏览器
方式2(调试):
①、运行测试并生成结果
pytest --alluredir=./reports/serve
②、生成实时报告
allure serve ./reports/serve
实时从 ./reports/serve
目录查找数据并生成报告展示
参数补充:
选项 | 说明 |
---|---|
--port |
指定端口(默认:4040) |
--host |
绑定主机(默认:0.0.0.0) |
--clean |
清除历史结果 |
解释:
- 一步完成:自动完成
generate
+open
两个操作 - 临时服务:启动一个本地 Web 服务器(默认端口:4040)
- 自动清理:生成的报告是临时的(存放在系统临时目录)
何时使用?
- 本地开发调试:快速查看最新测试结果
- 演示效果:即时展示无需保存报告
- 临时分析:不需要保留历史记录时
方式3(推荐)
编写 .sh
脚本来运行
#!/bin/bash
# 配置路径
REPORT_DIR="./reports"
RESULTS_DIR="$REPORT_DIR/results"
HTML_DIR="$REPORT_DIR/html"
CONFIG_DIR="./config"
# 确保目录存在
mkdir -p $RESULTS_DIR $HTML_DIR
# 1. 生成环境变量文件
echo "Generating environment properties..."
python $CONFIG_DIR/generate_env.py
# 2. 复制历史数据(如果存在)
if [ -d "$HTML_DIR/history" ]; then
echo "Copying historical data..."
cp -r $HTML_DIR/history $RESULTS_DIR/
fi
# 3. 运行测试
echo "Running tests..."
pytest --alluredir=$RESULTS_DIR "$@"
TEST_EXIT_CODE=$?
# 4. 生成报告
echo "Generating Allure report..."
allure generate $RESULTS_DIR -o $HTML_DIR --clean
# 5. 显示报告信息
echo -e "\nAllure report generated at: file://$(pwd)/$HTML_DIR/index.html"
echo "To view report run:"
echo "allure open $HTML_DIR"
# 仅在交互式终端中等待,最多等 30 秒(专门给windows本地调试用,在linux上可以去掉)
if [ -t 0 ] && [ -z "$CI" ]; then
echo "Press Enter to continue..." >&2
read -r -t 30 || true # 30秒后自动继续,不报错
fi
# 6. 根据测试结果退出
exit $TEST_EXIT_CODE
报告效果:
总结:
pytest-html
插件的报告静态简洁,属于“快速出结果”类型;allure-pytest
插件的报告更强大但更复杂,属于“专业级分析报告”类型。
基于allure的特性封装个工具类,并且基于pytest的fixture装饰器减少用例中的allure代码冗余,搞测试报告还是比较好用的😄
评论区