如何让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脚本解析报告,统计每个用例的历史失败次数,自动生成优先执行的用例列表:
- 每次测试后生成JUnit报告:
pytest --junitxml=test_report.xml
- 写个脚本(比如
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")
- 运行测试时先执行高频失败用例,再跑全量:
pytest -x $(cat failing_tests.txt) tests
这个方案不需要修改PyTest的核心逻辑,适合快速落地,但需要额外维护脚本和统计文件,灵活性不如自定义插件。
对比你现有的手动方案
自定义插件的优势在于完全自动化——不需要你手动收集或更新失败用例列表,PyTest会自动追踪每个用例的历史表现并调整顺序;而半自动脚本则是对你现有方案的升级,减少手动维护的工作量。
内容的提问来源于stack exchange,提问作者frans
相关产品推荐
相关产品推荐

