背景
在編寫接口case的時候,我們常遇到一個這樣的問題:
測試環境不穩定偶發接口超時(和服務無關,純粹是環境問題),然后執行接口case也因此偶發失敗。比如同一個接口case跑五次,其中有兩次失敗,另外三次都是成功的,這種偶發性的環境問題就需要我們手動重跑(還不一定能夠通過)。有沒有一個比較好的機制,保證case能夠盡最大努力通過測試呢?
這里我們介紹pytest的一個失敗重跑插件:pytest-rerunfailures
介紹
pytest-rerunfailures是一個通過重跑機制來消除不穩定失敗的pytest插件。
項目地址:https://github.com/pytest-dev/pytest-rerunfailures
安裝
安裝&運行要求:
- Python 3.6~3.9, or PyPy3
- pytest 5.0+
安裝插件
sudo pip(pip3) install pytest-rerunfailures
使用pytest-rerunfailures
使用方式有兩種:
- 命令行參數
- 裝飾器方式
命令行參數模式
示例case:test_demo.py
#!/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:pytest --reruns 10
如下圖,我們看到一共跑了兩次,第一次結果失敗,所以重跑了第二次,最終結果用R標注。(如果跑一次就成功,結果應該是'.')

例子2
指定失敗重跑最大次數為10,重跑間隔為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
#!/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調試時候會有不兼容性
最后總結,這個插件雖然還用,但是坑還是不少,建議主要使用失敗重試次數和重試間隔的功能即可。
博主:測試生財(一個不為996而996的測開碼農)
座右銘:專注測試開發與自動化運維,努力讀書思考寫作,為內卷的人生奠定財務自由。
內容范疇:技術提升,職場雜談,事業發展,閱讀寫作,投資理財,健康人生。
csdn:https://blog.csdn.net/ccgshigao
博客園:https://www.cnblogs.com/qa-freeroad/
51cto:https://blog.51cto.com/14900374
微信公眾號:測試生財(定期分享獨家內容和資源)


