Python:單元測試框架unittest
1、什么是單元測試
測試函數/方法或者一段代碼,用于檢驗被測代碼的一個很小的、很明確的功能是否正確,通常是開發做。
在Python中的單元測試框架有Unittest和Pytest,現在總結Unittest。
2、單元測試框架
1、Unittest框架的五個概念
-
test fixture:測試固件
-
test case:測試用例
-
test suit:測試套件
-
test runner:測試運行器
-
test loader:測試加載器
1) Test Case
一個TestCase 的實例就是一個測試用例。什么是測試用例呢?就是一個完整的測試流程,包括測試前準備環境的搭建(setUp),執行測試代碼(run),以及測試后環境的還原(tearDown)。單元測試(unittest)的本質也就在這里。一個測試用例是一個完整的測試單元,通過運行這個測試單元,可以對某一個問題進行驗證。
2) Test Suite
多個測試用例集合在一起,就是TestSuite,而且TestSuite也可以嵌套Test Suite。
3) Test runner
是用來執行測試用例的,其中的run(test)會執行TestSuite/TestCase 中的run(result) 方法。
4) Test Loader
是用來加載TestCase到Test Suite中的,其中幾個 loadTestsFrom__() 方法,就是從各個地方尋找TestCase,創建它們的實例,然后add到TestSuite中,再返回一個TestSuite 實例。
5) Test fixture
對一個測試用例環境的搭建和銷毀,是一個fixture,通過覆蓋Test Case的setUp() 和tearDown() 方法來實現。
2、Unittest中的兩個方法
方法1:setup 和teardown
1)結構:
測試基本setup方法:第一個測試開始之前只執行一次
多個類
類:
類setup方法:在當前類的第一個測試方法調用前執行,只執行一次
setup方法:在執行測試方法前的一些環境或者數據相關的準備
測試的方法:每個測試方法執行前,都會執行setup方法,然后執行teardown方法
teardown方法:在執行方法后的一些環境或者數據相關的清理
類teardown方法:在當前類的最后一個測試方法調用后執行,只執行一次
測試級別teardown方法:所有的測試結束之后只執行一次
2)說明
setUp:實現測試前的初始化工作
tearDown:實現測試結束后的清理工作
用例之間按用例ASCII碼的順序加載,數字和字母順序為 0~9,A~Z,a~z。
方法2:斷言方法

3、加載測試用例的四種方法
首先實例化:
suite = unittest.TestSuite()
loader = unittest.TestLoader()
方法一:通過測試用例類進行加載
suite.addTest(loader.loadTestsFromTestCase(MyTest1))
suite.addTest(loader.loadTestsFromTestCase(MyTest2))
方法二:通過測試用例模版去加載
suite.addTest(loader.loadTestsFromTestModule(MyTest1))
suite.addTest(loader.loadTestsFromTestModule(MyTest2))
方法三:通過路徑加載
import os path = os.path.dirname(os.path.abspath(__file__)) suite.addTest(loader.discover(path))
方法四:逐條加載 low
case1 = MyTest1("test1") case2 = MyTest1("test2") suite.addTest(case1) suite.addTest(case2)
3、代碼舉例
舉例1:
#encoding=utf-8 import unittest # 被測試類 class myclass(object): @classmethod #可以不加,但是調用前需要實例化 def sum(self, a, b): return a + b #將兩個傳入參數進行相加操作 @classmethod #可以不加,但是調用前需要實例化 def sub(self, a, b): return a - b #將兩個傳入參數進行相減操作 class mytest(unittest.TestCase): @classmethod #必須加 def setUpClass(cls):#每個測試類的setup方法,只會執行一次 "初始化類固件,固件是準備的意思" print ( "----setUpClass") @classmethod #必須加 def tearDownClass(cls):#每個測試類的tearDown方法,只會執行一次 "重構類固件" print ( "----tearDownClass") # 初始化工作 def setUp(self):#每個測試方法均會執行一次 self.a = 3 self.b = 1 print ( "--setUp") # 退出清理工作 def tearDown(self):#每個測試方法均會執行一次 print ( "--tearDown") # 具體的測試用例,一定要以test開頭 def testsum(self): #每個測試用例執行成功一次,打印一個 . # 斷言兩數之和的結果是否是4 self.assertEqual(myclass.sum(self.a, self.b), 4, 'test sum fail') def testsub(self): # 斷言兩數之差的結果是否是2 self.assertEqual(myclass.sub(self.a, self.b), 2, 'test sub fail') if __name__ == '__main__': unittest.main() # 啟動單元測試
執行結果:

