基于
allure-pytest2.13+ | Python 3.8+ | Allure 2.x | Windows 10/11
目录
- 安装与环境搭建
- 运行测试与生成报告
- 描述类注解
- 分类与层级标签(Epic / Feature / Story)
- 严重程度(Severity)
- 链接注解(Link / Issue / TmsLink)
- 测试步骤(Step)
- 附件(Attachment)
- 参数化测试
- 动态更新(allure.dynamic)
- Fixture 与 Allure 集成
- conftest.py 全局配置
- 环境信息配置
- 失败分类配置(Categories)
- 历史趋势
- 测试筛选与过滤
- 完整项目示例
- 常见问题与排查
- 速查表
1. 安装与环境搭建
1.1 安装 Python 依赖
bash
pip install pytest allure-pytest
# 验证安装
pip show allure-pytest
pip show pytest
1.2 安装 Allure CLI(Windows)
Allure CLI 依赖 Java,需先安装 JDK。
第一步:安装 Java(JDK 8 或以上)
前往 https://www.oracle.com/java/technologies/downloads/ 下载安装,或使用 OpenJDK:
powershell
# 验证 Java 是否安装成功
java -version
# 输出类似:java version "17.0.9" 2023-10-17 LTS
第二步:安装 Allure CLI
方式一:使用 Scoop(推荐,自动配置 PATH)
powershell
# 先安装 Scoop(如未安装)
Set-ExecutionPolicy -ExecutionPolicy RemoteSigned -Scope CurrentUser
Invoke-RestMethod -Uri https://get.scoop.sh | Invoke-Expression
# 安装 Allure
scoop install allure
方式二:手动安装
- 前往 https://github.com/allure-framework/allure2/releases 下载最新的
allure-2.x.x_windows.zip - 解压到指定目录,例如
C:\allure-2.27.0 - 将
C:\allure-2.27.0\bin添加到系统环境变量PATH:- 右键"此电脑" → 属性 → 高级系统设置 → 环境变量
- 在"系统变量"中找到
Path,点击编辑 → 新建 → 输入C:\allure-2.27.0\bin - 确定保存,重启 PowerShell / CMD
第三步:验证安装
powershell
allure --version
# 输出类似:2.27.0
1.3 项目结构
my_project/
├── tests/
│ ├── conftest.py
│ ├── test_login.py
│ ├── test_cart.py
│ └── api/
│ └── test_user_api.py
├── utils/
│ └── helpers.py
├── allure-results/ # 自动生成,存放 JSON 结果
├── allure-report/ # 自动生成,存放 HTML 报告
├── categories.json # 失败分类配置
├── environment.properties # 环境信息配置
├── pytest.ini
└── requirements.txt
1.4 pytest.ini 基础配置
[pytest]
addopts=
--alluredir=allure-results
--clean-alluredir
testpaths= tests
--alluredir:指定结果文件输出目录
-clean-alluredir:每次运行前清空该目录,避免旧结果混入
也支持 pyproject.toml:
toml
[tool.pytest.ini_options]
addopts = "--alluredir=allure-results --clean-alluredir"
testpaths = ["tests"]
2. 运行测试与生成报告
2.1 工作原理
pytest 运行 → allure-results/*.json → allure generate → allure-report/index.html
- pytest 运行时,
allure-pytest插件会将每个测试的结果写入allure-results目录(JSON 格式) allure generate命令读取这些 JSON,生成静态 HTML 报告
2.2 常用命令(PowerShell / CMD)
powershell
# 运行所有测试,结果输出到 allure-results
pytest tests\
# 指定结果目录(不使用 pytest.ini 时)
pytest tests\ --alluredir=allure-results
# 每次运行前清空旧结果
pytest tests\ --alluredir=allure-results --clean-alluredir
# 生成 HTML 报告
allure generate allure-results -o allure-report --clean
# 用浏览器打开已生成的报告(会自动启动本地服务)
allure open allure-report
# 【推荐,开发调试】一步到位:生成报告并立即打开
allure serve allure-results
# 指定端口和地址
allure serve allure-results -p 9999 -h 127.0.0.1
⚠️ 不要直接双击 index.html 打开,浏览器会因 CORS 限制显示空白页。
必须通过
allure open或allure serve命令启动本地服务后访问。
2.3 一键脚本(Windows .bat)
在项目根目录创建 run_test.bat:
bash
@echo off
echo ========== 清空旧结果 ==========
if exist allure-results rmdir /s /q allure-results
if exist allure-report rmdir /s /q allure-report
echo ========== 运行测试 ==========
pytest tests\ --alluredir=allure-results
echo ========== 生成报告 ==========
allure generate allure-results -o allure-report --clean
echo ========== 打开报告 ==========
allure open allure-report
pause
双击即可完成"运行测试 → 生成报告 → 打开浏览器"全流程。
2.4 报告目录结构
allure-results/ ← pytest 生成(原始数据)
├── abc123-result.json ← 每个测试用例的详细数据
├── xyz456-container.json ← 测试套件/Fixture 的数据
├── attach-001.png ← 附件文件
├── environment.properties
└── categories.json
allure-report/ ← allure generate 生成(静态网站)
├── index.html ← 入口(必须通过服务访问)
├── data/
├── history/
└── widgets/
3. 描述类注解
这类注解用于为测试用例添加可读性信息,显示在报告的测试详情页。
3.1 @allure.title --- 测试标题
默认情况下报告使用函数名作为标题,@allure.title 可以让标题更具业务含义。
python
import allure
# 静态标题
@allure.title("用户使用正确密码登录成功")
def test_login_with_correct_password():
pass
# 标题中引用参数(配合参数化使用)
@allure.title("用户{username} 登录测试")
@pytest.mark.parametrize("username, password", [
("admin", "admin123"),
("user01", "pass456"),
])
def test_login_parametrize(username, password):
pass
# 报告中标题会变成:
# "用户 admin 登录测试"
# "用户 user01 登录测试"
3.2 @allure.description --- 测试描述
python
# 方式一:装饰器
@allure.description("""
测试场景:用户登录功能
前置条件:用户账号已注册且处于正常状态
测试步骤:
1. 打开登录页面
2. 输入正确的用户名和密码
3. 点击登录按钮
预期结果:页面跳转到首页,显示欢迎信息
""")
def test_user_login():
pass
# 方式二:直接写函数的 docstring(更简洁)
def test_user_logout():
"""
测试用户正常退出登录。
退出后应跳转到登录页,且 session 失效。
"""
pass
3.3 @allure.description_html --- 富文本描述
python
@allure.description_html("""
<h3>接口测试说明</h3>
<table border="1" cellpadding="5">
<tr><th>字段</th><th>说明</th></tr>
<tr><td>username</td><td>用户名,长度 4-20</td></tr>
<tr><td>password</td><td>密码,长度 6-16</td></tr>
</table>
<p><b>注意:</b>密码字段不可为空</p>
""")
def test_login_api_fields():
pass
4. 分类与层级标签
这是 Allure 最核心的功能之一,使报告支持按业务维度导航(Behaviors 视图)。
4.1 层级结构
@allure.epic ← 史诗(最顶层,对应业务线/产品)
└── @allure.feature ← 功能模块
└── @allure.story ← 用户故事(具体场景)
└── 测试用例
4.2 基本用法
python
import allure
import pytest
@allure.epic("电商平台")
@allure.feature("用户中心")
@allure.story("账号登录")
def test_login_success():
pass
@allure.epic("电商平台")
@allure.feature("用户中心")
@allure.story("账号注册")
def test_register():
pass
@allure.epic("电商平台")
@allure.feature("商品管理")
@allure.story("商品搜索")
def test_search_product():
pass
4.3 类级别标注(推荐)
python
@allure.epic("电商平台")
@allure.feature("购物车")
class TestShoppingCart:
@allure.story("添加商品")
def test_add_item(self):
pass
@allure.story("删除商品")
def test_remove_item(self):
pass
@allure.story("修改数量")
def test_update_quantity(self):
pass
@allure.story("清空购物车")
def test_clear_cart(self):
pass
4.4 @allure.tag --- 自定义标签
python
@allure.tag("smoke")
@allure.tag("regression")
@allure.tag("P0")
def test_core_login():
pass
# 一次添加多个 tag
@allure.tag("smoke", "regression", "cross-browser")
def test_homepage_load():
pass
4.5 @allure.label --- 自定义标签键值对
python
# 自定义任意维度的标签
@allure.label("layer", "api") # 测试层级:api / ui / unit
@allure.label("owner", "张三") # 负责人
@allure.label("component", "login") # 组件
def test_login_api():
pass
5. 严重程度(Severity)
5.1 五种级别
| 级别 | 说明 | 使用场景示例 |
|---|---|---|
BLOCKER |
阻断级,系统无法使用 | 登录接口报 500、页面打不开 |
CRITICAL |
严重,核心功能异常 | 支付失败、订单创建失败 |
NORMAL |
普通(默认值),功能性缺陷 | 数据展示不完整 |
MINOR |
次要,不影响主流程 | 按钮样式偏移、文字截断 |
TRIVIAL |
轻微,几乎无影响 | 错别字、提示语不规范 |
5.2 用法示例
python
import allure
@allure.severity(allure.severity_level.BLOCKER)
def test_app_can_start():
"""应用能否正常启动"""
pass
@allure.severity(allure.severity_level.CRITICAL)
def test_user_payment():
"""用户支付核心流程"""
pass
@allure.severity(allure.severity_level.NORMAL)
def test_user_profile_update():
"""用户资料更新"""
pass
@allure.severity(allure.severity_level.MINOR)
def test_button_style():
"""按钮样式校验"""
pass
@allure.severity(allure.severity_level.TRIVIAL)
def test_footer_copyright_text():
"""页脚版权文字校验"""
pass
6. 链接注解
6.1 三种链接类型
python
import allure
# 通用链接(任意 URL)
@allure.link("https://example.com/docs/login", name="登录接口文档")
def test_login_api():
pass
# 缺陷链接(对应 Bug 追踪系统)
@allure.issue("BUG-1234", "登录页面验证码不显示")
def test_captcha_display():
pass
# 测试用例管理链接(对应 TMS 系统)
@allure.testcase("TC-5678", "登录功能正向测试用例")
def test_login_positive():
pass
# 组合使用
@allure.link("https://wiki.example.com/login", name="需求文档")
@allure.issue("BUG-2001")
@allure.testcase("TC-1001")
@allure.severity(allure.severity_level.CRITICAL)
def test_login_full():
pass
6.2 配置链接模板(自动拼接 URL)
在 pytest.ini 中配置模板,这样 @allure.issue("BUG-123") 会自动生成完整链接:
[pytest]
addopts= --alluredir=allure-results --clean-alluredir
allure_link_pattern=
issue:https://jira.yourcompany.com/browse/{}
tms:https://testrail.yourcompany.com/index.php?/cases/view/{}
配置后,代码中只需写 ID,报告会自动补全 URL:
python
@allure.issue("BUG-1234") # → https://jira.yourcompany.com/browse/BUG-1234
@allure.testcase("TC-5678") # → https://testrail.yourcompany.com/index.php?/cases/view/TC-5678
def test_example():
pass
7. 测试步骤(Step)
Step 是 Allure 中最常用的功能,将测试逻辑分解为清晰的步骤,方便定位失败原因。
7.1 方式一:with 上下文管理器(推荐)
适合在测试函数内部按步骤组织逻辑。
python
import allure
import requests
def test_create_order():
with allure.step("第一步:登录获取 token"):
response = requests.post("https://api.example.com/login", json={
"username": "test_user",
"password": "Test@123"
})
token = response.json()["token"]
assert response.status_code == 200
with allure.step("第二步:查询商品库存"):
response = requests.get(
"https://api.example.com/products/SKU001",
headers={"Authorization": f"Bearer{token}"}
)
assert response.json()["stock"] > 0
with allure.step("第三步:创建订单"):
response = requests.post(
"https://api.example.com/orders",
headers={"Authorization": f"Bearer{token}"},
json={"product_id": "SKU001", "quantity": 2}
)
order_id = response.json()["order_id"]
assert response.status_code == 201
with allure.step("第四步:验证订单状态为 pending"):
response = requests.get(
f"https://api.example.com/orders/{order_id}",
headers={"Authorization": f"Bearer{token}"}
)
assert response.json()["status"] == "pending"
7.2 方式二:@allure.step 装饰器(适合封装可复用步骤)
步骤名称中可以用 {参数名} 引用函数参数,报告中会显示实际值。
python
import allure
@allure.step("打开登录页面")
def open_login_page(driver):
driver.get("https://example.com/login")
@allure.step("输入用户名:{username}")
def input_username(driver, username):
driver.find_element("id", "username").send_keys(username)
@allure.step("输入密码(已脱敏)")
def input_password(driver, password):
# 密码不传入步骤名,避免泄露
driver.find_element("id", "password").send_keys(password)
@allure.step("点击登录按钮")
def click_login(driver):
driver.find_element("id", "login-btn").click()
@allure.step("验证跳转到首页")
def verify_redirect_to_home(driver):
assert "dashboard" in driver.current_url
# 在测试用例中调用
def test_ui_login(driver):
open_login_page(driver)
input_username(driver, "admin")
input_password(driver, "Admin@123")
click_login(driver)
verify_redirect_to_home(driver)
7.3 步骤嵌套
步骤可以无限嵌套,报告中会以树形结构展示。
python
def test_full_purchase():
with allure.step("一、用户登录"):
with allure.step("打开登录页"):
pass
with allure.step("填写凭据"):
pass
with allure.step("提交并验证 token"):
pass
with allure.step("二、选购商品"):
with allure.step("搜索关键词"):
pass
with allure.step("选择第一个商品"):
pass
with allure.step("加入购物车"):
pass
with allure.step("三、结算支付"):
with allure.step("确认收货地址"):
pass
with allure.step("选择支付方式"):
pass
with allure.step("完成支付"):
pass
with allure.step("四、验证订单"):
with allure.step("进入订单列表"):
pass
with allure.step("确认最新订单状态为已支付"):
pass
7.4 步骤中添加附件
步骤内部可以直接附加信息,这些附件会挂载在该步骤下。
python
def test_api_response():
with allure.step("发送请求"):
response = requests.get("https://jsonplaceholder.typicode.com/users/1")
# 在步骤内附加请求和响应信息
allure.attach(
f"GET https://jsonplaceholder.typicode.com/users/1\nStatus:{response.status_code}",
name="请求详情",
attachment_type=allure.attachment_type.TEXT
)
allure.attach(
response.text,
name="响应体",
attachment_type=allure.attachment_type.JSON
)
with allure.step("验证响应"):
assert response.status_code == 200
assert response.json()["id"] == 1
8. 附件(Attachment)
附件让报告更直观,可以附加截图、日志、请求响应、HTML 片段等。
8.1 allure.attach() --- 直接附加内容
python
import allure
import json
def test_with_attachments():
# ① 纯文本
allure.attach(
"这是一段测试日志\n执行步骤:登录 → 下单 → 支付",
name="执行日志",
attachment_type=allure.attachment_type.TEXT
)
# ② JSON 数据(自动格式化展示)
data = {"user": "admin", "role": "superadmin", "status": "active"}
allure.attach(
json.dumps(data, indent=2, ensure_ascii=False),
name="用户数据",
attachment_type=allure.attachment_type.JSON
)
# ③ HTML 片段
allure.attach(
"<h2 style='color:green'>✅ 测试通过</h2><p>所有断言均已通过</p>",
name="测试结果摘要",
attachment_type=allure.attachment_type.HTML
)
# ④ XML 内容
xml_content = "<response><code>200</code><message>OK</message></response>"
allure.attach(
xml_content,
name="XML 响应",
attachment_type=allure.attachment_type.XML
)
# ⑤ CSV 数据
csv_data = "id,name,email\n1,张三,zhang@example.com\n2,李四,li@example.com"
allure.attach(
csv_data,
name="测试数据",
attachment_type=allure.attachment_type.CSV
)
# ⑥ 图片(PNG 字节流)
with open("screenshot.png", "rb") as f:
allure.attach(
f.read(),
name="页面截图",
attachment_type=allure.attachment_type.PNG
)
8.2 allure.attach.file() --- 附加本地文件
python
def test_attach_files():
# 附加日志文件
allure.attach.file(
r"C:\logs\app.log",
name="应用日志",
attachment_type=allure.attachment_type.TEXT
)
# 附加截图文件
allure.attach.file(
"screenshots\\login_page.png",
name="登录页截图",
attachment_type=allure.attachment_type.PNG
)
# 附加 HTML 报告片段
allure.attach.file(
"response.html",
name="响应页面",
attachment_type=allure.attachment_type.HTML
)
8.3 支持的所有附件类型
python
allure.attachment_type.TEXT # 纯文本 .txt
allure.attachment_type.HTML # HTML .html
allure.attachment_type.XML # XML .xml
allure.attachment_type.JSON # JSON .json
allure.attachment_type.CSV # CSV .csv
allure.attachment_type.TSV # TSV .tsv
allure.attachment_type.URI_LIST # URI .uri
allure.attachment_type.PNG # 图片 .png
allure.attachment_type.JPG # 图片 .jpg
allure.attachment_type.SVG # 矢量图 .svg
allure.attachment_type.GIF # 动图 .gif
allure.attachment_type.BMP # 位图 .bmp
allure.attachment_type.TIFF # 位图 .tiff
allure.attachment_type.MP4 # 视频 .mp4
allure.attachment_type.OGG # 视频 .ogg
allure.attachment_type.WEBM # 视频 .webm
allure.attachment_type.PDF # PDF .pdf
allure.attachment_type.HAR # 网络日志 .har
8.4 UI 测试自动截图(Selenium)
python
import allure
import pytest
from selenium import webdriver
@pytest.fixture
def driver():
d = webdriver.Chrome()
yield d
d.quit()
def test_selenium_with_screenshot(driver):
driver.get("https://example.com")
with allure.step("验证页面标题"):
title = driver.title
# 截图附加到当前步骤
allure.attach(
driver.get_screenshot_as_png(),
name="当前页面截图",
attachment_type=allure.attachment_type.PNG
)
assert "Example" in title
8.5 失败时自动截图(conftest.py 全局配置)
详见第 12 节 conftest.py 的完整配置。
9. 参数化测试
9.1 基本参数化
python
import allure
import pytest
@pytest.mark.parametrize("username, password, expected_code", [
("admin", "Admin@123", 200),
("admin", "wrongpass", 401),
("nonexistent", "anypass", 401),
("", "Admin@123", 400),
("admin", "", 400),
])
def test_login_api(username, password, expected_code):
"""每组参数在报告中显示为独立的测试用例"""
response = requests.post(
"https://api.example.com/login",
json={"username": username, "password": password}
)
assert response.status_code == expected_code
9.2 参数化 + 动态标题
python
@pytest.mark.parametrize("username, password, expected_code", [
("admin", "Admin@123", 200),
("admin", "wrongpass", 401),
("", "Admin@123", 400),
])
@allure.title("登录测试 - 用户名:{username} 密码:{password} 期望状态码:{expected_code}")
def test_login_with_title(username, password, expected_code):
pass
# 报告标题变为:
# "登录测试 - 用户名:admin 密码:Admin@123 期望状态码:200"
# "登录测试 - 用户名:admin 密码:wrongpass 期望状态码:401"
9.3 用 pytest.param 添加 ID 和标记
python
@pytest.mark.parametrize("username, password, expected", [
pytest.param("admin", "Admin@123", True,
id="正常登录",
marks=pytest.mark.smoke),
pytest.param("admin", "wrongpass", False,
id="密码错误",
marks=pytest.mark.regression),
pytest.param("", "Admin@123", False,
id="用户名为空",
marks=[pytest.mark.regression, pytest.mark.xfail]),
])
def test_login_scenarios(username, password, expected):
result = do_login(username, password)
assert result == expected
9.4 参数化 + 数据驱动(从文件读取)
python
import json
import pytest
import allure
# 从 JSON 文件加载测试数据
def load_test_data(file_path):
with open(file_path, encoding="utf-8") as f:
return json.load(f)
# test_data.json:
# [
# {"username": "admin", "password": "Admin@123", "expected": 200},
# {"username": "admin", "password": "wrong", "expected": 401}
# ]
test_cases = load_test_data("test_data\\login_data.json")
@pytest.mark.parametrize("case", test_cases, ids=[c["username"] for c in test_cases])
@allure.title("登录测试 -{case[username]}")
def test_login_from_file(case):
response = requests.post("/api/login", json={
"username": case["username"],
"password": case["password"]
})
assert response.status_code == case["expected"]
10. 动态更新(allure.dynamic)
allure.dynamic 允许在测试运行时(而非定义时)动态修改报告中的信息,常用于根据运行条件灵活设置报告字段。
10.1 所有 dynamic 方法
python
import allure
import pytest
def test_dynamic_example():
# 动态标题(会覆盖 @allure.title)
allure.dynamic.title("根据运行时环境动态设置的标题")
# 动态描述
allure.dynamic.description("在运行时生成的描述信息,可包含动态数据")
# 动态 HTML 描述
allure.dynamic.description_html("<b>动态 HTML 描述</b>")
# 动态严重程度
allure.dynamic.severity(allure.severity_level.CRITICAL)
# 动态 Epic / Feature / Story
allure.dynamic.epic("动态设置的 Epic")
allure.dynamic.feature("动态设置的 Feature")
allure.dynamic.story("动态设置的 Story")
# 动态 Tag
allure.dynamic.tag("smoke", "regression")
# 动态 Label
allure.dynamic.label("owner", "QA Team B")
allure.dynamic.label("layer", "api")
# 动态链接
allure.dynamic.link("https://example.com", name="动态链接")
allure.dynamic.issue("BUG-9999")
allure.dynamic.testcase("TC-8888")
10.2 实际使用场景
场景一:根据测试环境动态设置标题
python
import os
import allure
def test_api_health_check():
env = os.getenv("TEST_ENV", "dev")
url = {
"dev": "https://dev-api.example.com/health",
"staging": "https://staging-api.example.com/health",
"prod": "https://api.example.com/health",
}[env]
allure.dynamic.title(f"[{env.upper()}] API 健康检查")
allure.dynamic.description(f"检查环境:{env}\n接口地址:{url}")
response = requests.get(url)
assert response.status_code == 200
场景二:参数化测试中动态调整严重程度
python
@pytest.mark.parametrize("level, username", [
("blocker", "admin"),
("critical", "operator"),
("normal", "viewer"),
])
def test_user_permission(level, username):
# 根据参数动态设置严重程度
severity_map = {
"blocker": allure.severity_level.BLOCKER,
"critical": allure.severity_level.CRITICAL,
"normal": allure.severity_level.NORMAL,
}
allure.dynamic.severity(severity_map[level])
allure.dynamic.title(f"用户权限测试 -{username}({level})")
# 执行测试逻辑...
11. Fixture 与 Allure 集成
11.1 在 Fixture 中使用 Step
python
import allure
import pytest
import requests
@pytest.fixture(scope="function")
def auth_token():
"""获取认证 Token 的 Fixture"""
with allure.step("[Fixture] 登录获取 Token"):
response = requests.post("https://api.example.com/login", json={
"username": "test_user",
"password": "Test@123"
})
assert response.status_code == 200
token = response.json()["token"]
allure.attach(
f"Token:{token[:10]}...",
name="Token 信息(脱敏)",
attachment_type=allure.attachment_type.TEXT
)
yield token
with allure.step("[Fixture] 清理:注销登录"):
requests.post(
"https://api.example.com/logout",
headers={"Authorization": f"Bearer{token}"}
)
def test_get_user_profile(auth_token):
with allure.step("获取用户资料"):
response = requests.get(
"https://api.example.com/profile",
headers={"Authorization": f"Bearer{auth_token}"}
)
with allure.step("验证响应"):
assert response.status_code == 200
assert "name" in response.json()
11.2 Fixture 的 Scope 与 Allure
python
# scope="session":整个测试会话只执行一次
@pytest.fixture(scope="session")
def base_url():
with allure.step("[Session Fixture] 读取测试环境配置"):
url = os.getenv("BASE_URL", "https://dev-api.example.com")
allure.attach(f"Base URL:{url}", name="环境配置", attachment_type=allure.attachment_type.TEXT)
return url
# scope="module":每个测试模块执行一次
@pytest.fixture(scope="module")
def test_database():
with allure.step("[Module Fixture] 初始化测试数据库"):
db = connect_test_db()
db.reset()
yield db
with allure.step("[Module Fixture] 清理测试数据库"):
db.cleanup()
db.close()
12. conftest.py 全局配置
conftest.py 是 pytest 的全局配置文件,结合 Allure 可以实现很多自动化的增强功能。
12.1 完整的 conftest.py 示例
python
# tests/conftest.py
import os
import json
import allure
import pytest
from pathlib import Path
from selenium import webdriver
from selenium.webdriver.chrome.options import Options
# ─────────────────────────────────────────────
# 1. 写入环境信息(在报告 Overview 中显示)
# ─────────────────────────────────────────────
def pytest_configure(config):
"""pytest 启动时写入环境信息到 allure-results"""
import platform
results_dir = Path("allure-results")
results_dir.mkdir(exist_ok=True)
env_file = results_dir / "environment.properties"
env_file.write_text(
f"OS=Windows{platform.version()}\n"
f"Python={platform.python_version()}\n"
f"pytest={pytest.__version__}\n"
f"Environment={os.getenv('TEST_ENV', 'dev')}\n"
f"Base.URL={os.getenv('BASE_URL', 'https://dev.example.com')}\n",
encoding="utf-8"
)
# ─────────────────────────────────────────────
# 2. Hook:记录测试执行结果(用于截图等后处理)
# ─────────────────────────────────────────────
@pytest.hookimpl(hookwrapper=True)
def pytest_runtest_makereport(item, call):
"""
此 hook 在每个测试阶段(setup/call/teardown)执行后触发,
将结果对象存入 item,方便 fixture 中读取。
"""
outcome = yield
rep = outcome.get_result()
setattr(item, f"rep_{rep.when}", rep) # item.rep_call, item.rep_setup 等
# ─────────────────────────────────────────────
# 3. Selenium Driver Fixture(含自动截图)
# ─────────────────────────────────────────────
@pytest.fixture(scope="function")
def driver(request):
"""初始化 Chrome Driver,失败时自动截图"""
options = Options()
# options.add_argument("--headless") # 无头模式(CI 环境中启用)
options.add_argument("--start-maximized")
options.add_argument("--disable-gpu")
options.add_argument("--no-sandbox")
with allure.step("[Fixture] 初始化 Chrome 浏览器"):
d = webdriver.Chrome(options=options)
yield d
# teardown:失败时截图
if hasattr(request.node, "rep_call") and request.node.rep_call.failed:
with allure.step("[Fixture] 失败截图"):
allure.attach(
d.get_screenshot_as_png(),
name=f"失败截图 -{request.node.name}",
attachment_type=allure.attachment_type.PNG
)
with allure.step("[Fixture] 关闭浏览器"):
d.quit()
# ─────────────────────────────────────────────
# 4. API 客户端 Fixture
# ─────────────────────────────────────────────
@pytest.fixture(scope="session")
def api_client():
"""创建带认证的 requests Session"""
import requests
session = requests.Session()
session.headers.update({
"Content-Type": "application/json",
"Accept": "application/json"
})
with allure.step("[Fixture] 登录获取 Token"):
resp = session.post(
f"{os.getenv('BASE_URL', 'https://dev.example.com')}/api/login",
json={"username": "test_admin", "password": "TestPass@123"}
)
token = resp.json().get("token", "")
session.headers.update({"Authorization": f"Bearer{token}"})
yield session
with allure.step("[Fixture] 注销登录"):
session.post(f"{os.getenv('BASE_URL', '')}/api/logout")
session.close()
# ─────────────────────────────────────────────
# 5. 自动记录 API 请求响应(对所有 API 测试生效)
# ─────────────────────────────────────────────
@pytest.fixture(autouse=False)
def log_request_response():
"""
可以在 API 测试类上用 @pytest.mark.usefixtures("log_request_response")
来自动附加请求响应日志。
"""
import requests
original_request = requests.Session.request
def patched_request(self, method, url, **kwargs):
response = original_request(self, method, url, **kwargs)
log = (
f">>> 请求 <<<\n"
f"{method.upper()}{url}\n"
f"Headers:{json.dumps(dict(self.headers), indent=2, ensure_ascii=False)}\n"
f"Body:{kwargs.get('json', kwargs.get('data', ''))}\n\n"
f">>> 响应 <<<\n"
f"Status:{response.status_code}\n"
f"Body:{response.text[:2000]}" # 截取前 2000 字符
)
allure.attach(log, name=f"{method.upper()}{url}", attachment_type=allure.attachment_type.TEXT)
return response
requests.Session.request = patched_request
yield
requests.Session.request = original_request
13. 环境信息配置
环境信息显示在报告的 Overview 页面右侧的 "Environment" 面板中。
13.1 方式一:静态文件(environment.properties)
在 allure-results 目录下创建 environment.properties(键值对格式):
# allure-results/environment.properties
Browser=Chrome 120.0
Browser.Driver=ChromeDriver 120.0
OS=Windows 11 22H2
Python.Version=3.11.0
pytest.Version=7.4.0
allure-pytest.Version=2.13.2
Environment=Staging
Base.URL=https://staging.example.com
App.Version=v2.5.1
Database=PostgreSQL 15.2
Tester=QA Team A
Build.Number=42
⚠️
environment.properties必须放在allure-results目录(不是allure-report),且必须在运行
allure generate之前存在。
13.2 方式二:在 conftest.py 中动态生成(推荐)
python
# conftest.py
import platform
import pytest
from pathlib import Path
def pytest_configure(config):
"""在测试开始前动态写入环境信息"""
results_dir = Path(config.getoption("--alluredir", default="allure-results"))
results_dir.mkdir(parents=True, exist_ok=True)
env_props = {
"OS": f"Windows{platform.version()}",
"Python.Version": platform.python_version(),
"pytest.Version": pytest.__version__,
"Environment": os.getenv("TEST_ENV", "dev"),
"Base.URL": os.getenv("BASE_URL", "https://dev.example.com"),
"App.Version": os.getenv("APP_VERSION", "unknown"),
"Build.Number": os.getenv("BUILD_NUMBER", "local"),
}
env_file = results_dir / "environment.properties"
content = "\n".join(f"{k}={v}" for k, v in env_props.items())
env_file.write_text(content, encoding="utf-8")
14. 失败分类配置(Categories)
Categories 对失败和中断的测试按原因进行分组,显示在报告的 Categories 页面,方便快速定位问题类型。
14.1 创建 categories.json
在项目根目录(与 pytest.ini 同级)创建 categories.json:
json
[
{
"name": "🔴 产品缺陷",
"matchedStatuses": ["failed"],
"messageRegex": ".*AssertionError.*"
},
{
"name": "🟠 环境问题",
"matchedStatuses": ["broken"],
"messageRegex": ".*ConnectionError.*|.*Timeout.*|.*ConnectionRefused.*"
},
{
"name": "🟡 元素找不到",
"matchedStatuses": ["broken"],
"messageRegex": ".*NoSuchElementException.*|.*ElementNotFound.*"
},
{
"name": "🟡 超时错误",
"matchedStatuses": ["broken"],
"messageRegex": ".*TimeoutException.*|.*ReadTimeout.*|.*ConnectTimeout.*"
},
{
"name": "⚪ 跳过的测试",
"matchedStatuses": ["skipped"]
},
{
"name": "🔵 已知问题(xfail)",
"matchedStatuses": ["failed"],
"traceRegex": ".*xfail.*"
}
]
14.2 将 categories.json 复制到 allure-results
在 conftest.py 中自动复制:
python
import shutil
from pathlib import Path
def pytest_configure(config):
results_dir = Path("allure-results")
results_dir.mkdir(exist_ok=True)
# 自动复制 categories.json 到结果目录
categories_src = Path("categories.json")
if categories_src.exists():
shutil.copy(categories_src, results_dir / "categories.json")
14.3 分类规则说明
| 字段 | 类型 | 说明 |
|---|---|---|
name |
string | 分类名称(显示在报告中) |
matchedStatuses |
array | 匹配的状态:failed / broken / skipped |
messageRegex |
string | 匹配错误信息的正则表达式 |
traceRegex |
string | 匹配堆栈信息的正则表达式 |
15. 历史趋势
历史趋势让你追踪多次 CI/CD 运行中测试通过率、稳定性的变化,显示在报告的 Overview → Trend 中。
15.1 原理
第 1 次运行 → 生成 allure-report/history/
第 2 次运行 → 将 history/ 复制回 allure-results/ → 生成包含趋势的新报告
15.2 Windows 脚本(bat 文件)
bash
@echo off
REM run_with_history.bat
echo ========== 第一步:保留历史数据 ==========
if exist allure-report\history (
echo 发现历史数据,复制到 allure-results...
xcopy /E /I /Y allure-report\history allure-results\history
)
echo ========== 第二步:运行测试 ==========
pytest tests\ --alluredir=allure-results --clean-alluredir
echo ========== 第三步:生成报告(包含历史趋势)==========
allure generate allure-results -o allure-report --clean
echo ========== 第四步:打开报告 ==========
allure open allure-report
pause
⚠️ 注意:
--clean-alluredir会清空allure-results,所以必须在运行 pytest 之前 先把 history 复制进去。上面的脚本顺序是正确的:先复制 history,然后再加--clean-alluredir运行测试(实际上可以去掉该选项,手动控制清理)。
15.3 修正版 bat 脚本(更安全)
bash
@echo off
echo ========== 备份历史数据 ==========
if exist allure-report\history (
if exist allure-results\history rmdir /s /q allure-results\history
xcopy /E /I /Y allure-report\history allure-results\history
echo 历史数据已备份
) else (
echo 无历史数据,首次运行
)
echo ========== 运行测试(保留 history)==========
REM 不加 --clean-alluredir,手动管理
REM 先删除旧的 result/container JSON,保留 history
for %%f in (allure-results\*-result.json) do del "%%f"
for %%f in (allure-results\*-container.json) do del "%%f"
pytest tests\ --alluredir=allure-results
echo ========== 生成报告 ==========
allure generate allure-results -o allure-report --clean
echo ========== 打开报告 ==========
allure open allure-report
pause
16. 测试筛选与过滤
16.1 按 Allure 标签筛选
powershell
# 只运行标记为 critical 和 blocker 的测试
pytest tests\ --alluredir=allure-results --allure-severities=critical,blocker
# 只运行指定 Feature 的测试
pytest tests\ --alluredir=allure-results --allure-features="用户中心"
# 只运行指定 Story 的测试
pytest tests\ --alluredir=allure-results --allure-stories="账号登录"
# 只运行指定 Epic 的测试
pytest tests\ --alluredir=allure-results --allure-epics="电商平台"
# 组合使用
pytest tests\ --alluredir=allure-results `
--allure-features="用户中心" `
--allure-severities=critical,blocker
16.2 按 pytest.mark 筛选
python
# 在测试中打标记
@pytest.mark.smoke
@allure.severity(allure.severity_level.CRITICAL)
def test_login():
pass
@pytest.mark.regression
def test_profile_update():
pass
powershell
# 只运行 smoke 测试
pytest tests\ -m smoke --alluredir=allure-results
# 只运行非 regression 测试
pytest tests\ -m "not regression" --alluredir=allure-results
# 组合条件
pytest tests\ -m "smoke and not slow" --alluredir=allure-results
16.3 按文件/目录/函数筛选
powershell
# 运行指定文件
pytest tests\test_login.py --alluredir=allure-results
# 运行指定目录
pytest tests\api\ --alluredir=allure-results
# 运行指定测试函数
pytest tests\test_login.py::TestLogin::test_login_success --alluredir=allure-results
# 按关键字筛选(函数名/类名包含关键字)
pytest tests\ -k "login" --alluredir=allure-results
pytest tests\ -k "login and not logout" --alluredir=allure-results
17. 完整项目示例
下面是一个完整的 API 测试项目示例,整合了本指南中的所有核心功能。
目录结构
api_test_project\
├── tests\
│ ├── conftest.py
│ ├── test_user_api.py
│ └── test_order_api.py
├── allure-results\ ← 自动生成
├── allure-report\ ← 自动生成
├── categories.json
├── environment.properties
├── pytest.ini
├── requirements.txt
└── run_test.bat
pytest.ini
[pytest]
addopts=
--alluredir=allure-results
--clean-alluredir
testpaths= tests
categories.json
json
[
{
"name": "产品缺陷",
"matchedStatuses": ["failed"],
"messageRegex": ".*AssertionError.*"
},
{
"name": "接口异常",
"matchedStatuses": ["broken"],
"messageRegex": ".*ConnectionError.*|.*Timeout.*"
},
{
"name": "跳过",
"matchedStatuses": ["skipped"]
}
]
conftest.py
python
import os
import json
import shutil
import platform
import allure
import pytest
from pathlib import Path
def pytest_configure(config):
results_dir = Path("allure-results")
results_dir.mkdir(exist_ok=True)
# 写入环境信息
env = {
"OS": f"Windows{platform.version()}",
"Python": platform.python_version(),
"Environment": os.getenv("TEST_ENV", "dev"),
"Base.URL": os.getenv("BASE_URL", "https://jsonplaceholder.typicode.com"),
}
(results_dir / "environment.properties").write_text(
"\n".join(f"{k}={v}" for k, v in env.items()), encoding="utf-8"
)
# 复制失败分类配置
if Path("categories.json").exists():
shutil.copy("categories.json", results_dir / "categories.json")
@pytest.hookimpl(hookwrapper=True)
def pytest_runtest_makereport(item, call):
outcome = yield
rep = outcome.get_result()
setattr(item, f"rep_{rep.when}", rep)
tests/test_user_api.py
python
import allure
import pytest
import requests
BASE_URL = "https://jsonplaceholder.typicode.com"
@allure.epic("JSONPlaceholder API 测试")
@allure.feature("用户接口")
class TestUserAPI:
@allure.story("获取用户列表")
@allure.title("正常获取所有用户")
@allure.severity(allure.severity_level.CRITICAL)
@pytest.mark.smoke
def test_get_all_users(self):
with allure.step("发送 GET /users 请求"):
response = requests.get(f"{BASE_URL}/users")
allure.attach(
response.text,
name="响应体",
attachment_type=allure.attachment_type.JSON
)
with allure.step("验证状态码为 200"):
assert response.status_code == 200
with allure.step("验证返回用户数量为 10"):
users = response.json()
assert len(users) == 10
@allure.story("获取单个用户")
@allure.severity(allure.severity_level.NORMAL)
@pytest.mark.parametrize("user_id, expected_name", [
(1, "Leanne Graham"),
(2, "Ervin Howell"),
(3, "Clementine Bauch"),
])
@allure.title("根据 ID 获取用户 - ID:{user_id}")
def test_get_user_by_id(self, user_id, expected_name):
with allure.step(f"发送 GET /users/{user_id} 请求"):
response = requests.get(f"{BASE_URL}/users/{user_id}")
with allure.step("验证状态码为 200"):
assert response.status_code == 200
with allure.step(f"验证用户名为{expected_name}"):
assert response.json()["name"] == expected_name
@allure.story("创建用户")
@allure.severity(allure.severity_level.CRITICAL)
@allure.testcase("TC-001", "创建用户正向测试")
@allure.issue("BUG-0000", "已知问题占位")
def test_create_user(self):
new_user = {
"name": "测试用户",
"username": "testuser",
"email": "test@example.com"
}
with allure.step("准备请求体"):
allure.attach(
str(new_user),
name="请求体",
attachment_type=allure.attachment_type.JSON
)
with allure.step("发送 POST /users 请求"):
response = requests.post(f"{BASE_URL}/users", json=new_user)
with allure.step("验证状态码为 201"):
allure.attach(
response.text,
name="响应体",
attachment_type=allure.attachment_type.JSON
)
assert response.status_code == 201
with allure.step("验证返回数据包含新增用户 ID"):
data = response.json()
assert "id" in data
assert data["name"] == "测试用户"
@allure.story("获取不存在的用户")
@allure.severity(allure.severity_level.MINOR)
def test_get_nonexistent_user(self):
allure.dynamic.title("获取 ID=9999 的不存在用户,期望 404")
with allure.step("发送 GET /users/9999 请求"):
response = requests.get(f"{BASE_URL}/users/9999")
with allure.step("验证状态码为 404"):
assert response.status_code == 404
@allure.story("删除用户")
@allure.severity(allure.severity_level.NORMAL)
@pytest.mark.skip(reason="删除接口暂未开放,跳过测试")
def test_delete_user(self):
pass
run_test.bat
bash
@echo off
chcp 65001 > nul
echo ============================================
echo pytest + Allure 自动化测试
echo ============================================
echo [1/3] 运行测试...
pytest tests\ -v
echo [2/3] 生成报告...
allure generate allure-results -o allure-report --clean
echo [3/3] 打开报告...
allure open allure-report
pause
18. 常见问题与排查
❓ 问题 1:报告页面空白 / 无内容
原因 :直接双击打开 index.html,浏览器 CORS 限制阻止了本地文件加载。
解决:
powershell
# 方式一:使用 allure open(推荐)
allure open allure-report
# 方式二:Python 启动本地服务
cd allure-report
python -m http.server 8080
# 然后浏览器访问 http://localhost:8080
❓ 问题 2:allure 命令无法识别
原因:Allure CLI 未安装,或 PATH 未配置。
解决:
powershell
# 检查是否安装
where allure
# 如使用 Scoop 安装
scoop install allure
# 手动安装后,确认 PATH 包含 C:\allure-x.x.x\bin
$env:Path
# 临时添加到当前会话(重启后失效)
$env:Path += ";C:\allure-2.27.0\bin"
❓ 问题 3:allure-results 目录没有生成
原因 :allure-pytest 未安装,或命令参数有误。
解决:
powershell
# 确认已安装
pip show allure-pytest
# 确认命令参数(注意是双横线)
pytest tests\ --alluredir=allure-results # ✅ 正确
pytest tests\ --allure-dir=allure-results # ❌ 旧版参数(已废弃)
❓ 问题 4:中文内容在报告中显示乱码
原因:文件编码问题。
解决:
powershell
# 方式一:在 PowerShell 中设置编码
$OutputEncoding = [Console]::OutputEncoding = [Text.Encoding]::UTF8
# 方式二:CMD 中设置 UTF-8
chcp 65001
# 方式三:Python 文件中确保使用 UTF-8 写入附件
allure.attach(
content.encode("utf-8").decode("utf-8"),
name="中文日志",
attachment_type=allure.attachment_type.TEXT
)
❓ 问题 5:历史趋势不显示
原因 :生成新报告时 allure-results/history 不存在。
解决 :确保在运行 pytest 之前,将上次报告的 history 目录复制到 allure-results:
powershell
# 在 PowerShell 中
if (Test-Path "allure-report\history") {
Copy-Item -Recurse -Force "allure-report\history" "allure-results\history"
}
pytest tests\ --alluredir=allure-results
allure generate allure-results -o allure-report --clean
❓ 问题 6:environment.properties 不显示在报告中
原因 :文件放错位置(应在 allure-results,不是 allure-report)。
解决:
allure-results\
├── environment.properties ✅ 正确位置
└── ...
allure-report\
├── environment.properties ❌ 错误位置(不会生效)
└── index.html
❓ 问题 7:@allure.step 装饰器参数引用不生效
原因:参数名拼写错误,或使用了关键字参数。
解决:
python
# ✅ 正确:使用位置参数,参数名与函数签名一致
@allure.step("登录用户:{username}")
def login(username, password):
pass
# ❌ 错误:参数名拼写不对
@allure.step("登录用户:{user_name}") # 函数参数是 username,不是 user_name
def login(username, password):
pass
19. 速查表
所有注解汇总
python
import allure
# ── 描述类 ──────────────────────────────────────────────
@allure.title("标题")
@allure.description("描述")
@allure.description_html("<b>HTML 描述</b>")
# ── 层级标签 ─────────────────────────────────────────────
@allure.epic("史诗")
@allure.feature("功能")
@allure.story("故事")
@allure.tag("smoke", "regression")
@allure.label("key", "value")
# ── 严重程度 ─────────────────────────────────────────────
@allure.severity(allure.severity_level.BLOCKER)
@allure.severity(allure.severity_level.CRITICAL)
@allure.severity(allure.severity_level.NORMAL) # 默认
@allure.severity(allure.severity_level.MINOR)
@allure.severity(allure.severity_level.TRIVIAL)
# ── 链接 ─────────────────────────────────────────────────
@allure.link("url", name="名称")
@allure.issue("BUG-123")
@allure.testcase("TC-456")
# ── 步骤(代码块)────────────────────────────────────────
with allure.step("步骤描述"):
pass
# ── 步骤(函数装饰器)────────────────────────────────────
@allure.step("步骤描述,参数:{arg}")
def some_step(arg):
pass
# ── 附件 ─────────────────────────────────────────────────
allure.attach("内容", name="名称", attachment_type=allure.attachment_type.TEXT)
allure.attach.file("文件路径", name="名称", attachment_type=allure.attachment_type.PNG)
# ── 动态更新 ──────────────────────────────────────────────
allure.dynamic.title("标题")
allure.dynamic.description("描述")
allure.dynamic.severity(allure.severity_level.CRITICAL)
allure.dynamic.epic("Epic")
allure.dynamic.feature("Feature")
allure.dynamic.story("Story")
allure.dynamic.tag("tag1", "tag2")
allure.dynamic.label("key", "value")
allure.dynamic.link("url", name="名称")
allure.dynamic.issue("BUG-xxx")
allure.dynamic.testcase("TC-xxx")
常用命令速查
powershell
# 运行测试
pytest tests\
pytest tests\ --alluredir=allure-results --clean-alluredir
pytest tests\ -m smoke --alluredir=allure-results
pytest tests\ --allure-severities=critical,blocker
# 生成与查看报告
allure generate allure-results -o allure-report --clean
allure open allure-report
allure serve allure-results # 推荐开发调试用
allure serve allure-results -p 9999
# 查看 Allure 版本
allure --version