Unittest官方 4個重要概念:
Test fixture(測試固件):初始化、清除
Test case(測試用例),test suite(測試套件),test runner(測試運行器)
兩種單元測試加載方法:
1.unittest.main()
2.將所有test case 添加到test suit中,然后一次性加載
知識點:
1.測試類要繼承unittest.TestCase類
2.每個用例方法 test開頭(self)
3.setUp和tearDown方法在每個用例執行前后都會執行
4.unittest.main()執行同一模塊內unittest子類所有方法
實例
被測試模塊 Calc.py
后續部分用例會引用此模塊
1 # coding=utf-8 2 class Calc(object): 3 def add(self, x, y, *d): 4 # 加法計算 5 result = x + y 6 for i in d: 7 result += i 8 return result 9 10 def sub(self, x, y, *d): 11 # 減法計算 12 result = x - y 13 for i in d: 14 result -= i 15 return result 16 17 @classmethod 18 def mul(cls, x, y, *d): 19 # 乘法計算 20 result = x * y 21 for i in d: 22 result *= i 23 return result 24 25 @staticmethod 26 def div(x, y, *d): 27 # 除法計算 28 if y != 0: 29 result = x / y 30 else: 31 return -1 32 for i in d: 33 if i != 0: 34 result /= i 35 else: 36 return -1 37 return result
例子1 用例初始化清除setUp、tearDown
1 #encoding=utf-8 2 import unittest 3 import random 4 class TestSequenceFunctions(unittest.TestCase): 5 def setUp(self): 6 # 初始化一個遞增序列 7 self.seq = range(10) 8 print "setup completed!" 9 10 def test_run(self): 11 # 從序列seq中隨機選取一個元素 12 element = random.choice(self.seq) 13 # 驗證隨機元素確實屬於列表中 14 self.assertTrue(element in self.seq) 15 16 def test_sth(self): 17 assert 1==1 18 19 def tearDown(self): 20 print "tearDown completed" 21 22 class TestDictValueFormatFunctions(unittest.TestCase): 23 def setUp(self): 24 self.seq = range(10) 25 26 def test_shuffle(self): 27 # 隨機打亂原seq的順序 28 random.shuffle(self.seq) 29 self.seq.sort() 30 self.assertEqual(self.seq, range(10)) 31 # 驗證執行函數時拋出了TypeError異常 32 self.assertRaises(TypeError, random.shuffle, (1, 2, 3)) 33 34 if __name__ == '__main__': 35 unittest.main()
例子2 類初始化清除setUpClass(cls)、tearDownClass(cls)
類里所有用例執行前后僅執行一次
1 #encoding=utf-8 2 import unittest 3 4 # 被測試類 5 class myclass(object): 6 @classmethod 7 def sum(self, a, b): 8 return a + b #將兩個傳入參數進行相加操作 9 10 @classmethod 11 def sub(self, a, b): 12 return a - b #將兩個傳入參數進行相減操作 13 14 15 class mytest(unittest.TestCase): 16 @classmethod 17 def setUpClass(cls): 18 "初始化類固件" 19 print "----setUpClass" 20 21 @classmethod 22 def tearDownClass(cls): 23 "重構類固件" 24 print "----tearDownClass" 25 26 # 初始化工作 27 def setUp(self): 28 self.a = 3 29 self.b = 1 30 print "--setUp" 31 32 # 退出清理工作 33 def tearDown(self): 34 print "--tearDown" 35 36 # 具體的測試用例,一定要以test開頭 37 def testsum(self): 38 # 斷言兩數之和的結果是否是4 39 self.assertEqual(myclass.sum(self.a, self.b), 4, 'test sum fail') 40 41 def testsub(self): 42 # 斷言兩數之差的結果是否是2 43 self.assertEqual(myclass.sub(self.a, self.b), 2, 'test sub fail') 44 45 46 if __name__ == '__main__': 47 unittest.main() # 啟動單元測試
例子3 按特定順序執行用例
unittest框架用例執行順序是按照用例名字符的ASCII碼順序
用例數小於10:可將用例名命名為test加0-9, 按數字由小到大順序執行
用例數大於10:可將用例名字首字母開始命名 a-Z按順序執行,如第一字母相同,則第二字母再按a-Z順序排序,以此類推
suit.addTest(class(‘testcase’)) 按此方式添加的用例會按添加順序執行
此代碼需要文本開頭的被測試模塊Calc.py
1 #encoding=utf-8 2 import unittest 3 from Calc import Calc 4 5 class MyTest(unittest.TestCase): 6 7 @classmethod 8 def setUpClass(self): 9 print u"單元測試前,創建Calc類的實例" 10 self.c = Calc() 11 12 # 具體的測試用例,一定要以test開頭,執行順序按照字母順序開頭 13 def test_0add(self): 14 print "run add()" 15 self.assertEqual(self.c.add(1, 2, 12), 15, 'test add fail') 16 17 def test_1sub(self): 18 print "run sub()" 19 self.assertEqual(self.c.sub(2, 1, 3), -2, 'test sub fail') 20 21 def test_2mul(self): 22 print "run mul()" 23 self.assertEqual(Calc.mul(2, 3, 5), 30, 'test mul fail') 24 25 def test_3div(self): 26 print "run div()" 27 self.assertEqual(Calc.div(8, 2, 4), 1, 'test div fail') 28 29 if __name__ == '__main__': 30 unittest.main()# 啟動單元測試 31 32 33 if __name__ == '__main__': 34 suite = unittest.TestSuite() 35 suite.addTest(MyTest(‘test_2mu’)) 36 suite.addTest(MyTest(‘test_1sub’)) 37 runner = unittest.TextTestRunner() 38 runner.run(suite)
例子4 忽略測試方法,不想執行的用例,可跳過
unittet可以分無條件忽略和有條件忽略,通過裝飾器實現
跳過用例時打印括號內后面參數的內容(跳過原因)
@unittest.skip(reason) 裝飾器:無條件跳過裝飾的測試,並說明跳過測試的原因。
@unittest.skipIf(a > 5, "condition is not satisfied!") 條件為真時,跳過裝飾的測試,並說明跳過測試的原因。
@unittest.skipUnless(sys.platform.startswith("linux"), "requires Linux")條件為假時,跳過裝飾的測試,並說明跳過測試的原因。
@unittest.expectedFailure(): expectedFailure()測試標記為失敗。
1 # coding=utf-8 2 import random 3 import unittest 4 import sys 5 6 class TestSequenceFunctions(unittest.TestCase): 7 a = 1 8 9 def setUp(self): 10 self.seq = list(range(10)) 11 12 @unittest.skip("skipping") # 無條件忽略該測試方法 13 def test_shuffle(self): 14 random.shuffle(self.seq) 15 self.seq.sort() 16 self.assertEqual(self.seq, list(range(10))) 17 self.assertRaises(TypeError, random.shuffle, (1, 2, 3)) 18 19 # 如果變量a > 5,則忽略該測試方法 20 @unittest.skipIf(a > 5, "condition is not satisfied!") 21 def test_choice(self): 22 element = random.choice(self.seq) 23 self.assertTrue(element in self.seq) 24 25 # 除非執行測試用例的平台是Linux平台,否則忽略該測試方法是windows 26 @unittest.skipUnless(sys.platform.startswith("linux"), "requires Linux") 27 def test_sample(self): 28 with self.assertRaises(ValueError): 29 random.sample(self.seq, 20) 30 for element in random.sample(self.seq, 5): 31 self.assertTrue(element in self.seq) 32 33 34 if __name__ == '__main__': 35 # unittest.main() 36 Case = unittest.TestLoader().loadTestsFromTestCase(TestSequenceFunctions) 37 suite = unittest.TestSuite(Case) 38 unittest.TextTestRunner(verbosity = 2).run(suite)
例子5 測試集合,批量執行測試用例
根據給定的測試類,獲取其中的所有以“test”開頭的測試方法,並返回一個測試套件
suite1=unittest.TestLoader().loadTestsFromTestCase(Class)
將多個測試類加載到測試套件中
suite = unittest.TestSuite([suite2, suite1,suite3])
通過調整suit2和suite1的順序,可以設定執行順序
設置verbosity = 2,可以打印出更詳細的執行信息
unittest.TextTestRunner(verbosity = 2).run(suite)
方式1:模塊中直接調用unittest.main()
此代碼需要文本開頭的被測試模塊Calc.py
此代碼模塊名:TestCalc.py(下一段代碼會用到)
1 #encoding=utf-8 2 import unittest 3 import random 4 from Calc import Calc 5 6 class TestCalcFunctions(unittest.TestCase): 7 def setUp(self): 8 self.c=Calc() 9 print "setup completed!" 10 11 def test_sum(self): 12 self.assertTrue(self.c.add(1,2,3,4)==10) 13 14 def test_sub(self): 15 self.assertTrue(self.c.sub(100,20,30,40)==10) 16 17 def test_mul(self): 18 self.assertTrue(self.c.mul(1,2,3,40)==240) 19 20 def test_div(self): 21 self.assertTrue(self.c.div(100,10,2)==5) 22 23 def tearDown(self): 24 25 print "test completed!" 26 27 28 def tearDown(self): 29 print "tearDown completed" 30 31 if __name__ == '__main__': 32 unittest.main()
方式2:測試用例類加載到一個套件中
1 #encoding=utf-8 2 import random 3 import unittest 4 from TestCalc import TestCalcFunctions 5 class TestSequenceFunctions(unittest.TestCase): 6 def setUp(self): 7 self.seq = range(10) 8 9 def tearDown(self): 10 pass 11 12 def test_choice(self): 13 # 從序列seq中隨機選取一個元素 14 element = random.choice(self.seq) 15 # 驗證隨機元素確實屬於列表中 16 self.assertTrue(element in self.seq) 17 18 def test_sample(self): 19 # 驗證執行的語句是否拋出了異常 20 with self.assertRaises(ValueError): 21 random.sample(self.seq, 20) 22 for element in random.sample(self.seq, 5): 23 self.assertTrue(element in self.seq) 24 25 26 class TestDictValueFormatFunctions(unittest.TestCase): 27 def setUp(self): 28 self.seq = range(10) 29 30 def tearDown(self): 31 pass 32 33 def test_shuffle(self): 34 # 隨機打亂原seq的順序 35 random.shuffle(self.seq) 36 self.seq.sort() 37 self.assertEqual(self.seq, range(10)) 38 # 驗證執行函數時拋出了TypeError異常 39 self.assertRaises(TypeError, random.shuffle, (1, 2, 3)) 40 41 if __name__ == '__main__': 42 # 根據給定的測試類,獲取其中的所有以“test”開頭的測試方法,並返回一個測試套件 43 suite1=unittest.TestLoader().loadTestsFromTestCase(TestSequenceFunctions) 44 suite2= unittest.TestLoader().loadTestsFromTestCase(TestDictValueFormatFunctions) 45 suite3 = unittest.TestLoader().loadTestsFromTestCase(TestCalcFunctions) 46 # 將多個測試類加載到測試套件中 47 suite = unittest.TestSuite([suite2, suite1,suite3]) #通過調整suit2和suite1的順序,可以設定執行順序 48 # 設置verbosity = 2,可以打印出更詳細的執行信息 49 unittest.TextTestRunner(verbosity = 2).run(suite)
例子6 斷言
self.assertRaises(TypeError,callable,argvs)驗證執行函數時拋出了TypeError異常
執行用例時出現異常,該用例會打印一個E;斷言失敗則會打印F,代表Fail
1 #encoding=utf-8 2 import unittest 3 import random 4 5 # 被測試類 6 class MyClass(object): 7 @classmethod 8 def sum(self, a, b): 9 return a + b 10 11 @classmethod 12 def div(self, a, b): 13 return a / b 14 15 @classmethod 16 def retrun_None(self): 17 return None 18 19 # 單元測試類 20 class MyTest(unittest.TestCase): 21 22 # assertEqual()方法實例 23 def test_assertEqual(self): 24 # 斷言兩數之和的結果 25 try: 26 a, b = 1, 2 27 sum = 3 28 self.assertEqual(a + b, sum, '斷言失敗,%s + %s != %s' %(a, b, sum)) 29 except AssertionError, e: 30 print e 31 32 # assertNotEqual()方法實例 33 def test_assertNotEqual(self): 34 # 斷言兩數之差的結果 35 try: 36 a, b = 5, 2 37 res = 1 38 self.assertNotEqual(a - b, res, '斷言失敗,%s - %s != %s' %(a, b, res)) 39 except AssertionError, e: 40 print e 41 42 # assertTrue()方法實例 43 def test_assertTrue(self): 44 # 斷言表達式的為真 45 try: 46 self.assertTrue(1 == 1, "表達式為假") 47 except AssertionError, e: 48 print e 49 50 # assertFalse()方法實例 51 def test_assertFalse(self): 52 # 斷言表達式為假 53 try: 54 self.assertFalse(3 == 2, "表達式為真") 55 except AssertionError, e: 56 print e 57 58 # assertIs()方法實例 59 def test_assertIs(self): 60 # 斷言兩變量類型屬於同一對象 61 try: 62 a = 12 63 b = a 64 self.assertIs(a, b, "%s與%s不屬於同一對象" %(a, b)) 65 except AssertionError, e: 66 print e 67 68 # test_assertIsNot()方法實例 69 def test_assertIsNot(self): 70 # 斷言兩變量類型不屬於同一對象 71 try: 72 a = 12 73 b = "test" 74 self.assertIsNot(a, b, "%s與%s屬於同一對象" %(a, b)) 75 except AssertionError, e: 76 print e 77 78 # assertIsNone()方法實例 79 def test_assertIsNone(self): 80 # 斷言表達式結果為None 81 try: 82 result = MyClass.retrun_None() 83 self.assertIsNone(result, "not is None") 84 except AssertionError, e: 85 print e 86 87 # assertIsNotNone()方法實例 88 def test_assertIsNotNone(self): 89 # 斷言表達式結果不為None 90 try: 91 result = MyClass.sum(2, 5) 92 self.assertIsNotNone(result, "is None") 93 except AssertionError, e: 94 print e 95 96 # assertIn()方法實例 97 def test_assertIn(self): 98 # 斷言對象A是否包含在對象B中 99 try: 100 strA = "this is a test" 101 strB = "is" 102 self.assertIn(strB, strA, "%s不包含在%s中" %(strB, strA)) 103 except AssertionError, e: 104 print e 105 106 # assertNotIn()方法實例 107 def test_assertNotIn(self): 108 # 斷言對象A不包含在對象B中 109 try: 110 strA = "this is a test" 111 strB = "Selenium" 112 self.assertNotIn(strB, strA, "%s包含在%s中" %(strB, strA)) 113 except AssertionError, e: 114 print e 115 116 # assertIsInstance()方法實例 117 def test_assertIsInstance(self): 118 # 測試對象A的類型是否值指定的類型 119 try: 120 x = MyClass 121 y = object 122 self.assertIsInstance(x, y, "%s的類型不是%s".decode("utf-8") %(x, y)) 123 except AssertionError, e: 124 print e 125 126 # assertNotIsInstance()方法實例 127 def test_assertNotIsInstance(self): 128 # 測試對象A的類型不是指定的類型 129 try: 130 a = 123 131 b = str 132 self.assertNotIsInstance(a, b, "%s的類型是%s" %(a, b)) 133 except AssertionError, e: 134 print e 135 136 # assertRaises()方法實例 137 def test_assertRaises(self): 138 # 測試拋出的指定的異常類型 139 # assertRaises(exception) 140 with self.assertRaises(ValueError) as cm: 141 random.sample([1,2,3,4,5], "j") 142 # 打印詳細的異常信息 143 #print "===", cm.exception 144 145 # assertRaises(exception, callable, *args, **kwds) 146 try: 147 self.assertRaises(ZeroDivisionError, MyClass.div, 3, 0) 148 except ZeroDivisionError, e: 149 print e 150 151 # assertRaisesRegexp()方法實例 152 def test_assertRaisesRegexp(self): 153 # 測試拋出的指定異常類型,並用正則表達式具體驗證 154 # assertRaisesRegexp(exception, regexp) 155 with self.assertRaisesRegexp(ValueError, 'literal') as ar: 156 int("xyz") 157 # 打印詳細的異常信息 158 #print ar.exception 159 # 打印正則表達式 160 #print "re:",ar.expected_regexp 161 162 # assertRaisesRegexp(exception, regexp, callable, *args, **kwds) 163 try: 164 self.assertRaisesRegexp(ValueError, "invalid literal for.*XYZ'$", int, 'XYZ') 165 except AssertionError, e: 166 print e 167 168 169 if __name__ == '__main__': 170 # 執行單元測試 171 unittest.main()
例子7 生成報告
把HTMLTestRunner.py和執行程序文件放置同一目錄
1 # coding=utf-8 2 import unittest 3 import HTMLTestRunner 4 import math 5 6 class Calc(object): 7 8 def add(self, x, y, *d): 9 # 加法計算 10 result = x + y 11 for i in d: 12 result += i 13 return result 14 15 def sub(self, x, y, *d): 16 # 減法計算 17 result = x - y 18 for i in d: 19 result -= i 20 return result 21 22 class SuiteTestCalc(unittest.TestCase): 23 def setUp(self): 24 self.c = Calc() 25 26 @unittest.skip("skipping") 27 def test_Sub(self): 28 print "sub" 29 self.assertEqual(self.c.sub(100, 34, 6), 61, u'求差結果錯誤!') 30 31 def testAdd(self): 32 print "add" 33 self.assertEqual(self.c.add(1, 32, 56), 89, u'求和結果錯誤!') 34 35 36 class SuiteTestPow(unittest.TestCase): 37 def setUp(self): 38 self.seq = range(10) 39 40 # @unittest.skipIf() 41 def test_Pow(self): 42 print "Pow" 43 self.assertEqual(pow(6, 3), 2161, u'求冪結果錯誤!') 44 45 def test_hasattr(self): 46 print "hasattr" 47 # 檢測math模塊是否存在pow屬性 48 self.assertTrue(hasattr(math, 'pow1'), u"檢測的屬性不存在!") 49 50 if __name__ == "__main__": 51 suite1 = unittest.TestLoader().loadTestsFromTestCase(SuiteTestCalc) 52 suite2 = unittest.TestLoader().loadTestsFromTestCase(SuiteTestPow) 53 suite = unittest.TestSuite([suite1, suite2]) 54 #unittest.TextTestRunner(verbosity=2).run(suite) 55 filename = "test.html" # 定義個報告存放路徑,支持相對路徑。 56 # 以二進制方式打開文件,准備寫 57 fp = file(filename, 'wb') 58 # 使用HTMLTestRunner配置參數,輸出報告路徑、報告標題、描述,均可以配 59 runner = HTMLTestRunner.HTMLTestRunner(stream = fp, 60 title = 'Report_title', description = 'Report_description') 61 # 運行測試集合 62 runner.run(suite)
例子8 批量執行多個模塊生成報告
1程序文件模式
即將測試發現代碼編寫在測試腳本中,然后直接執行腳本文件即可,具體由
TestLoader().discover(dir)實現,dir為被測試模塊目錄
模塊名需test開頭,測試方法也要test開頭,類名不需要test開頭
1 #coding=utf-8 2 import unittest 3 import HTMLTestRunner 4 5 if __name__ == '__main__' : 6 # 加載當前目錄下所有有效的測試模塊(以test開頭的文件),“.”表示當前目錄 7 testSuite = unittest.TestLoader().discover('.') 8 filename = "test.html" # 定義個報告存放路徑,支持相對路徑。 9 # 以二進制方式打開文件,准備寫 10 fp = file(filename, 'wb') 11 # 使用HTMLTestRunner配置參數,輸出報告路徑、報告標題、描述,均可以配 12 runner = HTMLTestRunner.HTMLTestRunner(stream = fp, 13 title = 'Report_title', description = 'Report_description') 14 # 運行測試集合 15 runner.run(testSuite)
2命令行模式
unittest支持簡單的test discovery. 命令行傳入discovery后,框架會自動在當前目錄搜索要測試的案例並執行.搜索目錄必須是包或者模塊.基本使用如下:
python -m unittest discover
子選項如下:
-v, –verbose
python -m unittest discover -v
輸出信息的詳細級別
-s, –start-directory directory
python -m unittest discover -s other_dir
開始搜索目錄 (默認為當前目錄)
-p, –pattern pattern
python -m unittest discover -p ‘YW*.py’
匹配的文件名 (默認為test*.py)
-t, –top-level-directory directory
python -m unittest discover -t g:\
搜索的頂層目錄 (默認為start directory)
例子9 webdriver和unittest結合
1 #encoding=utf-8 2 import unittest 3 from selenium import webdriver 4 import time 5 6 class WebTest(unittest.TestCase): 7 def setUp(self): 8 # 啟動Firefox瀏覽器 9 self.driver=webdriver.Firefox(executable_path = "d:\\driver\geckodriver") 10 11 def testSoGou(self): 12 # 訪問搜狗首頁 13 self.driver.get("http://sogou.com") 14 # 清空搜索輸入框默認內容 15 self.driver.find_element_by_id("query").clear() 16 # 在搜索輸入框中輸入“自動化測試” 17 self.driver.find_element_by_id("query").send_keys(u"自動化測試") 18 # 單擊“搜索”按鈕 19 self.driver.find_element_by_id("stb").click() 20 # 等待3秒 21 time.sleep(3) 22 assert u"web自動化" in self.driver.page_source, u"頁面中不存在要尋找的關鍵字!".encode("gbk") 23 24 25 def testBing(self): 26 # 訪問搜狗首頁 27 self.driver.get("http://cn.bing.com") 28 # 清空搜索輸入框默認內容 29 self.driver.find_element_by_id("sb_form_q").clear() 30 # 在搜索輸入框中輸入“自動化測試” 31 self.driver.find_element_by_id("sb_form_q").send_keys(u"自動化測試") 32 # 單擊“搜索”按鈕 33 self.driver.find_element_by_id("sb_form_go").click() 34 # 等待3秒 35 time.sleep(3) 36 assert u"web自動化" in self.driver.page_source, u"頁面中不存在要尋找的關鍵字!".encode("gbk") 37 38 39 def tearDown(self): 40 # 退出瀏覽器 41 self.driver.quit() 42 43 if __name__ == '__main__': 44 unittest.main()
例子10 補充:命令行模式執行測試用例
1. 運行整個測試模塊
python -m unittest test_module1 test_module2……
2. 執行測試模塊中某個測試類
python -m unittest test_module.TestClass
3. 執行測試模塊中某個測試類下的測試方法
python -m unittest test_module.TestClass.test_method
練習
寫一個讀文件的類,里面有個方法可以讀取文件的全部內容。
寫個單元測試,斷言文件中包含關鍵字"well done!"
1 #被測試模塊:readcont.py 2 3 #coding=utf-8 4 import unittest 5 import os.path 6 7 class TestReadContent(): 8 @staticmethod 9 def read_content(filepath): 10 if os.path.exists(filepath): 11 with open(filepath) as fp: 12 return fp.read() 13 else: 14 return '' 15 if __name__=='__main__': 16 fcon = TestReadContent.read_content('g:/a.txt') 17 print fcon 18 19 #單元測試模塊: 20 21 #coding= utf-8 22 import unittest 23 from readcont import TestReadContent 24 25 class TestRead(unittest.TestCase): 26 def test_run(self): 27 content = TestReadContent.read_content('g:/a.txt') 28 self.assertTrue('well done!' in content ) 29 30 if __name__=='__main__': 31 unittest.main()