舉例2:跳過指定的測試用例的三種方法
# coding=utf-8 import random import unittest import sys class TestSequenceFunctions(unittest.TestCase): a = 6 def setUp(self): self.seq = list(range(10)) @unittest.skip("skipping") # 無條件忽略該測試方法 def test_shuffle(self): random.shuffle(self.seq) self.seq.sort() self.assertEqual(self.seq, list(range(10))) self.assertRaises(TypeError, random.shuffle, (1, 2, 3)) # 如果變量a > 5,則忽略該測試方法 @unittest.skipIf(a > 5, "condition is not satisfied!") def test_choice(self): element = random.choice(self.seq) self.assertTrue(element in self.seq) # 除非執行測試用例的平臺是Linux平臺,否則忽略該測試方法 win32是windows @unittest.skipUnless(sys.platform.startswith("linux"), "requires Linux") #被忽略時打印 第二個參數 def test_sample(self): with self.assertRaises(ValueError): random.sample(self.seq, 20) for element in random.sample(self.seq, 5): self.assertTrue(element in self.seq) if __name__ == '__main__': # unittest.main() # 指定加載哪幾個測試類,生成測試集合 suite = unittest.TestLoader().loadTestsFromTestCase(TestSequenceFunctions) suite = unittest.TestSuite(suite) unittest.TextTestRunner(verbosity = 2).run(suite)
結果:
D:\學習\自動化\單元測試>py -3 e4_unittest_skip.py test_choice (__main__.TestSequenceFunctions) ... skipped 'condition is not satisfied!' test_sample (__main__.TestSequenceFunctions) ... skipped 'requires Linux' test_shuffle (__main__.TestSequenceFunctions) ... skipped 'skipping' ---------------------------------------------------------------------- Ran 3 tests in 0.003s OK (skipped=3)
舉例3:常用斷言舉例
#encoding=utf-8 import unittest import random # 被測試類 class MyClass(object): @classmethod def sum(self, a, b): return a + b @classmethod def div(self, a, b): return a / b @classmethod def retrun_None(self): return None # 單元測試類 class MyTest(unittest.TestCase): # assertEqual()方法實例 def test_assertEqual(self): # 斷言兩數之和的結果 # 一般不需要捕獲異常,unittest會自動捕獲異常 try: a, b = 1, 2 sum = 3 self.assertEqual(a + b, sum, '斷言失敗,%s + %s != %s' %(a, b, sum)) except AssertionError as e: print (e) # assertNotEqual()方法實例 def test_assertNotEqual(self): # 斷言兩數之差的結果 try: a, b = 5, 2 res = 1 self.assertNotEqual(a - b, res, '斷言失敗,%s - %s != %s' %(a, b, res)) except AssertionError as e: print (e) # assertTrue()方法實例 def test_assertTrue(self): # 斷言表達式的為真 try: self.assertTrue(1 == 1, "表達式為假") except AssertionError as e: print (e) # assertFalse()方法實例 def test_assertFalse(self): # 斷言表達式為假 try: self.assertFalse(3 == 2, "表達式為真") except AssertionError as e: print (e) # assertIs()方法實例 def test_assertIs(self): # 斷言兩變量類型屬于同一對象 try: a = 12 b = a self.assertIs(a, b, "%s與%s不屬于同一對象" %(a, b)) except AssertionError as e: print (e) # test_assertIsNot()方法實例 def test_assertIsNot(self): # 斷言兩變量類型不屬于同一對象 try: a = 12 b = "test" self.assertIsNot(a, b, "%s與%s屬于同一對象" %(a, b)) except AssertionError as e: print (e) # assertIsNone()方法實例 def test_assertIsNone(self): # 斷言表達式結果為None try: result = MyClass.retrun_None() self.assertIsNone(result, "not is None") except AssertionError as e: print (e) # assertIsNotNone()方法實例 def test_assertIsNotNone(self): # 斷言表達式結果不為None try: result = MyClass.sum(2, 5) self.assertIsNotNone(result, "is None") except AssertionError as e: print (e) # assertIn()方法實例 def test_assertIn(self): # 斷言對象A是否包含在對象B中 try: strA = "this is a test" strB = "is" self.assertIn(strB, strA, "%s不包含在%s中" %(strB, strA)) except AssertionError as e: print (e) # assertNotIn()方法實例 def test_assertNotIn(self): # 斷言對象A不包含在對象B中 try: strA = "this is a test" strB = "Selenium" self.assertNotIn(strB, strA, "%s包含在%s中" %(strB, strA)) except AssertionError as e: print (e) # assertIsInstance()方法實例 def test_assertIsInstance(self): # 測試對象A的類型是否值指定的類型 try: x = MyClass y = object self.assertIsInstance(x, y, "%s的類型不是%s" %(x, y)) except AssertionError as e: print (e) # assertNotIsInstance()方法實例 def test_assertNotIsInstance(self): # 測試對象A的類型不是指定的類型 try: a = 123 b = str self.assertNotIsInstance(a, b, "%s的類型是%s" %(a, b)) except AssertionError as e: print (e) # assertRaises()方法實例 def test_assertRaises(self): # 測試拋出的指定的異常類型 # assertRaises(exception) with self.assertRaises(TypeError) as cm: random.sample([1,2,3,4,5], "j") # 打印詳細的異常信息 #print "===", cm.exception # assertRaises(exception, callable, *args, **kwds) try: self.assertRaises(ZeroDivisionError, MyClass.div, 3, 0) except ZeroDivisionError as e: print (e) # assertRaisesRegexp()方法實例 def test_assertRaisesRegexp(self): # 測試拋出的指定異常類型,并用正則表達式具體驗證 # assertRaisesRegexp(exception, regexp) with self.assertRaisesRegex(ValueError, 'literal') as ar: int("xyz") # 打印詳細的異常信息 #print ar.exception # 打印正則表達式 #print "re:",ar.expected_regexp # assertRaisesRegexp(exception, regexp, callable, *args, **kwds) try: self.assertRaisesRegexp(ValueError, "invalid literal for.*XYZ'$", int, 'XYZ') except AssertionError as e: print (e) if __name__ == '__main__': # 執行單元測試 unittest.main()
舉例4:生成圖形化測試報告
導入文件HTMLTestRunner.py ;放到python的安裝目錄下(D:\Python37-32\Lib\site-packages)
運行對應的主方法:
import unittest import HTMLTestRunner if __name__ == '__main__' : # 加載當前目錄下所有有效的測試模塊(以test開頭的文件),“.”表示當前目錄 testSuite = unittest.TestLoader().discover('.') filename = "test.html" # 定義個報告存放路徑,支持相對路徑。 # 以二進制方式打開文件,準備寫 fp = open(filename, 'wb') # 使用HTMLTestRunner配置參數,輸出報告路徑、報告標題、描述,均可以配 runner = HTMLTestRunner.HTMLTestRunner(stream = fp, title = 'Report_title', description = 'Report_description') # 運行測試集合 runner.run(testSuite)
結果:

