筆者在執行自動化測試用例時,會發現有時候用例失敗并非代碼問題,而是由于服務正在發版,導致請求失敗,從而降低了自動化用例的穩定性,最后還要花時間定位到底是自身case的原因還是業務邏輯問題,還是其他原因,增加了定位成本。增加容錯機制,失敗重試,會解決大部分由于網絡原因、服務重啟等原因造成的case失敗問題。那該如何增加失敗重試機制呢?帶著問題我們一起探索。
02 pytest-rerunfailures 插件
先給出答案,我們將使用?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"])
03 運行機制
到這里,應該會使用了。我們簡單概括一下它的運行機制:
1、pytest 通過插件系統加載 pytest-rerunfailures 插件,并啟用其功能。
2、當 pytest 運行測試時,對每個測試用例的執行進行監控。
3、如果一個測試用例執行失敗,pytest-rerunfailures 插件會捕獲該失敗,并判斷是否需要進行重試。
4、如果該測試用例被標記為可重試(使用了?@pytest.mark.flaky?裝飾器),插件會根據配置的重試次數和延遲時間重新運行該測試用例。
5、在每次重試之前,插件會根據設置的延遲時間暫停一段時間。
6、如果測試用例在重試次數達到上限之前通過了,即成功執行,則插件會將該測試用例標記為通過。
7、如果測試用例在達到最大重試次數后仍然失敗,則插件會返回最后一次失敗的結果作為最終的結果。
總結起來,pytest-rerunfailures 插件在測試執行失敗時,根據配置的重試次數和延遲時間重新運行測試用例,并根據重試結果判斷最終的測試結果。這樣可以提高測試用例的穩定性,并減少偶發性問題導致的測試失敗。
04 源碼解讀
使用階段,我們使用?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 的配置中加入了一行字符串。
-
這行字符串指定了標記名稱為 "flaky",并使用參數?reruns?和?reruns_delay?來說明重試次數和延遲時間的默認值。
-
標記的含義是將被標記的測試用例重新運行最多 "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,表示已經實現重試機制。
總結起來,這段代碼通過循環執行測試用例,并在滿足重試條件時進行重試,直到滿足退出條件為止。在重試過程中,會記錄日志、生成報告,并根據設定的重試次數和延遲時間進行控制。
04 最后???????
失敗重試功能并不是解決所有測試問題的法寶,它應該被視為一種提高測試穩定性的輔助手段。在使用 pytest-rerunfailures 進行失敗重試時,我們應該仔細分析失敗的原因,確保重試次數和延遲時間設置合理,并與團隊成員共同討論和決定是否需要重試測試用例。
總結起來,pytest-rerunfailures 是一個非常有用的工具,可以提高測試用例的穩定性。通過使用它,我們可以輕松地實現失敗重試功能,并減少由于偶發性問題導致的測試失敗。
另外源碼中,看到了?pytest_runtest_logstart?等,可能有些同學不明白這是干嘛用的,之后我們專門寫一篇文章來介紹它的作用。
感謝每一個認真閱讀我文章的人,禮尚往來總是要有的,雖然不是什么很值錢的東西,如果你用得到的話可以直接拿走:
這些資料,對于【軟件測試】的朋友來說應該是最全面最完整的備戰倉庫,這個倉庫也陪伴上萬個測試工程師們走過最艱難的路程,希望也能幫助到你!有需要的小伙伴可以點擊下方小卡片領取???
?