單元測試
-
unittest核心工作原理
unittest中最核心的四個概念是:test case, test suite, test runner, test fixture。
- 一個TestCase的實例就是一個測試用例。什麼是測試用例呢?就是一個完整的測試流程,包括測試前準備環境的搭建(setUp),執行測試代碼(run),以及測試後環境的還原(tearDown)。元測試(unit test)的本質也就在這裏,一個測試用例是一個完整的測試單元,通過運行這個測試單元,可以對某一個問題進行驗證。
- 而多個測試用例集合在一起,就是TestSuite,而且TestSuite也可以嵌套TestSuite。
TestLoader是用來加載TestCase到TestSuite中的,其中有幾個loadTestsFrom__()方法,就是從各個地方尋找TestCase,創建它們的實例,然後add到TestSuite中,再返回一個TestSuite實例。 - TextTestRunner是來執行測試用例的,其中的run(test)會執行TestSuite/TestCase中的run(result)方法。
測試的結果會保存到TextTestResult實例中,包括運行了多少測試用例,成功了多少,失敗了多少等信息。 - 而對一個測試用例環境的搭建和銷燬,是一個fixture。
-
先上一段代碼
calculate_func.py
def add(a, b): return a+b def minus(a, b): return a-b def multi(a, b): return a*b def divide(a, b): return a/b
test_calculate_func.py
#導入unittest模塊 import unittest from calculate_func import * class TestCalculateFunc(unittest.TestCase): """Test calculate_func.py""" #每個測試方法均以 test 開頭,否則是不被unittest識別的。 def test_add(self): """Test method add(a, b)""" self.assertEqual(3, add(1, 2)) self.assertNotEqual(2, add(2, 2)) def test_minus(self): """Test method minus(a, b)""" self.assertEqual(1, minus(3, 2)) def test_multi(self): """Test method multi(a, b)""" self.assertEqual(6, multi(2, 3)) def test_divide(self): """Test method divide(a, b)""" self.assertEqual(2, divide(6, 3)) self.assertEqual(2.5, divide(5, 2)) if __name__ == '__main__': unittest.main()
一個class繼承了unittest.TestCase,便是一個測試用例,但如果其中有多個以 test 開頭的方法,那麼每有一個這樣的方法,在load的時候便會生成一個TestCase實例,如:一個class中有四個test_xxx方法,最後在load到suite中時也有四個測試用例。
寫好TestCase,然後由TestLoader加載TestCase到TestSuite,然後由TextTestRunner來運行TestSuite,運行的結果保存在TextTestResult中,我們通過命令行或者unittest.main()執行時,main會調用TextTestRunner中的run來執行,或者我們可以直接通過TextTestRunner來執行用例。這裏加個說明,在Runner執行時,默認將執行結果輸出到控制檯,我們可以設置其輸出到文件,在文件中查看結果(HTMLTestRunner,通過它可以將結果輸出到HTML中,生成漂亮的報告,它跟TextTestRunner是一樣的,從名字就能看出來,這個我們後面再說)。
-
TestSuite
上面的代碼示例瞭如何編寫一個簡單的測試,但有兩個問題,我們怎麼控制用例執行的順序呢?(這裏的示例中的幾個測試方法並沒有一定關係,但之後你寫的用例可能會有先後關係,需要先執行方法A,再執行方法B),我們就要用到TestSuite了。我們添加到TestSuite中的case是會按照添加的順序執行的。
問題二是我們現在只有一個測試文件,我們直接執行該文件即可,但如果有多個測試文件,怎麼進行組織,總不能一個個文件執行吧,答案也在TestSuite中。
import unittest from test_calculate_func import TestCalculateFunc if __name__ == '__main__': suite = unittest.TestSuite() tests = [TestCalculateFunc("test_add"), TestCalculateFunc("test_minus"), TestCalculateFunc("test_divide")] suite.addTests(tests) runner = unittest.TextTestRunner(verbosity=2) runner.run(suite)
下面方法也可以傳入測試用例
# tests = [TestCalculateFunc("test_add"), TestCalculateFunc("test_minus"), TestCalculateFunc("test_divide")] # suite.addTests(tests) # # 直接用addTest方法添加單個TestCase # suite.addTest(TestCalculateFunc("test_multi")) # 用addTests + TestLoader # loadTestsFromName(),傳入'模塊名.TestCase名' # suite.addTests(unittest.TestLoader().loadTestsFromName('test_calculate_func.TestCalculateFunc')) # suite.addTests(unittest.TestLoader().loadTestsFromNames(['test_calculate_func.TestCalculateFunc'])) # loadTestsFromTestCase(),傳入TestCase suite.addTests(unittest.TestLoader().loadTestsFromTestCase(TestCalculateFunc)) runner = unittest.TextTestRunner(verbosity=2) runner.run(suite)
用TestLoader的方法是無法對case進行排序的,同時,suite中也可以套suite。
-
將測試結果寫入文件
if __name__ == '__main__': suite = unittest.TestSuite() suite.addTests(unittest.TestLoader().loadTestsFromTestCase(TestCalculateFunc)) with open('UnittestTextReport.txt', 'a') as f: runner = unittest.TextTestRunner(stream=f, verbosity=2) runner.run(suite)
-
unittest中的參數化
pip3 install nose_parameterized
–
def login(username, password):
if username == ‘xiaogang’ and password == ‘123456’:
return True
else:
return False測試用例模塊
from parameterized import parameterized class TestCalculateFunc(unittest.TestCase): """Test mathfuc.py""" # num = 1 @parameterized.expand( [ ['xiaogang', '123456', True], # 可以是list,也可以是元祖 ['', '123456', True], ['xiaogang', '', False], ['adgadg', '123456', False] ] ) # @classmethod # def tearDownClass(cls): # print("This tearDownClass() method only called once too.") # # cls.num=2 def test_login(self, username, passwd, flag): # 這裏的參數對應上述列表裏的元素,運行的時候會遍歷上述列表裏的二維列表直到所有元素都調用運行完成 '''登錄''' res = login(username, passwd) self.assertEqual(res, flag)
-
setUp() tearDown()
setUp() 和 tearDown() 兩個方法(其實是重寫了TestCase的這兩個方法),這兩個方法在每個測試方法執行前以及執行後執行一次,setUp用來爲測試準備環境,tearDown用來清理環境,已備之後的測試。
setUp和tearDown在每次執行case前後都執行了一次。def setUp(self): print "do something before test.Prepare environment." def tearDown(self): print "do something after test.Clean up."
如果想要在所有case執行之前準備一次環境,並在所有case執行結束之後再清理環境,我們可以用類方法 setUpClass() 與 tearDownClass() (此處可以用類屬性測試):
#導入unittest模塊 import unittest from calculate_func import * class TestCalculateFunc(unittest.TestCase): """Test mathfuc.py""" num=1 # def setUp(self): # print("do something before test.Prepare environment.") # # def tearDown(self): # print("do something after test.Clean up.") @classmethod def setUpClass(cls): print("This setUpClass() method only called once.") cls.num=0 @classmethod def tearDownClass(cls): print("This tearDownClass() method only called once too.") cls.num=2 def test_add(self): """Test method add(a, b)""" self.assertEqual(3, add(1, 2)) self.assertNotEqual(2, add(2, 2)) print(TestCalculateFunc.num) def test_minus(self): """Test method minus(a, b)""" self.assertEqual(1, minus(3, 2)) print(TestCalculateFunc.num) def test_multi(self): """Test method multi(a, b)""" self.assertEqual(6, multi(2, 3)) print(TestCalculateFunc.num) def test_divide(self): """Test method divide(a, b)""" self.assertEqual(2, divide(6, 3)) self.assertEqual(2.5, divide(5, 2)) print(TestCalculateFunc.num) if __name__ == '__main__': unittest.main(verbosity=2)
-
跳過某個case
-
skip裝飾器
@unittest.skip("I don't want to run this case.") def test_divide(self): ...
skip裝飾器一共有三個
-
unittest.skip(reason)
-
unittest.skipIf(condition, reason)
-
unittest.skipUnless(condition, reason)
skip無條件跳過,skipIf當condition爲True時跳過,skipUnless當condition爲False時跳過。
@unittest.skipIf(num>0,"num的值沒有大於零") #此處調用類屬性不需要加類名 def test_add(self): 。。。
-
-
-
用HTMLTestRunner輸出漂亮的HTML報告
我們能夠輸出txt格式的文本執行報告了,但是文本報告太過簡陋,是不是想要更加高大上的HTML報告?但unittest自己可沒有帶HTML報告,我們只能求助於外部的庫了。
HTMLTestRunner是一個第三方的unittest HTML報告庫,首先我們下載HTMLTestRunner.py,並放到當前目錄下,或者你的’C:\Python27\Lib’下,就可以導入運行了。
下載地址:
-
總結
- unittest是Python自帶的單元測試框架,我們可以用其來作爲我們自動化測試框架的用例組織執行框架。
- unittest的流程:寫好TestCase,然後由TestLoader加載TestCase到TestSuite,然後由TextTestRunner來運行TestSuite,運行的結果保存在TextTestResult中,我們通過命令行或者unittest.main()執行時,main會調用TextTestRunner中的run來執行,或者我們可以直接通過TextTestRunner來執行用例。
- 一個class繼承unittest.TestCase即是一個TestCase,其中以 test 開頭的方法在load時被加載爲一個真正的TestCase。
- verbosity參數可以控制執行結果的輸出,0 是簡單報告、1 是一般報告、2 是詳細報告。
- 可以通過addTest和addTests向suite中添加case或suite,可以用TestLoader的loadTestsFrom__()方法。
- 用 setUp()、tearDown()、setUpClass()以及 tearDownClass()可以在用例執行前佈置環境,以及在用例執行後清理環境
- 我們可以通過skip,skipIf,skipUnless裝飾器跳過某個case,或者用TestCase.skipTest方法。
- 參數中加stream,可以將報告輸出到文件:可以用TextTestRunner輸出txt報告,以及可以用HTMLTestRunner輸出html報告。
-
unittest處理關聯接口
源文件
def login(username, password): if username == 'vip001' and password == '123456': return 'vip' else: return False def buy(token): if token == 'vip': return True else: return False
測試用例
def login(self): # 注意,這裏不以test開頭命名,就是一普通方法,在執行測試用例的時候並不會運行,調用的時候纔會 res = login('vip001', '123456') self.assertEqual(res, 'vip') return res def test_login_cj(self): res = self.login() # 調用登錄方法,獲取sign self.jp = buy(res) self.assertEqual(self.jp, True)
-
進階