舉例5:單元測試框架跑UI測試
#encoding=utf-8 import unittest from selenium import webdriver import time class GloryRoad(unittest.TestCase): def setUp(self): # 啟動Chrome瀏覽器 # 驅動文件位置要改,本地自己的驅動 self.driver = webdriver.Chrome() def testSoGou(self): # 訪問搜狗首頁 self.driver.get("http://sogou.com") # 清空搜索輸入框默認內容 self.driver.find_element_by_id("query").clear() # 在搜索輸入框中輸入“光榮之路自動化測試” self.driver.find_element_by_id("query").send_keys(u"WebDriver實戰寶典") # 單擊“搜索”按鈕 self.driver.find_element_by_id("stb").click() # 等待3秒 time.sleep(3) assert u"吳曉華" in self.driver.page_source, u"頁面中不存在要尋找的關鍵字!".encode("gbk") def testBing(self): # 訪問bing首頁 self.driver.get("http://cn.bing.com") # 清空搜索輸入框默認內容 self.driver.find_element_by_id("sb_form_q").clear() # 在搜索輸入框中輸入“光榮之路自動化測試” self.driver.find_element_by_id("sb_form_q").send_keys(u"WebDriver實戰寶典") # 單擊“搜索”按鈕 self.driver.find_element_by_id("sb_form_go").click() # 等待3秒 time.sleep(3) assert u"吳曉華" in self.driver.page_source, u"頁面中不存在要尋找的關鍵字!".encode("gbk") def tearDown(self): # 退出瀏覽器 self.driver.quit() if __name__ == '__main__': unittest.main()
舉例6:單元測試和ddt數據驅動結合
安裝包:pip3 install ddt
代碼:
import unittest import ddt @ddt.ddt class Praddt(unittest.TestCase): def setUp(self): print("my test start!") def tearDown(self): print("my test complete!") @ddt.data(["admin", "1qaz", "OK"], #一組數據,每一行都會觸發運行 ["admin", "", "ERROR"], #另一組數據 ["1", "1qaz", "ERROR"], #另一組數據.. ["admin", "1234", "ERROR"], ["Admin", "1qaz", "ERROR"]) #實現了數據驅動,可以執行5次 @ddt.unpack def test_ddt(self, user, passwd, expect_value): print(user,passwd,expect_value) self.assertTrue(len(user)>0) if __name__ == '__main__': # 執行單元測試 unittest.main()
測試結果:

浙公網安備 33010602011771號