一、什么是Flaky Tests?
①Flaky Tests作為一個軟件工程術語,目前還缺乏適當的中文翻譯。“Flaky”這個單詞在英文中的意思是“薄而易剝落的”。倘若據此來解釋Flaky Tests,顯然會讓人覺得雲里霧里。
②所謂Flaky Tests,就是指在被測對象和測試條件都不變的情況下,有時候失敗、有時候成功的測試。因此,Flaky Tests實際上就是不穩定的測試,或者隨機失敗(隨機成功)的測試。這意味着相比Flaky Tests,Unreliable Tests或者Random-failure Tests似乎更加容易理解。
③Flaky Tests是在重復測試中出現的。自動化測試的特點之一就是測試更加易於重復執行。因此,相比手動測試,Flaky Tests更多的是自動化測試的產物。在敏捷和DevOps時代,自動化測試是軟件測試的主旋律。Flaky Tests也就隨着自動化測試的普及而成為普遍的、突出的問題。
二、Flaky Tests的影響?
①有少數人認為Flaky Tests是有益的。因為他們發現,一些隱蔽的、有價值的軟件bug,往往是通過Flaky Tests發現的,而那些永遠成功的測試反而遺漏了這些問題。這種說法不無道理。然而,主流的看法是:Flaky Tests是有害的。它的有害性體現在多個方面。
②Flaky Tests無法實現測試目標:給定被測軟件,我們希望通過測試結果來判斷軟件是否有bug。理想情況下,如果測試失敗,那么軟件存在bug;如果測試成功,那么軟件沒有bug。也就是說,測試結果與軟件質量具有確定性的關系。不幸的是,Flaky Tests的存在,破壞了這種確定性的關系,同時造成了一種可惡的隨機性。給定被測軟件,測試一會兒失敗,一會兒成功。這使得我們無法根據測試結果來判斷軟件質量是好是壞。軟件測試的目標也就無法實現。
③Flaky Tests有可能摧毀測試價值:在敏捷和DevOps中,(自動化)測試是有巨大價值的。關注測試結果的絕非只有軟件測試人員。軟件開發人員基於自動化測試的結果判斷軟件代碼的改動是否可以提交;項目管理人員基於自動化測試的結果判斷軟件產品是否可以交付。如果自動化測試的結果出現隨機性(尤其當Flaky Tests被證明是由非軟件代碼因素造成時),軟件開發人員和項目管理人員對自動化測試的信任度將大打折扣。長此以往,他們可能逐漸忽略甚至以某些方式徹底繞開自動化測試。自動化測試名存實亡之日,就是軟件bug長驅直入之時。
④Flaky Tests降低研發團隊的效率:在TDD(測試驅動開發)中,代碼倉庫的任何改動(包括產品代碼和測試代碼),通常都需要經過(自動化)回歸測試集的驗證,才能被接受。回歸測試集的觸發和執行是高頻率的。以一個100人的開發/測試團隊為例,假如每人每天提交5次代碼,那么回歸測試集每天將執行500次。如果回歸測試集有100個用例,那么每天執行的用例次數將達50000次。即使Flaky Tests的占比低至1% (谷歌目前的比例是2%),Flaky Tests失敗的概率是10%,那么每天回歸測試集也會因此失敗50次。這50次測試失敗並不能發現任何新問題,並且我們還需要花費時間檢查和確認這50次失敗是否由已知的Flaky Tests問題造成。這意味着,由於Flaky Tests的存在,研發團隊每天都會浪費一定的資源。
⑤Flaky Tests影響研發團隊的協作:在TDD中,開發人員和測試人員緊密協作,是產生高質量軟件產品的重要條件。然而,Flaky Tests的存在,有可能給雙方的合作帶來負面影響,如何理解?這是因為,當Flaky Tests出現時,開發人員會認為“測試用例在軟件代碼沒有任何改動的情況下失敗了,測試用例不穩定”;而測試人員則會認為“用同樣的測試方法,軟件一會兒工作,一會兒不工作,軟件功能不穩定”。各自有各自的道理。如果相互抱怨,難免會影響彼此的信任。
三、產生Flaky Tests的原因?
產生Flaky Tests的原因有許多可能。我們只討論幾種常見的原因。
①異步等待:在程序中,我們經常需要執行異步調用,並等待對方的回復。從發起調用請求到接收回復之間存在一個時間間隔。一般來說,為了避免浪費,我們希望等待的時間盡可能短。然而,異步調用存在不確定性。有些時候調用所花費的時間會比平時長。如果等待時間固定為平常的時間,那么調用就會失敗。通過回調或者輪詢機制,能夠在保證效率的同時,避免極端情況下的調用失敗。需要注意的是,無論是軟件代碼還是測試代碼,都可能有異步行為,都可能是因此產生Flaky Tests。
②並發:在多線程程序中,線程之間的執行順序受操作系統調度,通常表現出不確定性。多個線程同時訪問共享資源(race condition),多線程死鎖(deadlock)等,都可能導致軟件行為出現波動,從而產生Flaky Tests。
③資源泄露:如果程序在結束使用后沒有及時釋放資源,那么,或許前期程序運行正常,但是隨着時間的推進,資源最終會被消耗殆盡,程序行為將出現異常。此時,也有可能產生Flaky Tests。
④遠程服務:在許多時候,被測軟件和測試用例會依賴來自第三方的遠程服務,例如遠程的文件服務器、時鍾同步服務器、證書管理服務器等。一般來說,這些服務不在本地,而是以網絡調用的形式提供。經驗告訴我們,網絡服務並不總是可靠的。遠程服務器不穩定、網絡通信不穩定、本地網絡連接處理不穩定,都可能造成網絡服務不可靠。這種情況下是很容易出現Flaky Tests的。應對遠程服務不可靠的常用做法是將其Mock掉。
⑤測試依賴性:測試用例通常是批量執行的。多個測試用例共享測試環境、測試配置等全局資源。某個測試用例的執行可能影響后續測試用例。我們期望的是:任意測試用例的執行既不影響其他測試用例,也不受其他測試用例影響。在實際中,這是很難做到的。由測試用例依賴性所導致的Flaky Tests也並不鮮見。
四、如何應對Flaky Tests?
①對自動化測試用例進行可靠性測試:在上面的討論中,我們可以看到造成Flaky Tests的原因既可能是軟件因素,也可能是測試因素。為了減少自動化測試自身的因素,我們可以在自動化測試用例上線(進入回歸測試集)之前,對自動化測試用例進行嚴格的可靠性測試。例如,我們可以不間斷地對測試用例重復執行幾十次、上百次,只有全部通過之后,我們才認為測試用例的質量過關。這種方法的好處是可以相當程度保證自動化用例的質量,但是犧牲了效率和實時性。
②對Flaky Tests進行重跑處理:當回歸測試用例失敗時,有兩種可能。一是代碼的改動破壞了回歸測試集,產生了新的問題;二是測試用例是Flaky Tests,其失敗和代碼的改動沒有關系。那么,如何判斷是哪一種情況呢?這時,可以自動觸發重新執行失敗的用例。只有連續失敗或者達到一定失敗率的情況下,我們才認定用例不是Flaky Tests。但是,重跑也有明顯的弊端。一是犧牲了效率,因為重跑必然意味着更長的驗證時間;二是可能引入新的Flaky Tests,因為測試用例可能由於這次代碼提交而變成Flaky Tests。
③對Flaky Tests進行隔離處理:由於FlakyTests的危害性,有一種普遍做法是將所有的Flaky Tests移到一個專門的隔離區。這種情況下,任何人提交代碼將不再執行隔離區中的用例,或者隔離區中用例的執行結果不作為判定代碼改動正確性的依據。為了避免隔離區中的Flaky Tests數量膨脹,可以限制隔離區的容量或者用例在隔離區的最大存留時間,倒逼相關人員及時解決Flaky Tests。
④對Flaky Tests進行詳細記錄、深入分析並舉一反三:對FlakyTests進行重跑或者隔離,並不能消除Flaky Tests。實際上,處理Flaky Tests沒有捷徑可走。再硬的骨頭,也只能去啃。對Flaky Tests逐一地記錄、分析、解決,是唯一的途徑。認識Flaky Tests是解決Flaky Tests的基礎。可以通過自動化工具實時地監測Flaky Tests的執行情況,並收集充足的信息供研發人員分析和解決。另外,舉一反三也是很重要的。每天都有大量的新代碼(軟件代碼+測試代碼)注入到代碼倉庫中,如果沒有舉一反三,那么Flaky Tests解決的速度可能還趕不上Flaky Tests增加的速度。
五、總結
①承認Flaky Tests的客觀存在是必要的。我們已經說過,Flaky Tests是自動化測試的產物。事實上,測試的自動化程度越高,Flaky Tests的問題也就越突出。例如,自動化回歸測試集是在每次代碼提交時就執行,還是在軟件出包之后才執行,其頻率不在一個層級,出現Flaky Tests的概率也不在一個層級。有一種說法:“如果你還沒有遇到Flaky Tests,那是因為你的自動化測試還沒有做得足夠好”。
②另外,需要更多地從代碼角度重視Flaky Tests。盡管造成Flaky Tests的原因既可能是產品代碼bug,也可能是測試代碼bug。我們卻不能因為這一點就放棄從產品代碼角度排查Flaky Tests。畢竟,對於產品質量來說,漏掉一個真實產品bug的后果,遠遠比誤查一個測試代碼bug的后果嚴重得多。有一種說法:“一個bug最好的藏身之地就是在Flaky Tests中”。因為開發人員會潛意識地認為這是測試問題而不去做任何事情。這是很危險的。
③對於Flaky Tests,“投降論”和“速勝論”都不可取。對Flaky Tests互相推脫或者置之不理,會讓軟件的質量充滿風險;輕視Flaky Tests或者沒有投入資源解決Flaky Tests的意識,會讓我們吃苦頭。與Flaky Tests的斗爭是一場持久戰。只有經過長期、艱苦的努力,我們才能逐步減少、直至最終消滅Flaky Tests。
六、基於pytest測試框架的Flaky Tests
插件下載(pytest的版本需要高於pytest 6.2)
pip install pytest-ignore-flaky
pytest忽略Flaky Tests
Flaky Tests通常通過,但有時失敗。 你應該總是避免片狀測試,但不總是可能的。
@pytest.mark.flaky 插件可用於選擇性地忽略Flaky Test的失敗。
首先用 @pytest.mark.flaky 標記測試測試用例:
import pytest @pytest.mark.flaky def test_mf(): assert 0 == random.randint(0, 10)
①默認情況下, @pytest.mark.flaky 裝飾器標記的測試用例默認會執行;當用例執行結果成功時正常執行正常顯示用例結果;當用例執行結果失敗時,測試用例默認失敗重跑一次。
運行結果:
②pytest命令行參數 --ignore-flaky 運行 @pytest.mark.flaky 標記的測試用例。當用例執行成功時執行結果顯示正常;當用例執行失敗時執行結果顯示XFAIL(skip flaky test failure)
運行結果: