You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

如何让PyTest优先运行过往最易失败的测试用例?

让PyTest优先运行历史上频繁失败的测试用例

我之前也碰到过一模一样的需求——维护上千个测试用例,就那十几个“惯犯”总出问题,每次等全量测试跑完才发现它们失败,实在太耗时间了。--failed-first只看上次运行的结果,手动维护失败用例文件又容易遗漏更新,确实不够灵活。这里分享几个我亲测有效的解决思路:

方案1:自定义PyTest插件(最优雅的自动方案)

核心思路是追踪每个测试用例的历史失败次数,然后在测试收集阶段调整执行顺序,把失败次数多的用例优先安排。你只需要在项目根目录写一个conftest.py文件,利用PyTest的钩子函数实现这个逻辑:

步骤1:记录测试用例的失败次数

用pytest_runtest_makereport钩子捕获每个用例的执行结果,更新缓存中的失败统计:

def pytest_runtest_makereport(item, call):
    if call.when == "call":
        # 获取用例的唯一标识(比如用例的nodeid)
        test_id = item.nodeid
        # 从缓存中获取当前失败次数,默认0
        cache = item.config.cache
        fail_count = cache.get(f"failed_counts/{test_id}", 0)
        
        if call.excinfo is not None:
            # 如果用例失败,失败次数+1
            cache.set(f"failed_counts/{test_id}", fail_count + 1)
        else:
            # 如果用例成功,可选择重置失败次数(或者保持累计,看你需求)
            # 这里我们选择保持累计,因为要统计历史总失败次数
            pass

步骤2:根据失败次数排序测试用例

用pytest_collection_modifyitems钩子调整收集到的用例顺序:

def pytest_collection_modifyitems(session, config, items):
    # 按失败次数从高到低排序,失败次数相同的保持原顺序
    def get_fail_count(item):
        cache = config.cache
        return cache.get(f"failed_counts/{item.nodeid}", 0)
    
    # 反向排序,失败次数多的排前面
    items.sort(key=get_fail_count, reverse=True)

把这两段代码放到项目根目录的conftest.py里,之后每次运行pytest时,就会自动把历史失败次数最多的用例优先执行了!

补充细节:

  • 缓存数据存在项目根目录的.pytest_cache文件夹里,如果需要重置所有统计,直接删除这个文件夹即可。
  • 你还可以扩展这个插件,比如加一个命令行参数(比如--prioritize-frequent-failures),让这个排序逻辑可以按需启用,避免影响正常的全量测试。

方案2:基于JUnit报告的半自动脚本(适合不想写插件的场景)

如果不想写插件,也可以用PyTest的--junitxml参数生成测试报告,再写个简单的Python脚本解析报告,统计每个用例的历史失败次数,自动生成优先执行的用例列表:

  1. 每次测试后生成JUnit报告:
pytest --junitxml=test_report.xml
  1. 写个脚本(比如update_failing_tests.py)解析报告,统计失败次数并写入failing_tests.txt:
import xml.etree.ElementTree as ET
from collections import defaultdict

# 读取历史失败统计(如果文件存在)
fail_counts = defaultdict(int)
try:
    with open("fail_counts.txt", "r") as f:
        for line in f:
            test_id, count = line.strip().split("\t")
            fail_counts[test_id] = int(count)
except FileNotFoundError:
    pass

# 解析最新的JUnit报告
tree = ET.parse("test_report.xml")
root = tree.getroot()

for testcase in root.findall(".//testcase"):
    test_id = f"{testcase.attrib['classname']}::{testcase.attrib['name']}"
    # 如果用例失败,次数+1
    if testcase.find("failure") is not None:
        fail_counts[test_id] += 1

# 保存更新后的失败统计
with open("fail_counts.txt", "w") as f:
    for test_id, count in fail_counts.items():
        f.write(f"{test_id}\t{count}\n")

# 按失败次数排序,生成优先执行的用例列表
sorted_tests = sorted(fail_counts.items(), key=lambda x: x[1], reverse=True)
with open("failing_tests.txt", "w") as f:
    for test_id, _ in sorted_tests:
        f.write(f"{test_id}\n")
  1. 运行测试时先执行高频失败用例,再跑全量:
pytest -x $(cat failing_tests.txt) tests

这个方案不需要修改PyTest的核心逻辑,适合快速落地,但需要额外维护脚本和统计文件,灵活性不如自定义插件。

对比你现有的手动方案

自定义插件的优势在于完全自动化——不需要你手动收集或更新失败用例列表,PyTest会自动追踪每个用例的历史表现并调整顺序;而半自动脚本则是对你现有方案的升级,减少手动维护的工作量。

内容的提问来源于stack exchange,提问作者frans

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.05.09 07:52:40