通過前面的7個章節,作者學習了python的各項基礎知識,也學習了python的編譯和執行。但在實際環境上,我們需要驗證我們的代碼功能符合我們的設計預期,所以需要結合python的單元測試類,編寫單元測試代碼。
Python有一個內置的unittest
模塊,我們可以使用它來進行單元測試。
基礎用法
基本流程:
- 新建類,繼承自unittest.TestCase
- 類的成員函數統一用test_開頭,否則會無法識別和執行
- 通過調用unittest.main()來執行測試用例
簡單的示例程序如下:
import unittest#新建類,繼承自unittest.TestCase
#類的成員函數統一用test_開頭,否則會無法識別和執行
#通過調用unittest.main()來執行測試用例class TestMath(unittest.TestCase):def test_add(self):self.assertEqual(1 + 1, 2)def test_subtract(self):self.assertEqual(3 - 2, 1)if __name__ == '__main__':unittest.main()
結果輸出如下:
..
----------------------------------------------------------------------
Ran 2 tests in 0.000sOK
測試函數和類方法
對函數的單元測試:
import unittest#定義測試用的函數
def add(v1,v2):return v1+v2def subtract(v1,v2):return v1-v2class TestMath(unittest.TestCase):def test_add(self):#測試用例實現對函數的調用self.assertEqual(add(1, 1), 2)def test_subtract(self):#測試用例實現對函數的調用self.assertEqual(subtract(2, 3), -1)if __name__ == '__main__':unittest.main()
結果輸出:
..
----------------------------------------------------------------------
Ran 2 tests in 0.000sOK
對類的單元測試:
在下面示例中,我們對函數和復用的Car類同時進行了單元測試。
import unittest#定義測試用的函數
def add(v1,v2):return v1+v2def subtract(v1,v2):return v1-v2#復用前期定義的Car類
class Car(object):__slots__ = ('_color', '_number')@propertydef color(self):return self._color@color.setterdef color(self,value):self._color = value@propertydef number(self):return self._number@number.setterdef number(self,value):self._number = value@number.deleterdef number(self):print('oops! number is deleted!')def func(self):print('car number: %d' % self._number)passclass TestAll(unittest.TestCase):def test_add(self):#測試用例實現對函數的調用self.assertEqual(add(1, 1), 2)def test_subtract(self):#測試用例實現對函數的調用self.assertEqual(subtract(2, 3), -1)def test_car_color(self):car = Car()car.color ="blue"self.assertEqual(car.color, "blue")#屬性測試def test_car_property(self):car = Car()with self.assertRaises(AttributeError):value = car.engineif __name__ == '__main__':unittest.main()
結果輸出:?
....
----------------------------------------------------------------------
Ran 4 tests in 0.000sOK
測試套件和測試運行器
繼承自unittest.TestCase的類,用test_開頭的成員函數,可以稱為一個測試用例。
使用測試套件的方法,可以自由的執行這些測試用例。如執行先后次序,部分執行等。
示例代碼如下:
import unittest#定義測試用的函數
def add(v1,v2):return v1+v2def subtract(v1,v2):return v1-v2#復用前期定義的Car類
class Car(object):__slots__ = ('_color', '_number')@propertydef color(self):return self._color@color.setterdef color(self,value):self._color = value@propertydef number(self):return self._number@number.setterdef number(self,value):self._number = value@number.deleterdef number(self):print('oops! number is deleted!')def func(self):print('car number: %d' % self._number)passclass TestAll(unittest.TestCase):def test_add(self):#測試用例實現對函數的調用self.assertEqual(add(1, 1), 2)def test_subtract(self):#測試用例實現對函數的調用self.assertEqual(subtract(2, 3), -1)def test_car_color(self):car = Car()car.color ="blue"self.assertEqual(car.color, "blue")#屬性測試def test_car_property(self):car = Car()with self.assertRaises(AttributeError):value = car.engineif __name__ == '__main__':# 創建測試套件suite = unittest.TestSuite()suite.addTest(TestAll('test_add'))#suite.addTest(TestAll('test_subtract'))suite.addTest(TestAll('test_car_property'))# 創建測試運行器runner = unittest.TextTestRunner()runner.run(suite) # 通過執行結果,我們可以看到僅執行了我們添加的兩個測試用例
測試準備和清理
很多時候,我們在測試用例執行之前,需要做一些準備操作,如執行數據庫相關測試用例之前,需要進行數據庫連接;之后需要斷開數據庫連接。這種場景下,我們需要使用python單元測試類默認的setUp和tearDown方法。修改上面的代碼,并測試。
import unittest#定義測試用的函數
def add(v1,v2):return v1+v2def subtract(v1,v2):return v1-v2#復用前期定義的Car類
class Car(object):__slots__ = ('_color', '_number')@propertydef color(self):return self._color@color.setterdef color(self,value):self._color = value@propertydef number(self):return self._number@number.setterdef number(self,value):self._number = value@number.deleterdef number(self):print('oops! number is deleted!')def func(self):print('car number: %d' % self._number)passclass TestAll(unittest.TestCase):def setUp(self):# 準備工作print("準備工作完成")def tearDown(self):# 結束后的工作print("結束清理工作完成")def test_add(self):#測試用例實現對函數的調用self.assertEqual(add(1, 1), 2)def test_subtract(self):#測試用例實現對函數的調用self.assertEqual(subtract(2, 3), -1)def test_car_color(self):car = Car()car.color ="blue"self.assertEqual(car.color, "blue")#屬性測試def test_car_property(self):car = Car()with self.assertRaises(AttributeError):value = car.engineif __name__ == '__main__':# 創建測試套件suite = unittest.TestSuite()suite.addTest(TestAll('test_add'))#suite.addTest(TestAll('test_subtract'))suite.addTest(TestAll('test_car_property'))# 創建測試運行器runner = unittest.TextTestRunner()runner.run(suite)
結果輸出:
準備工作完成
結束清理工作完成
.準備工作完成
結束清理工作完成
.
----------------------------------------------------------------------
Ran 2 tests in 0.001sOK
高級測試用法
條件測試
可以使用@unittest.skip關鍵字,來表示測試用例只有在滿足特定條件下才執行。下面的示例中,我們使用關鍵字實現了一直跳過和條件跳過的功能。
@unittest.skip(reason)?
Unconditionally skip the decorated test.?reason?should describe why the test is being skipped.
無條件跳過,需要輸入填過的原因。
@unittest.skip("Always skip!")
@unittest.skipIf(condition,?reason)
Skip the decorated test if?condition?is true.
條件滿足時跳過。
@unittest.skipIf(mylib.__version__ < (1, 3),"not supported in this library version")
@unittest.skipUnless(condition,?reason)
Skip the decorated test unless?condition?is true.
除此條件外,跳過。
@unittest.skipUnless(sys.platform.startswith("win"), "requires Windows")
@unittest.expectedFailure
Mark the test as an expected failure or error. If the test fails or errors in the test function itself (rather than in one of the?test fixture?methods) then it will be considered a success. If the test passes, it will be considered a failure.
失敗時,測試用例返回成功。
@unittest.expectedFailuredef test_fail(self):self.assertEqual(1, 0, "failed test")
exception?unittest.SkipTest(reason)
This exception is raised to skip a test.
import unittest
import sys#定義測試用的函數
def add(v1,v2):return v1+v2def subtract(v1,v2):return v1-v2#復用前期定義的Car類
class Car(object):__slots__ = ('_color', '_number')@propertydef color(self):return self._color@color.setterdef color(self,value):self._color = value@propertydef number(self):return self._number@number.setterdef number(self,value):self._number = value@number.deleterdef number(self):print('oops! number is deleted!')def func(self):print('car number: %d' % self._number)passclass TestAll(unittest.TestCase):def setUp(self):# 準備工作print("準備工作完成")def tearDown(self):# 結束后的工作print("結束清理工作完成")@unittest.skip("啊!我被永久的跳過了")def test_add(self):#測試用例實現對函數的調用self.assertEqual(add(1, 1), 2)# windows系統下,執行此測試用例。還可以使用版本號版本等等@unittest.skipUnless(sys.platform.startswith("win"), "requires Windows")def test_subtract(self):#測試用例實現對函數的調用self.assertEqual(subtract(2, 3), -1)def test_car_color(self):car = Car()car.color ="blue"self.assertEqual(car.color, "blue")#屬性測試def test_car_property(self):car = Car()with self.assertRaises(AttributeError):value = car.engineif __name__ == '__main__':# 創建測試套件suite = unittest.TestSuite()suite.addTest(TestAll('test_add'))suite.addTest(TestAll('test_subtract'))suite.addTest(TestAll('test_car_property'))# 創建測試運行器runner = unittest.TextTestRunner()runner.run(suite)
結果輸出:
準備工作完成
結束清理工作完成
.準備工作完成
結束清理工作完成
.
----------------------------------------------------------------------
Ran 3 tests in 0.001sOK (skipped=1)
模擬對象
Python的unittest.mock
模塊提供了一種創建模擬對象的方法,我們可以用它來模擬外部的、不可控的因素。
import unittest
import sys
import datetime
#使用mock需要執行此引用
from unittest.mock import patch#定義測試用的函數
def add(v1,v2):return v1+v2def subtract(v1,v2):return v1-v2#復用前期定義的Car類
class Car(object):__slots__ = ('_color', '_number')@propertydef color(self):return self._color@color.setterdef color(self,value):self._color = value@propertydef number(self):return self._number@number.setterdef number(self,value):self._number = value@number.deleterdef number(self):print('oops! number is deleted!')def func(self):print('car number: %d' % self._number)def say(self):current_hour = datetime.datetime.now().hourif current_hour < 12:return "Good morning!"elif current_hour < 18:return "Good afternoon!"else:return "Good evening!"passclass TestAll(unittest.TestCase):def setUp(self):# 準備工作print("準備工作完成")def tearDown(self):# 結束后的工作print("結束清理工作完成")@unittest.skip("啊!我被永久的跳過了")def test_add(self):#測試用例實現對函數的調用self.assertEqual(add(1, 1), 2)# windows系統下,執行此測試用例。還可以使用版本號版本等等@unittest.skipUnless(sys.platform.startswith("win"), "requires Windows")def test_subtract(self):#測試用例實現對函數的調用self.assertEqual(subtract(2, 3), -1)def test_car_color(self):car = Car()car.color ="blue"self.assertEqual(car.color, "blue")#屬性測試def test_car_property(self):car = Car()with self.assertRaises(AttributeError):value = car.engine@patch('datetime.datetime')# 注意此處多了一個mock_datetime參數def test_car_say(self, mock_datetime):car = Car()mock_datetime.now.return_value.hour = 9self.assertEqual(car.say(), "Good morning!")mock_datetime.now.return_value.hour = 15self.assertEqual(car.say(), "Good afternoon!")mock_datetime.now.return_value.hour = 20self.assertEqual(car.say(), "Good evening!")if __name__ == '__main__':# 創建測試套件suite = unittest.TestSuite()suite.addTest(TestAll('test_add'))suite.addTest(TestAll('test_subtract'))suite.addTest(TestAll('test_car_property'))suite.addTest(TestAll('test_car_say'))# 創建測試運行器runner = unittest.TextTestRunner()runner.run(suite)
結果輸出:
備工作完成
結束清理工作完成
.準備工作完成
結束清理工作完成
.準備工作完成
結束清理工作完成
.
----------------------------------------------------------------------
Ran 4 tests in 0.002sOK (skipped=1)