pytest(13):pytest失败重跑插件: pytest-rerunfailures使用
背景
在编写接口case的时候,我们常遇到一个这样的问题:
测试环境不稳定偶发接口超时(和服务无关,纯粹是环境问题),然后执行接口case也因此偶发失败。比如同一个接口case跑五次,其中有两次失败,另外三次都是成功的,这种偶发性的环境问题就需要我们手动重跑(还不一定能够通过)。有没有一个比较好的机制,保证case能够尽最大努力通过测试呢?
这里我们介绍pytest的一个失败重跑插件:pytest-rerunfailures
介绍
pytest-rerunfailures是一个通过重跑机制来消除不稳定失败的pytest插件。
安装插件
pip3 install pytest-rerunfailures -i http://pypi.douban.com/simple/ --trusted-host pypi.douban.com
提前了解重点
命令行参数:--reruns n(重新运行次数),--reruns-delay m(等待运行秒数)
装饰器参数:reruns=n(重新运行次数),reruns_delay=m(等待运行秒数)
重新运行所有失败的用例
要重新运行所有测试失败,使用 --reruns 命令行选项,并指定要运行测试的最大次数:
pytest --reruns 5 -s
添加重新运行的延时
要在两次重试之间增加延迟时间,使用 --reruns-delay 命令行选项,指定下次测试重新开始之前等待的秒数
pytest --reruns 5 --reruns-delay 10 -s
重新运行指定的测试用例
要将单个测试用例添加flaky装饰器 @pytest.mark.flaky(reruns=5) ,并在测试失败时自动重新运行,需要指定最大重新运行的次数
例子1
指定失败重跑最大次数为10:pytest --reruns 10
如下图,我们看到一共跑了两次,第一次结果失败,所以重跑了第二次,最终结果用R标注。(如果跑一次就成功,结果应该是'.')
例子2
指定失败重跑最大次数为10,重跑间隔为1秒:pytest --reruns 10 --reruns-delay 1
如下图,一共重跑了两次,重跑两次的执行时间为2.1秒,上图中一次只需要0.07秒,这里多出的两秒就是因为--reruns-delay指定的重跑间隔为1秒。
例子3
import pytest @pytest.mark.flaky(reruns=5) def test_example(): import random assert random.choice([True, False, False])
同样的,这个也可以指定重新运行的等待时间
@pytest.mark.flaky(reruns=5, reruns_delay=2) def test_example(): import random assert random.choice([True, False, False])
注意事项
如果指定了用例的重新运行次数,则在命令行添加--reruns对这些用例是不会生效的
例子4:通过表达式指定失败重跑
test_demo.py解释:
-
test_assert_error随机抛出AssertionError
-
test_value_error随机抛出ValueError
#!/usr/bin/env python3
#!coding:utf-8
import pytest
import random
def test_assert_error():
r = random.randint(1,2)
with pytest.raises(AssertionError):
#这里如果不使用pytest.raises显式抛出AssertionError异常,pytest-rerunfailures无法捕获到assert r == 1,应该是该插件的bug。
assert r == 1
def test_value_error():
r = random.randint(1,2)
if r == 1:
s = int('www')
执行:pytest --reruns 10 --only-rerun AssertionError --only-rerun ValueError test_demo.py -v
其中多个--only-rerun之间是或的关系
如上图,遇到AssertionError和ValueError的情况下都被重跑了
装饰器模式
test_demo.py
-
test_assert_error随机抛出AssertionError,最多失败重跑五次
-
test_value_error随机抛出ValueError,最多失败重跑五次,失败重跑间隔为2秒
-
test_value_error_condition,最多失败重跑五次,仅当系统为win32系统才重跑。
#!/usr/bin/env python3
#!coding:utf-8
import pytest
import random
import sys
#这个最多失败重跑五次
@pytest.mark.flaky(reruns=5)
def test_assert_error():
r = random.randint(1,2)
#raise AssertionError("ERR")
with pytest.raises(AssertionError):
assert r == 1
#这个最多失败重跑五次
@pytest.mark.flaky(reruns=5, reruns_delay=2)
def test_value_error():
r = random.randint(1,2)
if r == 1:
s = int('nick')
#官网的这个例子有问题,如果拿mac或者linux机器跑也会有重试(condition中指定的是win32平台才会触发重跑机制)
@pytest.mark.flaky(reruns=5, condition=not sys.platform.startswith("win32"))
def test_value_error_condition():
r = random.randint(1,2)
if r == 1:
s = int('nick')
执行:pytest -v
这里前面两个testcase都有过失败重跑,但是第三个也重跑了(作者原意是condition为False情况下不会重跑),这里是有bug的,即condition是无效的。
去查看项目源码,果然发现这里有些问题,是否不重跑的函数里面用的是or,即最后一个not condition只是不重跑的条件之一,如果前面满足重跑,则condition这个参数可以被忽略掉。
兼容性
- 不可以与类,模块还有包级别的fixture装饰器一起使用: @pytest.fixture()
- 该插件与pytest-xdist的 --looponfail 标志不兼容
- 该插件在使用pdb调试时候会有不兼容性
- 最后总结,这个插件虽然还用,但是坑还是不少,建议主要使用失败重试次数和重试间隔的功能即可。