pytest测试框架 —— 失败重试:pytest-rerunfailures库使用
·
在自动化测试中,测试失败往往并非由代码缺陷引起,而是由于环境不稳定、资源竞争或网络抖动等临时性问题。
pytest-rerunfailures插件提供了强大的失败重试机制,本教程将全面解析其使用方法和最佳实践。
一、为什么需要失败重试?
1.1 测试不稳定的常见原因
| 问题类型 | 典型案例 | 影响 |
|---|---|---|
| 环境因素 | 服务启动延迟、资源不足 | 30%的失败 |
| 网络问题 | API超时、连接中断 | 25%的失败 |
| 并发竞争 | 数据竞争、死锁 | 20%的失败 |
| 测试依赖 | 外部服务不可用 | 15%的失败 |
| 随机故障 | 偶然性错误 | 10%的失败 |
1.2 重试机制的价值
二、环境安装与配置
2.1 安装插件
pip install pytest-rerunfailures
2.2 基础配置方式
方式1:命令行参数
pytest --reruns 3 # 重试3次
pytest --reruns 5 --reruns-delay 2 # 重试5次,每次间隔2秒
方式2:配置文件 (pytest.ini)
[pytest]
addopts = --reruns 3 --reruns-delay 1
方式3:标记特定测试
@pytest.mark.flaky(reruns=5, reruns_delay=1)
def test_unstable_api():
...
三、基础使用场景
3.1 全局重试所有失败测试
# 重试所有失败测试3次
pytest --reruns 3
# 重试5次,每次间隔2秒
pytest --reruns 5 --reruns-delay 2
3.2 特定测试重试
import pytest
import random
# 标记单个测试
@pytest.mark.flaky(reruns=3)
def test_flaky_login():
assert random.random() > 0.3 # 70%失败率
# 标记整个测试类
@pytest.mark.flaky(reruns=5, reruns_delay=1)
class TestPayment:
def test_credit_card(self):
...
def test_paypal(self):
...
四、高级功能详解
4.1 条件重试
# conftest.py
def pytest_configure(config):
# 注册自定义条件重试标记
config.addinivalue_line(
"markers",
"retry_on(condition, reruns=3, delay=0.5): 条件重试标记"
)
def pytest_runtest_setup(item):
"""动态应用重试策略"""
for marker in item.iter_markers("retry_on"):
condition = marker.kwargs["condition"]
if condition == "network":
item.add_marker(pytest.mark.flaky(
reruns=marker.kwargs.get("reruns", 5),
reruns_delay=marker.kwargs.get("delay", 1)
))
# test_api.py
@pytest.mark.retry_on(condition="network", reruns=3, delay=2)
def test_api_connection():
"""仅在网络错误时重试"""
result = call_external_api()
assert result.status_code == 200
4.2 重试后清理
def test_db_operation():
try:
# 测试执行
db.insert(test_data)
assert db.query() == expected
finally:
# 重试前清理
db.delete(test_data.id)
4.3 重试间隔策略
import math
def exponential_backoff(attempt):
"""指数退避策略"""
return 0.5 * math.pow(2, attempt)
@pytest.fixture(autouse=True)
def rerun_with_backoff(request):
"""应用指数退避重试"""
if request.config.getoption("--reruns"):
delay = exponential_backoff(request.session.testsfailed)
request.config.option.reruns_delay = delay
五、企业级最佳实践
5.1 与 Allure 报告集成
# conftest.py
def pytest_runtest_logreport(report):
"""在报告中记录重试信息"""
if hasattr(report, "wasxfail"):
return
if report.outcome == "failed" and hasattr(report, "rerun"):
allure.dynamic.description(
f"失败重试记录: {report.rerun}/{report.max_rerun}次重试"
)
allure.attach(
f"重试间隔: {report.rerun_delay}秒",
"重试策略",
allure.attachment_type.TEXT
)
5.2 CI/CD 集成配置
# .gitlab-ci.yml
stages:
- test
test:
stage: test
script:
- pip install pytest pytest-rerunfailures
# 开发环境重试3次,生产环境仅重试1次
- |
if [ "$CI_ENVIRONMENT" = "dev" ]; then
RERUNS=3
else
RERUNS=1
fi
pytest --reruns=$RERUNS --reruns-delay=1
5.3 重试分析报告
# conftest.py
RERUN_STATS = {}
def pytest_runtest_logstart(nodeid, location):
"""追踪重试情况"""
RERUN_STATS[nodeid] = RERUN_STATS.get(nodeid, 0)
def pytest_runtest_logreport(report):
"""记录重试数据"""
if report.outcome == "rerun":
RERUN_STATS[report.nodeid] += 1
def pytest_terminal_summary(terminalreporter):
"""生成重试报告"""
if not RERUN_STATS:
return
terminalreporter.write_sep("=", "重试统计报告")
for test, count in sorted(RERUN_STATS.items(), key=lambda x: x[1], reverse=True):
if count > 0:
terminalreporter.write_line(f"- {test}: {count}次重试")
六、实战案例分析
6.1 电商支付测试
import pytest
import time
@pytest.mark.flaky(reruns=3, reruns_delay=2)
def test_payment_gateway():
"""支付网关重试测试"""
# 模拟支付过程
transaction_id = start_payment()
# 网关处理可能需要时间
time.sleep(1)
# 验证支付状态
status = check_payment_status(transaction_id)
assert status == "COMPLETED"
def start_payment():
"""模拟支付启动(50%成功率)"""
import random
if random.random() > 0.5:
return "txn_123456"
raise ConnectionError("支付网关连接失败")
def check_payment_status(txn_id):
"""模拟支付状态检查"""
return "COMPLETED"
七、注意事项与陷阱
7.1 不适合重试的场景
| 场景类型 | 原因 | 替代方案 |
|---|---|---|
| 确定性失败 | 总是失败 | 修复测试/缺陷 |
| 数据污染 | 重试加剧问题 | 更严格的清理 |
| 性能测试 | 改变执行时间 | 独立环境 |
| 顺序依赖 | 改变执行顺序 | 使用pytest-ordering |
7.2 避免过度使用
-
生产环境:禁用或最小化重试(最多1-2次)
-
关键测试:记录完整重试日志
-
重试上限:根据测试重要性设置不同阈值:
# conftest.py def pytest_collection_modifyitems(items): for item in items: if "critical" in item.keywords: item.add_marker(pytest.mark.flaky(reruns=5)) elif "high_priority" in item.keywords: item.add_marker(pytest.mark.flaky(reruns=3)) else: item.add_marker(pytest.mark.flaky(reruns=1))
八、性能优化策略
8.1 并行执行优化
# 使用xdist并行执行同时支持重试
pytest -n 4 --reruns 2
8.2 智能重试策略
# conftest.py
def should_retry(excinfo):
"""智能重试判断"""
exception_type = excinfo.type
# 网络错误重试
if exception_type in [ConnectionError, TimeoutError]:
return True
# 特定状态码重试
if hasattr(excinfo.value, "status_code"):
if excinfo.value.status_code in [502, 503, 504]:
return True
return False
@pytest.hookimpl(hookwrapper=True)
def pytest_runtest_makereport(item, call):
"""自定义重试决策"""
outcome = yield
report = outcome.get_result()
if report.failed and should_retry(call.excinfo):
# 设置重试标记
setattr(report, "should_retry", True)
九、完整工作流示例
9.1 项目结构
ecommerce-tests/
├── conftest.py # 重试配置
├── pytest.ini # 基础配置
├── tests/
│ ├── test_payment.py # 支付测试
│ └── test_inventory.py # 库存测试
└── run_tests.sh # 执行脚本
9.2 pytest.ini 配置
[pytest]
addopts =
-v
--reruns 2
--reruns-delay 1
--only-rerun AssertionError
--only-rerun ".*Connection.*"
markers =
critical: 关键路径测试
unstable: 不稳定测试(额外重试)
9.3 执行与报告
# 运行测试
pytest
# 输出示例
========================= test session starts =========================
platform linux -- Python 3.8.10, pytest-7.0.1, pluggy-1.0.0
plugins: rerunfailures-10.2
collected 23 items
test_payment.py::test_credit_card RERUN [ 10%]
test_payment.py::test_credit_card PASSED [ 20%]
test_inventory.py::test_stock_update FAILED (首次失败) [ 30%]
test_inventory.py::test_stock_update RERUN [ 40%]
test_inventory.py::test_stock_update PASSED [ 50%]
================= 21 passed, 2 rerun in 15.32 seconds =================
十、扩展功能:重试分析
10.1 重试趋势监控
# 记录重试历史到CSV
import csv
import time
def record_retry_history(stats):
with open("retry_history.csv", "a", newline="") as f:
writer = csv.writer(f)
for test, count in stats.items():
writer.writerow([test, count, time.strftime("%Y-%m-%d")])
10.2 Jenkins集成
pipeline {
agent any
stages {
stage('Test') {
steps {
script {
// 开发分支允许更多重试
if (env.BRANCH_NAME == 'develop') {
RERUNS = 5
} else {
RERUNS = 2
}
sh "pytest --reruns=${RERUNS} --reruns-delay=1"
}
}
post {
always {
// 生成重试报告
sh "python generate_retry_report.py"
archiveArtifacts 'retry_report.html'
}
}
}
}
}
总结
使用建议
- 分层重试策略:根据测试重要性配置不同重试次数
- 智能过滤:仅重试临时性问题,而非真实缺陷
- 适当间隔:根据系统恢复时间设置重试间隔
- 完整记录:在报告中记录所有重试行为
- 性能监控:追踪重试对整体执行时间的影响
pytest-rerunfailures 通过提供灵活的失败重试机制,可以显著提高测试套件的稳定性和可靠性,减少因环境问题导致的误报,同时帮助识别真正需要修复的问题。
更多推荐
所有评论(0)