測試代碼 介紹如何使用Python模塊unittest 中的工具來測試代碼。 1. 測試函數 在Python中,測試函數是用於自動化測試,使用python模塊中的unittest中的工具來進行測試。 例如,創建一個函數max_function()接受兩個數字,求其最大值,再創建一個函數number_ ...
測試代碼
介紹如何使用Python模塊unittest 中的工具來測試代碼。
1. 測試函數
在Python中,測試函數是用於自動化測試,使用python模塊中的unittest中的工具來進行測試。
例如,創建一個函數max_function()接受兩個數字,求其最大值,再創建一個函數number_function()提示用戶輸入兩個數
代碼1:
1 def get_max_number(x,y): 2 """求兩個數中的最大值""" 3 if x > y : 4 max_number = x 5 else: 6 max_number = y 7 return max_number
代碼2:
1 from max_function import get_max_number 2 3 print("Enter 'q' at any time to quit.") 4 while True: 5 x = input("Please enter x:") 6 if x == 'q': 7 break 8 y = input("Please enter y:") 9 if y == 'q': 10 break 11 max_number = get_max_number(x,y) 12 print("The max is :",max_number,".")
運行結果:
1 Enter 'q' at any time to quit. 2 Please enter x:12 3 Please enter y:23 4 The max is : 23 . 5 Please enter x:66 6 Please enter y:99 7 The max is : 99 . 8 Please enter x:q
1.1 單元測試和測試用例
Python標準庫中的模塊 unittest 提供了代碼測試工具。單元測試用於核實函數的某個方面沒有問題;測試用例是一組單元測試,這些單元測試一起核實函數在各種情形下的行為都符合要求。良好的測試用例考慮到了函數可能收到的各種輸入,包含針對所有這些情形的測試。全覆蓋式測試用例包含一整套單元測試,涵蓋了各種可能的函數使用方式。對於大型項目,要實現全覆蓋可能很難。通常,最初只要針對代碼的重要行為編寫測試即可,等項目被廣泛使用時再考慮全覆蓋。
1.2 可通過的測試
創建測試用例的語法需要一段時間才能習慣,但測試用例創建後,再添加針對函數的單元測試就很簡單了。要為函數編寫測試用例,可先導入模塊 unittest 以及要測試的函數,再創建一個繼承 unittest.TestCase 的類,並編寫一系列方法對函數行為的不同方面進行測試。
例如,創建一個包含一個方法的測試用例,它檢查函數get_max_number()在給定x、y值時,是否能正確求出最大值。
代碼:
1 import unittest 2 3 from max_function import get_max_number 4 5 class TestMaxCase(unittest.TestCase): 6 """測試max_function.py""" 7 8 def test_x_y(self): 9 """能夠處理x,y這樣的數字""" 10 max_number = get_max_number(20,18) 11 self.assertEqual(max_number,20) 12 13 unittest.main
說明:
第1行,導入一個模塊unittest。
第2行,從模塊max_function中導入函數get_max_number()。
第5行,創建了一個名為TestMaxCase的類,用於包含一系列針對 get_max_number() 的單元測試。
第8行,定義一個方法test_x_y()。
第10行,調用函數get_max_number()求出最大值,並賦值給變數max_number 。
第11行,調用 unittest 的方法 assertEqual() ,並向它傳遞 max_number 和 20。代碼行self.assertEqual(max_number,20)的意思是說:“將 max_number的值同字數字20進行比較,如果它們相等,就萬事大吉,如果它們不相等,跟我說一聲!”
第13行,調用unittest的main方法。
運行結果:
1 Ran 1 test in 0.001s 2 3 OK
從第1行可知,測試了一個函數,花費了0.001s;第3行,說明測試OK,通過測試。
1.3 不能通過的測試
例如,我們創建的一個求三個數的最大值的函數,但是只給其提供兩個值。
代碼1:
1 def get_max_number(x,y,z): 2 """求三個數中的最大值""" 3 if x > y and x > z: 4 max_number = x 5 elif y > z: 6 max_number = y 7 else: 8 max_number = z 9 10 return max_number
代碼2:
1 import unittest 2 3 from max_function1 import get_max_number 4 5 class TestMaxCase(unittest.TestCase): 6 """測試max_function.py""" 7 8 def test_x_y(self): 9 """能夠處理x,y這樣的數字""" 10 max_number = get_max_number(20,18) 11 self.assertEqual(max_number,20) 12 13 unittest.main
代碼2的運行結果如下:
1 Ran 1 test in 0.000s 2 3 FAILED (errors=1) 4 Launching unittests with arguments python -m unittest test_max_function1.TestMaxCase in F:\PyProject\s14\exercise\chapter_eleven 5 6 Error 7 Traceback (most recent call last): 8 File "D:\Python\Python36\lib\unittest\case.py", line 59, in testPartExecutor 9 yield 10 File "D:\Python\Python36\lib\unittest\case.py", line 601, in run 11 testMethod() 12 File "F:\PyProject\s14\exercise\chapter_eleven\test_max_function1.py", line 12, in test_x_y 13 max_number = get_max_number(20,18) 14 TypeError: get_max_number() missing 1 required positional argument: 'z'
從以上運行結果可知,由於取最大值的函數需要三個位置實參,但是只給其傳了兩個,少了一個,由於報錯。
1.4 測試未通過時怎麼辦
當測試不通過時,我們不要測試區修改用於測試的函數,而應該去修改被測試的函數,使其通過測試。
例如,對1.3中測試未通過的函數進行完善。
代碼1:
1 def get_max_number(x=0,y=0,z=0): 2 """求三個數中的最大值""" 3 if x > y and x > z: 4 max_number = x 5 elif y > z: 6 max_number = y 7 else: 8 max_number = z 9 10 return max_number
說明:
第1行,為了在用戶不提供任何參數而調用求最大值的函數時不報錯,我們給每個形參都賦予一個預設值0。
代碼2:
1 import unittest 2 3 from max_function2 import get_max_number 4 5 class TestMaxCase(unittest.TestCase): 6 """測試max_function.py""" 7 8 def test_x_y(self): 9 """能夠處理x,y這樣的數字""" 10 max_number = get_max_number(20,18) 11 self.assertEqual(max_number,20) 12 13 unittest.main
運行結果:
1 Ran 1 test in 0.000s 2 3 OK
從以上運行結果可知,測試已通過。
1.5 添加新測試
例如,我們在以上求最大的模塊中,增加一個求最小值的函數。
代碼1:
1 def get_max_number(x=0,y=0,z=0): 2 """求三個數中的最大值""" 3 if x > y and x > z: 4 max_number = x 5 elif y > z: 6 max_number = y 7 else: 8 max_number = z 9 10 return max_number 11 12 def get_min_number(x=0,y=0,z=0): 13 """求三個數中的最小值""" 14 if x < y and x < z : 15 min_number = x 16 elif y < z : 17 min_number = y 18 else: 19 min_number = z 20 21 return min_number
代碼2:
1 import unittest 2 3 from max_min_function import get_max_number 4 from max_min_function import get_min_number 5 6 class TestMaxCase(unittest.TestCase): 7 """測試max_function.py""" 8 9 def test_max_number(self): 10 """能夠處理x,y這樣的數字""" 11 max_number = get_max_number(20,18) 12 self.assertEqual(max_number,20) 13 14 def test_min_number(self): 15 """能否處理x、y、z這樣的數字""" 16 min_number = get_min_number(1,13,18) 17 self.assertEqual(min_number,1) 18 19 20 unittest.main
運行結果:
1 Ran 2 test in 0.001s 2 3 OK
從以上結果可知,兩測試都已通過,耗時0.001s。
2. 測試類
測試類就是針對類來編寫測試,驗證類是否能想我沒期待的那樣。如果針對類的測試通過了,我沒就能確信對類所做的改進沒有意外地破壞其原有的行為。
2.1 各種斷言方法
Python在 unittest.TestCase 類中提供了很多斷言方法。斷言方法檢查我沒認為應該滿足的條件是否確實滿足。如果該條件確實滿足,我們對程式行為的假設就得到了確認,我們就可以確信其中沒有錯誤。如果我們認為應該滿足的條件實際上並不滿足,Python將引發異常。
常用的斷言方法有:
(1)assertEqual(a, b),核實 a == b。
(2)assertNotEqual(a, b),核實 a != b。
(3)assertTrue(x),核實 x 為 True。
(4)assertFalse(x),核實 x 為 False。
(5)assertIn( item , list ),核實 item 在 list 中。
(6)assertNotIn( item , list ),核實 item 不在 list 中。
2.2 測試類
類的測試與函數的測試相似,只不過類測試所做的大部分工作都是測試類中方法的行為,但存在一些不同之處。
例如,我們創建一個管理匿名調查問卷的類AnonymousSurvey,並將其存於模塊survey中,然後測試它。
(1)創建類AnonymousSurvey
代碼1:
1 class AnonymousSurvey(): 2 """收集匿名調查問卷的答案""" 3 4 def __init__(self,question): 5 """存儲一個問題,併為存儲答案做準備""" 6 self.question = question 7 self.responses = [] 8 9 def show_question(self): 10 """顯示調查問卷""" 11 print(self.question) 12 13 def store_response(self,new_response): 14 """存儲單份調查問卷""" 15 self.responses.append(new_response) 16 17 def show_results(self): 18 """顯示收集到的所有答案""" 19 print("Survey results:") 20 for reponse in self.responses: 21 print(('-' + reponse))
說明:
第4~7行,存儲了一個指定的調查問題,並創建了一個空列表response,用於存儲答案。
第9~11行,創建一個函數show_question(),用於顯示調查問卷。
第13~15行,創建一個函數store_response(),用於存儲單份調查問卷,即使用append()方法往答案列表中添加新答案。
第17~21行,創建一個函數show_response(),用於顯示收集到的所有答案,其中使用for迴圈遍歷答案列表response。
代碼2:
1 from survey import AnonymousSurvey 2 3 # 定義一個問題,並創建一個表示調查的AnonymousSurvey對象 4 question = "What language did you first learn to speak?" 5 my_survey = AnonymousSurvey(question) 6 7 # 顯示問題並存儲答案 8 my_survey.show_question() 9 print("Enter 'q' at time to quit.\n") 10 while True: 11 response = input("language:") 12 if response == 'q': 13 break 14 my_survey.store_response(response) 15 16 # 顯示調查結果 17 print("\nThank you to everyone who participated in the survey!") 18 my_survey.show_results()
說明:
第1行,從模塊survey中導入一個類AnonymousSurvey。
第4~5行,定義一個問題,並應用該問題創建一個表示調查的AnonymousSurvey對象。
第8~14行,調用 show_question() 來顯示問題,並提示用戶輸入答案。收到每個答案的同時將其存儲起來。用戶輸入所有答案(輸入 q 要求退出)
後。
第17~18行,顯示調查結果,即調用 show_results() 來列印調查結果。
運行結果:
1 What language did you first learn to speak? 2 Enter 'q' at time to quit. 3 4 language:English 5 language:Spanish 6 language:English 7 language:Chinese 8 language:Mandarin 9 language:q 10 11 Thank you to everyone who participated in the survey! 12 Survey results: 13 -English 14 -Spanish 15 -English 16 -Chinese 17 -Mandarin
(2)測試 AnonymousSurvey 類
- 測試單個答案
代碼1:
1 import unittest 2 from survey import AnonymousSurvey 3 4 class TestAnonmyousSurvey(unittest.TestCase): 5 """針對AnonymousSurvey類的測試""" 6 7 def test_store_single_response(self): 8 """測試單個答案會被妥善地存儲""" 9 question = "What language did you first learn to speak?" 10 my_survey = AnonymousSurvey(question) 11 my_survey.store_response('English') 12 self.assertIn('English', my_survey.responses) 13 14 unittest.main
說明:
第1行,導入測試模塊unittest。
第2行,從模塊survey中導入類AnonymousSurvey。
第4行,繼承模塊unittest中的類TestCase創建一個測試類TestAnonmyousSurvey。
第7~12行,創建一個方法test_store_single_response來測試單個答案會被妥善地存儲。
第14行,調用模塊unittest中的main方法來執行測試。
運行結果:
1 Ran 1 test in 0.000s 2 3 OK
- 測試多個答案
代碼2:
1 import unittest 2 from survey import AnonymousSurvey 3 4 class TestAnonmyousSurvey(unittest.TestCase): 5 """針對AnonymousSurvey類的測試""" 6 7 def test_store_single_response(self): 8 """測試單個答案會被妥善地存儲""" 9 question = "What language did you first learn to speak?" 10 my_survey = AnonymousSurvey(question) 11 my_survey.store_response('English') 12 self.assertIn('English', my_survey.responses) 13 14 def test_store_three_responses(self): 15 """測試三個答案會被妥善地存儲""" 16 question = "What language did you first learn to speak?" 17 my_survey = AnonymousSurvey(question) 18 responses = ['English', 'Spanish', 'Mandarin'] 19 for response in responses: 20 my_survey.store_response(response) 21 22 for response in responses: 23 self.assertIn(response, my_survey.responses) 24 25 unittest.main
運行結果:
1 Ran 2 tests in 0.000s2 3 OK
2.3 方法 setUp()的使用
在前面的test_survey.py中,我們在每個測試方法中都創建了一個 AnonymousSurvey 實例,併在每個方法中都創建了答案。 unittest.TestCase 類包含方法 setUp() ,讓我們只需創建這些對象一次,併在每個測試方法中使用它們。如果你在 TestCase 類中包含了方法 setUp() ,Python將先運行它,再運行各個以test_打頭的方法。這樣,在我們編寫的每個測試方法中都可使用在方法 setUp()中創建的對象了。
例如,使用 setUp() 來創建一個調查對象和一組答案,供方法 test_store_single_response() 和test_store_three_responses() 使用。
代碼:
1 import unittest 2 from survey import AnonymousSurvey 3 4 class TestAnonymousSurvey(unittest.TestCase): 5 """針對AnonymousSurvey類的測試""" 6 def setUp(self): 7 """ 8 創建一個調查對象和一組答案,供使用的測試方法使用 9 """ 10 question = "What language did you first learn to speak?" 11 self.my_survey = AnonymousSurvey(question) 12 self.responses = ['English', 'Spanish', 'Mandarin'] 13 14 def test_store_single_response(self): 15 """測試單個答案會被妥善地存儲""" 16 self.my_survey.store_response(self.responses[0]) 17 self.assertIn(self.responses[0], self.my_survey.responses) 18 19 def test_store_three_responses(self): 20 """測試三個答案會被妥善地存儲""" 21 for response in self.responses: 22 self.my_survey.store_response(response) 23 for response in self.responses: 24 self.assertIn(response, self.my_survey.responses) 25 26 unittest.main
說明:
第11行,創建一個調查對象。
第12行,創建一個答案列表。
運行結果:
1 Ran 1 test in 0.000s 2 3 OK
測試自己編寫的類時,方法 setUp() 讓測試方法編寫起來更容易:可在 setUp() 方法中創建一系列實例並設置它們的屬性,再在測試方法中直接使用這些實例。相比於在每個測試方法中都創建實例並設置其屬性,這要容易得多。
運行測試用例時,每完成一個單元測試,Python都列印一個字元:測試通過時列印一個句點;測試引發錯誤時列印一個 E ;測試導致斷言失敗時列印一個 F 。這就是你運行測試用例時,在輸出的第一行中看到的句點和字元數量各不相同的原因。如果測試用例包含很多單元測試,需要運行很長時間,就可通過觀察這些結果來獲悉有多少個測試通過了。