一篇文章搞懂Python Unittest測試方法的執(zhí)行順序
Unittest
unittest大家應(yīng)該都不陌生。它作為一款博主在5-6年前最常用的單元測試框架,現(xiàn)在正被pytest,nose慢慢蠶食。
漸漸地,看到大家更多的討論的內(nèi)容從unittest+HTMLTestRunner變?yōu)閜ytest+allure2等后起之秀。
不禁感慨,終究是自己落伍了,跟不上時(shí)代的大潮了。
回到主題
感慨完了,回到正文。雖然unittest正在慢慢被放棄,但是它仍然是一款很全面的測試框架。
今天在群里看到有個(gè)群友的一番言論,激起了我的一番回憶。
自己以前是知道unittest的執(zhí)行順序并不是按照編寫test方法的順序執(zhí)行,而是按照字典序執(zhí)行的。但遺憾的是我都是投機(jī)取巧去解決的問題(后面會講)。
下面我們就來探討下unittest類的test方法的執(zhí)行順序問題。
源碼初窺
研究一下源碼(unittest.TestLoader)可以發(fā)現(xiàn),在加載一個(gè)class下面的test方法的時(shí)候,原生Loader進(jìn)行了排序,并且根據(jù)functools.cmp_to_key方法對測試方法列表進(jìn)行了排序。
我們知道,unittest是不需要我們指定對應(yīng)的方法,說白了,它是從類里面自動(dòng)獲取到咱們的方法,并約定了以test開頭的方法都會被視為測試方法。
查詢一下self.sortTestMethodsUsing(這個(gè)是一個(gè)排序的方式)。
可以看到這個(gè)比較方法寫的很明確了,如果x < y那么返回-1,x = y則返回0,x > y返回1。
其實(shí)大家可能不知道Python里面的字符串也是可以比較的,在此必須說明一下字典序。我們來看看這個(gè)例子:
a = "abc" b = "abcd" c = "abce" print(a > b) print(b > c)
猜猜看執(zhí)行結(jié)果,很顯然,字典序的比較,是按A-Z的順序來比較的,如果前綴一樣但長度不一樣,那么長度長的那個(gè),字典序靠后。
了解了字典序以后,我們就不難知道,在unittest里面它尋找case的過程可以這樣簡化:
- 找到對應(yīng)類下面以test開頭的測試方法
- 對他們進(jìn)行字典序排序
- 依次執(zhí)行
這樣就不難解釋為什么我們有時(shí)候?qū)懙腸ase不按照自己想的順序來。
回到問題的本質(zhì)
搞清楚為什么用例會亂,那就想到對應(yīng)的解決方案。由于修改源碼是不太合適的,那我們有2個(gè)策略去達(dá)成目的。
比如我有多個(gè)test方法:
class Testcase(unittest.TestCase): def setUp(self) -> None: pass def test_1(self): print("執(zhí)行第一個(gè)") def test_2(self): print("第二個(gè)") def test_3(self): print("第三個(gè)") def test_10(self): print("第四個(gè)") def test_11(self): print("第五個(gè)") def tearDown(self) -> None: pass if __name__ == "__main__": unittest.main()
執(zhí)行起來,按照字典序,其實(shí)是1 10 11 2 3的順序。
1. 以字典序的方式編寫test方法
我們可以手動(dòng)修改test方法的名稱,這也是我早前的處理方式。也就是說把想要先執(zhí)行的case字典序排到前面:
class Testcase(unittest.TestCase): def setUp(self) -> None: pass def test_0_1(self): print("執(zhí)行第一個(gè)") def test_0_2(self): print("第二個(gè)") def test_0_3(self): print("第三個(gè)") def test_1_0(self): print("第四個(gè)") def test_1_1(self): print("第五個(gè)") def tearDown(self) -> None: pass
我們可以把數(shù)字按位數(shù)拆開,個(gè)位數(shù)就把10位補(bǔ)0,這樣就能達(dá)到效果,如果會寫100個(gè)case,我們就需要補(bǔ)2個(gè)0,比如0_0_1,當(dāng)然一個(gè)文件里面也不會有太多case。
如果遇到test_login這種怎么辦呢,不是數(shù)字結(jié)尾的方法。
其實(shí)是一樣的,可以寫成test_數(shù)字_業(yè)務(wù)的模式。番貨寫了一個(gè)裝飾器專門解決這樣的問題,大家可以去參考下。
2. 回歸本質(zhì),從根本解決問題
方案1用了番貨的裝飾器,好是好,但是改變了方法本身的名稱,我們其實(shí)可以針對他的排序方式入手,按照我們編寫case的順序排序測試方法,就能達(dá)到想要的目的。
說說思路:
- 手寫一個(gè)loader繼承自TestLoader類,改寫里面的排序方法
- 在unittest運(yùn)行的時(shí)候傳入這個(gè)新的loader
來看看完整代碼,注釋里面寫的很完善了。
import unittest class MyTestLoader(unittest.TestLoader): def getTestCaseNames(self, testcase_class): # 調(diào)用父類的獲取“測試方法”函數(shù) test_names = super().getTestCaseNames(testcase_class) # 拿到測試方法list testcase_methods = list(testcase_class.__dict__.keys()) # 根據(jù)list的索引對testcase_methods進(jìn)行排序 test_names.sort(key=testcase_methods.index) # 返回測試方法名稱 return test_names class Testcase(unittest.TestCase): def setUp(self) -> None: pass def test_1(self): print("執(zhí)行第一個(gè)") def test_2(self): print("第二個(gè)") def test_3(self): print("第三個(gè)") def test_10(self): print("第四個(gè)") def test_11(self): print("第五個(gè)") def tearDown(self) -> None: pass if __name__ == "__main__": unittest.main(testLoader=MyTestLoader())
執(zhí)行了一下還是不對,是不是哪里出了什么問題呢?
是因?yàn)閜ycharm有一種默認(rèn)的unittest的調(diào)試方法,我們要改成普通的方法去執(zhí)行。
試試用控制臺執(zhí)行:
總結(jié)
到此這篇關(guān)于如何通過一篇文章搞懂Python Unittest測試方法的執(zhí)行順序的文章就介紹到這了,更多相關(guān)Python Unittest測試執(zhí)行順序內(nèi)容請搜索本站以前的文章或繼續(xù)瀏覽下面的相關(guān)文章希望大家以后多多支持本站!
版權(quán)聲明:本站文章來源標(biāo)注為YINGSOO的內(nèi)容版權(quán)均為本站所有,歡迎引用、轉(zhuǎn)載,請保持原文完整并注明來源及原文鏈接。禁止復(fù)制或仿造本網(wǎng)站,禁止在非www.sddonglingsh.com所屬的服務(wù)器上建立鏡像,否則將依法追究法律責(zé)任。本站部分內(nèi)容來源于網(wǎng)友推薦、互聯(lián)網(wǎng)收集整理而來,僅供學(xué)習(xí)參考,不代表本站立場,如有內(nèi)容涉嫌侵權(quán),請聯(lián)系alex-e#qq.com處理。