Unittest單元測試框架
unittest是什麼
unittest是python內置的單元測試框架,具備編寫用例、組織用例、執行用例、輸出報告等自動化框架的條件。
單元測試框架的優點
一般來說不用單元測試框架也能編寫單元測試,因爲單元測試本身就是通過一段代碼去驗證另一段代碼,所以不用單元測試框架也能編寫單元測試。只是使用框架時會有更多的優點
1、提供用例組織與執行:
當測試用例達到成百上千條時,就產生了擴展性與維護性等問題,此時就需要考慮用例的規範與組織問題了。單元測試框架便能很好的解決這個問題
2、提供豐富的比較方法:
不論是功能測試還是單元測試,在用例完成之後都需要將實際結果與預期結果進行比較(斷言),從而斷定用例是否執行通過。單元測試框架一般會提供豐富的斷言方法。例如:相等\不相等,包含\不包含,True\False的斷言方法等
3、提供豐富的日誌:
當測試用例執行失敗時能拋出清晰的失敗原因,當所有用例執行完成之後能提供豐富的執行結果。例如,總執行時間、失敗用例數、成功用例數等
認識單元測試
例1:下面爲一段需要測試的代碼
class Count():
def __init__(self,x,y):
self.x = int(x)
self.y = int(y)
def Add(self,c):
sum = self.x + self.y + c
return sum
def Subtract(self):
subtract = self.x - self.y
return subtract
def Product(self):
product = self.x * self.y
return product
def Division(self):
division = self.x / self.y
return division
if __name__ == "__main__":
count = Count(6,3)
print(count.Add(2))
print(count.Subtract())
print(count.Product())
print(count.Division())
"""
11
3
18
2.0
"""
例1_1:不使用框架編寫的單元測試
#使用非Unittest框架進行單元測試
#from Module.Unittest_Module.Add_count import Count
#調用其他模塊中的函數:from 模塊路徑.模塊名.類名 import 函數名
from Module.Unittest_Module import Add_count
#調用其他模塊中的函數:from 模塊路徑.模塊名 import 類名
class Test_Add_Count():
def test_add(self):
try:
count = Add_count.Count(2, 3)
#count = Count(2, 3)
Add = count.Add(2)#不管怎麼調用,調用方法時都是實例名.方法名()
#assert (Count.Add == 7), "Integer addition result error"
#assert (Add_count.Count.Add == 7), "Integer addition result error"
assert (Add == 7),"Integer addition result error"
except AssertionError as Error_msg:
print(Error_msg)
else:
print("Test pass")
test_case = Test_Add_Count()
test_case.test_add()
"""
#assert比較相等的結果時
Test pass
#assert比較不相等的結果時
7
Integer addition result error
"""
備註:
1、上面代碼的執行過程爲:調用Module.Unittest_Module下面的Add_count模塊下的Count類,然後在test_add方法中調用Count類的Add()方法並傳入對應的參數進行對應的加法運算,並通過assert()方法判斷Add()的返回值是否是7,如果不相等則拋出自定的異常,相等則打印"Test pass"
2、從上面的代碼可以看出,測試程序的寫法沒有一定的規範,不同的人會有不同的寫法,不統一的代碼維護起來十分麻煩。因此爲了讓單元測試代碼更容易維護和編寫,最好的方式就是遵循一定的規範來寫。這就是單元測試框架誕生的初衷。
3、從上面的代碼註釋等可以看出,在調用其他模塊中的類時,有不同的調用方法,總結出來就是一句話:最後要確定函數的具體位置(模塊路徑.模塊名.類名.函數名)。不同的調用方法在實例化類時有點不一致,但最終調用類中的方法都是一樣的(實例名.方法名( ))
例1_2:
#使用單元測試框架(Unittest)來寫
from Module.Unittest_Module import Add_count
import unittest
class TestAddCount(unittest.TestCase):
def setUp(self):
print("測試開始")
def test_add_case(self):
count = Add_count.Count(6, 3)
self.assertEqual(count.Add(2),11) #測試函數的返回值
def test_subtract_case(self):
count = Add_count.Count(6, 3)
self.assertEqual(count.Subtract(),3)
def test_product_case(self):
count = Add_count.Count(6, 3)
self.assertEqual(count.Product(),18)
def test_division_case(self):
count = Add_count.Count(6, 3)
self.assertEqual(count.Division(),2)
def tearDown(self):
print("測試結束")
if __name__=="__main__":
unittest.main()
"""
#測試通過時
測試開始
測試結束
OK
#測試未通過時
測試開始
測試結束
3 != 2.0
Expected :2.0
Actual :3
"""
備註:分析上面的代碼
1、首先引入unittest模塊,創建TestAddCount測試類並繼承unittest的TestCase類,我們可以把TestCase類看成是對特定類進行測試的集合
2、setUp()方法用於測試用例執行前的初始化工作,這裏只是簡單地打印"測試開始"。tearDown()方法與setUP()方法相呼應,用於測試用例執行之後的善後工作,這裏打印"測試結束"信息
3、在test_add_case()等方法中,首先是調用Count類並傳入要計算的參數(實例化類),然後在調用類中對應的方法(Add()等)得到對應的返回值。這裏就不在使用繁瑣的異常處理了,而是直接調用unittest框架所提供的assertEqual()方法對Add()的返回值進行斷言,判斷兩者是否相等。assertEqual()方法由Testcase類集成而來
4、unittest提供了全局main()方法,使用它可以方便地將一個單元測試模塊變成可以直接運行的測試腳本。main()方法使用TestLoader類來搜索所有包含在該模塊中以"test"命名開頭的測試方法,並自動執行它們(因此:所有的測試函數以test開頭)
5、在unittest.main()中加 verbosity 參數可以控制輸出的錯誤報告的詳細程度,默認是 1,如果設爲 0,則不輸出每一用例的執行結果,即沒有上面的結果中的第1行;如果設爲 2,則輸出詳細的執行結果
單元測試重要的概念
使用unittest前需要了解該框架的五個概念,即test case,test suite,testLoader,test runner,test fixture
1、Test Case:
一個TestCase的實例就是一個測試用例。什麼是測試用例呢?就是一個完整的測試流程。包括測試前準備環境的搭建(SetUP)、實現測試過程的代碼(run),以及測試後環境的還原(tearDown)。單元測試(Unittest)的本質也就在這裏,一個測試用例就是一個完整的測試單元,通過運行這個測試單元,可以對某一個功能進行驗證
2、Test Suite:
一個功能的驗證往往需要多個測試用例,可以把多個測試用例集合在一起執行,這就產生了測試套件TestSuite的概念。TestSuite用來組裝單個測試用例。可以通過addTest加載TestCase到TestSuite中,從而返回一個TestSuite實例。而且TestSuite也可以嵌套TestSuite
3、Test Runner:
測試的執行也是單元測試中非常重要的一個概念,一般單元測試框架中都會提供豐富的執行策略和執行結果。在Unittest單元測試框架中,通過TextTestRunner類提供的run()方法來執行test suite/test case。test runner可以使用圖形界面、文本界面,或返回一個特殊的值等方式來表示測試執行的結果
4、Test Fixture:
對一個測試用例環境的搭建和銷燬,就是一個Fixture,通過覆蓋TestCase的setUP()和tearDown()方法來實現。這有什麼用呢?比如說在這個測試用例中需要訪問數據庫,那麼可以在setUP()中建立數據庫連接來進行初始化,在tearDown()中清除數據庫產生的數據,然後關閉連接
注:tearDown()方法的過程很重要,要爲下一個TestCase留下一個乾淨的環境
例2:
from Module.Unittest_Module import Add_count
import unittest
class TestAddCount(unittest.TestCase):
def setUp(self):
print("測試開始")
def test_add_case(self):
count = Add_count.Count(6, 3)
print("測試加法")
self.assertEqual(count.Add(2),11)
def test_subtract_case(self):
count = Add_count.Count(6, 3)
print("測試減法")
self.assertEqual(count.Subtract(),3)
def test_product_case(self):
count = Add_count.Count(6, 3)
print("測試乘法")
self.assertEqual(count.Product(),18)
def test_division_case(self):
count = Add_count.Count(6, 3)
print("測試除法")
self.assertEqual(count.Division(),2)
def tearDown(self):
print("測試結束")
if __name__=="__main__":
#構造測試集
suite = unittest.TestSuite()
suite.addTest(TestAddCount("test_division_case"))
#執行測試
runner = unittest.TextTestRunner()
runner.run(suite)
"""
測試開始
測試加法
測試結束
測試開始
測試除法
測試結束
測試開始
測試乘法
測試結束
測試開始
測試減法
測試結束
Ran 4 tests in 0.008s
OK
"""
注:
1、這樣寫出來後若是直接執行的話,還是會執行全部的用例;因此還需要專門設置下才能實現執行測試集(TestSuite)中的用例,設置步驟如下圖
2、從上面的例子中我們去掉了main()方法,採用構造測試集的方法來加載與運行測試用例,實現了有選擇的執行測試用例。當然,也可以通過註釋的方法來註釋掉其餘不需要執行的用例,但是這種做法並不優雅
3、上面代碼的執行過程:首先調用unittest框架的TestSuite()類來創建測試套件(實例化),通過它提供的addTest()方法來添加測試用例(test_division_case)。接着調用unittest框架的TextTestRunner()類(實例化),通過它下面的run()方法來運行suite所組裝的測試用例
unitest的工作原理
通過unittest類調用分析,可將框架的工作流程概況如下:
編寫TestCase,由TestLoader加載TestCase到TestSuite,然後由TextTestRunner來運行TestSuite,最後將運行的結果保存在TextTestResult中
備註:
執行測試用例後,並把測試結果寫入到TXT中
例3:
from Module.Unittest_Module import Add_count
import unittest
class TestAddCount(unittest.TestCase):
def setUp(self):
print("測試開始")
def test_add_case(self):
count = Add_count.Count(6, 3)
print("測試加法")
self.assertEqual(count.Add(2),11)
def test_subtract_case(self):
count = Add_count.Count(6, 3)
print("測試減法")
self.assertEqual(count.Subtract(),3)
def test_product_case(self):
count = Add_count.Count(6, 3)
print("測試乘法")
self.assertEqual(count.Product(),18)
def test_division_case(self):
count = Add_count.Count(6, 3)
print("測試除法")
self.assertEqual(count.Division(),2)
def tearDown(self):
print("測試結束")
if __name__=="__main__":
suite = unittest.TestSuite()#實例化一個測試集
suite.addTest(TestAddCount("test_division_case"))
with open('F:\Pycharm_project\\UnittestTextReport.txt',"w") as UnittestTextReport:
runner = unittest.TextTestRunner(stream=UnittestTextReport, verbosity=2)
runner.run(suite)
"""
測試開始
測試除法
測試結束
"""
拓展:
下面說的可能不對,這只是我的理解,感謝各位大佬指出錯誤
同一個類中引用其他方法中的變量
例4:
class Count():
def __init__(self,x,y):
self.x = int(x)
self.y = int(y)
def Add(self,c):
self.c = c
sum = self.x + self.y + self.c
return sum
def Subtract(self):
subtract = self.x - self.y - self.c
return subtract
count = Count(2,3)
print(count.Add(2))
print(count.Subtract())
"""
7
-3
"""
1、在Count()類中的Add()方法中有一個自己的變量C,且將其賦值給了實例變量,且在其他方法中調用了該變量(Subtract()方法中),這種寫法時,給我的感覺就是:此時變量C就相當於是個全局變量,在這個類中所有的方法都可以調用,但是就必須先執行,定義該變量的方法(先調用Add()方法),如果不先調用Add方法的話,就會報錯,所以這樣寫沒有實際意思,還不如不將其賦值給變量(就直接c)。所以方法中有自己的變量的話,還是隻給自己用就好了,不要想着給別的方法中,即使在同一個類中
2、__init__方法中的變量跟上面說的變量c不是一個東西,__init__中的變量,用於初始化(相當於一個全局變量),在這個類中的任何方法都可以調用
3、unittese框架的中setUp()方法給我的感覺就跟__init__方法一樣,都是用於初始化,且裏面的變量整個類都可以調用(具體例子看下一篇)
例4_1:
class Count():
def __init__(self,x,y):
self.x = int(x)
self.y = int(y)
def Add(self,c):
self.c = c
sum = self.x + self.y + self.c
return sum
def Subtract(self):
subtract = self.x - self.y - self.c
return subtract
count = Count(2,3)
#print(count.Add(2))
print(count.Subtract())
"""
AttributeError: 'Count' object has no attribute 'c'
"""
例4_2:
class Count():
def __init__(self,x,y):
self.x = int(x)
self.y = int(y)
def Add(self,c):
#self.c = c
sum = self.x + self.y + c
return sum
def Subtract(self):
subtract = self.x - self.y - c
return subtract
count = Count(2,3)
print(count.Add(2))
print(count.Subtract())
"""
NameError: name 'c' is not defined
"""
例4_3:
正確的寫法:完全把變量c當做私有變量只能在定義的方法中調用,其他方法都不能調用
class Count():
def __init__(self,x,y):
self.x = int(x)
self.y = int(y)
def Add(self,c):
sum = self.x + self.y + c
return sum
def Subtract(self):
subtract = self.x - self.y
return subtract
count = Count(2,3)
print(count.Add(2))
print(count.Subtract())
"""
7
-1
"""