一、背景
在编写自动化测试用例的时候,我们常遇到一个这样的问题:
测试环境不稳定偶发接口超时(和服务无关,纯粹是环境问题),然后执行自动化 case 也因此偶发失败。比如同一个 case 跑五次,其中有两次失败,另外三次都是成功的,这种偶发性的环境问题就需要我们手动重跑(还不一定能够通过)。有没有一个比较好的机制,保证 case 能够尽最大努力通过测试呢?
这里我们介绍 pytest 的一个失败重跑插件:pytest-rerunfailures
二、介绍
pytest-rerunfailures
是一个通过重跑机制来消除不稳定失败的 pytest 插件。
项目地址:https://github.com/pytest-dev/pytest-rerunfailures
三、安装
安装&运行要求:
1
2
Python 3.6~3.9, or PyPy3
pytest 5.0+
安装插件:
1
> pip install pytest-rerunfailures==10.1
四、使用pytest-rerunfailures
使用方式有两种:
- 命令行参数
- 装饰器方式
1、命令行参数模式
case:test_demo.py
1
2
3
4
5
6
7
8
9
#!/usr/bin/env python3
#!coding:utf-8
import pytest
import random
def test_simple_assume():
#每次case运行的值为1或者2,具有随机性
r = random.randint(1,2)
assert r == 1
如果我们直接运行 pytest test_demo.py
,那么每次运行的结果会具有一定随机性(可能成功也可能失败)。
如果使用 pytest-rerunfailures 指定执行多次,只要执行次数足够多,那么遇到结果成功的机会就会更多。
(1)示例一:指定失败重跑最大次数为10
1
> pytest --reruns 10
如下图,我们看到一共跑了两次,第一次结果失败,所以重跑了第二次,最终结果用 R
标注。(如果跑一次就成功,结果应该是 .
)
(2)示例二:指定失败重跑最大次数为10,重跑间隔为1秒
1
> pytest --reruns 10 --reruns-delay 1
如下图,一共重跑了两次,重跑两次的执行时间为2.1秒,上图中一次只需要0.07秒,这里多出的两秒就是因为 --reruns-delay
指定的重跑间隔为1秒。
(3)通过表达式指定失败重跑
test_demo.py 解释:
- test_assert_error 随机抛出 AssertionError
- test_value_error 随机抛出 ValueError
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
#!/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')
执行以下命令行,其中多个 --only-rerun
之间是或的关系。
1
> pytest --reruns 10 --only-rerun AssertionError --only-rerun ValueError test_demo.py -v
如下图,遇到 AssertionError 和 ValueError 的情况下都被重跑了。
2、装饰器模式
case: test_demo.py
- test_assert_error 随机抛出 AssertionError,最多失败重跑五次
- test_value_error 随机抛出 ValueError,最多失败重跑五次,失败重跑间隔为2秒
- test_value_error_condition 最多失败重跑五次,仅当系统为 win32 系统才重跑
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
#!/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-xdis
t 的--looponfail
标志不兼容 - 该插件在使用 pdb 调试时候会有不兼容性
最后总结,这个插件虽然还用,但是坑还是不少,建议主要使用失败重试次数和重试间隔的功能即可。
六、allure测试报告上的效果
在运行结果不稳定的测试用例加上失败重跑,重跑次数为3,重跑间隔为2秒:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
@pytest.mark.usefixtures("global_step")
@pytest.mark.usefixtures("kill_driver")
@pytest.mark.flaky(reruns=3, reruns_delay=2)
@allure.title('销售-营销数据分析-业绩分布页面-报表搜索成功')
def test_amazondistributionview_search_success(self, global_step):
PageLogin(global_step).get_login()
PageLogin(global_step).input_username(keyword=username)
PageLogin(global_step).input_password(keyword=password)
PageLogin(global_step).click_login_button()
time.sleep(3)
PageAmazonDistributionView(global_step).get_amazondistributionview()
PageAmazonDistributionView(global_step).click_date_select_button()
PageAmazonDistributionView(global_step).click_pre_month()
PageAmazonDistributionView(global_step).click_search_button()
time.sleep(5)
PageAmazonDistributionView(global_step).assert_order_total(assert_value='订单量汇总:413,718')
测试报告上的显示效果如下:
相关链接:
Pytest失败重跑