笔者在执行自动化测试用例时,会发现有时候用例失败并非代码问题,而是由于服务正在发版,导致请求失败,从而降低了自动化用例的稳定性,最后还要花时间定位到底是自身case
的原因还是业务逻辑问题,还是其他原因,增加了定位成本。增加容错机制,失败重试,会解决大部分由于网络原因、服务重启等原因造成的case
失败问题。那该如何增加失败重试机制呢?带着问题我们一起探索。
先给出答案,我们将使用pytest-rerunfailures
插件来实现失败重试功能。
pytest-rerunfailures
?pytest-rerunfailures
是一个基于 pytest 框架的插件,它允许我们对测试用例进行失败重试。当一个测试用例失败时,插件会自动重新运行失败的测试用例,直到达到预定的重试次数或测试用例通过为止。这样可以增加用例的稳定性,并减少因为偶发性问题导致的测试失败。
pytest-rerunfailures
?首先,确保已经安装了 pytest-rerunfailures 插件。可以使用以下命令进行安装:
pip install pytest-rerunfailures
安装完成后,在项目中使用 pytest 运行测试用例时,pytest-rerunfailures 插件会自动生效。
接下来,在编写测试用例时,可以通过添加 @pytest.mark.flaky
装饰器将需要重试的测试用例标记起来。例如:
test_demo.py
import pytest @pytest.mark.flaky(reruns=3, reruns_delay=2) def test_case(): assert 1 == 2
在上述示例中,我们使用了 @pytest.mark.flaky
装饰器来标记测试用例 test_case
为可重试的。参数 reruns
指定了重试次数,而 reruns_delay
则指定了每次重试之间的延迟时间(以秒为单位)。
我们来运行case
,看一下执行结果:
执行命令:pytest -s -v test_demo.py::test_case
,会看到如下结果:
RERUN test_dir/test_demo.py::test_case RERUN test_dir/test_demo.py::test_case RERUN test_dir/test_demo.py::test_case FAILED
可以看到,重试了3次,最终结果为失败。
注意:如果你是在pycharm中点击绿色三角形直接运行是不生效的
总结一下:
当运行测试时,如果测试用例失败,pytest-rerunfailures 插件会根据我们配置的重试次数和延迟时间自动重新运行该测试用例,直到达到最大重试次数或测试通过为止。
除了使用装饰器来标记测试用例外,pytest-rerunfailures 还支持使用命令行选项和配置文件的方式进行配置。
命令行执行的话,可以这样写:
pytest -s -v --reruns 3 --reruns-delay 2 test_demo.py::test_case
或者代码运行的话,可以这样写:
pytest.main(["-s", "-v", "--reruns", "3", "--reruns-delay", "2", "test_demo.py::test_case"])
到这里,应该会使用了。我们简单概括一下它的运行机制:
@pytest.mark.flaky
装饰器),插件会根据配置的重试次数和延迟时间重新运行该测试用例。总结起来,pytest-rerunfailures 插件在测试执行失败时,根据配置的重试次数和延迟时间重新运行测试用例,并根据重试结果判断最终的测试结果。这样可以提高测试用例的稳定性,并减少偶发性问题导致的测试失败。
使用阶段,我们使用mark
标记,那源码中应该添加了该标记
def pytest_configure(config):
# add flaky marker
config.addinivalue_line(
"markers",
"flaky(reruns=1, reruns_delay=0): mark test to re-run up "
"to 'reruns' times. Add a delay of 'reruns_delay' seconds "
"between re-runs.",
)
......
简单解释一下:
pytest_configure(config)
是 pytest 的一个钩子函数,用于在 pytest 配置阶段对配置进行自定义操作。config.addinivalue_line()
是 pytest 的配置方法,用于向配置中添加新的配置项或配置信息。config.addinivalue_line()
方法,插件向 pytest 的配置中加入了一行字符串。reruns
和 reruns_delay
来说明重试次数和延迟时间的默认值。通过这个自定义的标记,就可以使用 @pytest.mark.flaky
装饰器来标记需要进行重试的测试用例,并且可以在装饰器中指定具体的重试次数和延迟时间。
我们看看实现失败重试的源码,这才是重点
def pytest_runtest_protocol(item, nextitem):
"""
Run the test protocol.
Note: when teardown fails, two reports are generated for the case, one for
the test case and the other for the teardown error.
"""
reruns = get_reruns_count(item)
if reruns is None:
# global setting is not specified, and this test is not marked with
# flaky
return
# while this doesn't need to be run with every item, it will fail on the
# first item if necessary
check_options(item.session.config)
delay = get_reruns_delay(item)
parallel = not is_master(item.config)
db = item.session.config.failures_db
item.execution_count = db.get_test_failures(item.nodeid)
db.set_test_reruns(item.nodeid, reruns)
if item.execution_count > reruns:
return True
need_to_run = True
while need_to_run:
item.execution_count += 1
item.ihook.pytest_runtest_logstart(nodeid=item.nodeid, location=item.location)
reports = runtestprotocol(item, nextitem=nextitem, log=False)
for report in reports: # 3 reports: setup, call, teardown
report.rerun = item.execution_count - 1
if _should_not_rerun(item, report, reruns):
# last run or no failure detected, log normally
item.ihook.pytest_runtest_logreport(report=report)
else:
# failure detected and reruns not exhausted, since i < reruns
report.outcome = "rerun"
time.sleep(delay)
if not parallel or works_with_current_xdist():
# will rerun test, log intermediate result
item.ihook.pytest_runtest_logreport(report=report)
# cleanin item's cashed results from any level of setups
_remove_cached_results_from_failed_fixtures(item)
_remove_failed_setup_state_from_session(item)
break # trigger rerun
else:
need_to_run = False
item.ihook.pytest_runtest_logfinish(nodeid=item.nodeid, location=item.location)
return True
简单解释一下:
首先,通过函数 get_reruns_count(item)
获取当前测试用例需要重试的次数。如果没有设置重试次数,则直接返回。
然后,检查配置选项并获取重试的延迟时间,并确定是否运行在并行模式下。还会获取失败记录数据库对象,并获取当前测试用例已经执行的次数。
接下来,通过比较已执行次数和设定的重试次数,判断是否需要进行重试。如果已执行次数大于等于设定的重试次数,则不再进行重试,直接返回。
如果需要重试,会进入一个循环,每次重试会增加已执行次数。在重试过程中,会调用 pytest_runtest_logstart
函数记录测试用例开始执行的日志。
然后,通过调用 runtestprotocol
函数执行测试用例,并获取测试结果。在这里,生成的报告会被标记为执行次数减一,以便区分原始执行和重试执行的报告。
接着,通过 _should_not_rerun
函数判断当前报告是否满足不需要重试的条件。如果满足,则继续执行后续操作。
如果报告表明需要重试,并且重试次数未达到设定的次数,会将报告的结果设置为 "rerun",并根据设定的延迟时间暂停一段时间。
然后,根据并行模式和当前使用的 xdist 版本,决定是否记录中间结果。同时,会清除缓存的结果和执行状态。
之后,重试循环会继续,直到不满足重试条件为止。最后,会调用 pytest_runtest_logfinish
函数记录测试用例结束执行的日志。
最后,函数返回 True,表示已经实现重试机制。
总结起来,这段代码通过循环执行测试用例,并在满足重试条件时进行重试,直到满足退出条件为止。在重试过程中,会记录日志、生成报告,并根据设定的重试次数和延迟时间进行控制。
失败重试功能并不是解决所有测试问题的法宝,它应该被视为一种提高测试稳定性的辅助手段。在使用 pytest-rerunfailures 进行失败重试时,我们应该仔细分析失败的原因,确保重试次数和延迟时间设置合理,并与团队成员共同讨论和决定是否需要重试测试用例。
总结起来,pytest-rerunfailures 是一个非常有用的工具,可以提高测试用例的稳定性。通过使用它,我们可以轻松地实现失败重试功能,并减少由于偶发性问题导致的测试失败。
另外源码中,看到了pytest_runtest_logstart
等,可能有些同学不明白这是干嘛用的,之后我们专门写一篇文章来介绍它的作用。
最后:下面是配套学习资料,对于做【软件测试】的朋友来说应该是最全面最完整的备战仓库,这个仓库也陪伴我走过了最艰难的路程,希望也能帮助到你!【100%无套路免费领取】
被百万人刷爆的软件测试题库!!!谁用谁知道!!!全网最全面试刷题小程序,手机就可以刷题,地铁上公交上,卷起来!
涵盖以下这些面试题板块:
1、软件测试基础理论 ,2、web,app,接口功能测试 ,3、网络 ,4、数据库 ,5、linux
6、web,app,接口自动化 ,7、性能测试 ,8、编程基础,9、hr面试题 ,10、开放性测试题,11、安全测试,12、计算机基础