讀書筆記「Python程式設計:從入門到實踐」_11.測試函式
11.1 測試函式
要學習測試,得有要測試的程式碼。下面是一個簡單的函式,它接受名和姓並返回整潔的姓名:
def get_formatted_name(first, last): """Generate a neatly formatted full name.""" full_name = first + ' ' + last return full_name.title()
為核實get_formatted_name() 像期望的那樣工作,我們來編寫一個使用這個函式的程式。
程式names.py讓使用者輸入名和姓,並顯示整潔的全名:
fromname_function import get_formatted_name print("Enter 'q' at any time to quit.") while True: first = input("\nPlease give me a first name: ") if first == 'q': break last = input("Please give me a last name: ") if last == 'q': break formatted_name = get_formatted_name(first, last)print("\tNeatly formatted name: " + formatted_name + '.')
我們可以在每次修改get_formatted_name() 後都進行測試:執行程式names.py,並輸入像Janis Joplin 這樣的姓名,但這太煩瑣
我們可以利用單元測試函式,每次修改完元source以後,直接執行單元測試函式來判斷程式是否正確
11.1.1 單元測試和測試用例
Python標準庫中的模組unittest 提供了程式碼測試工具。
單元測試 用於核實函式的某個方面沒有問題;
測試用例 是一組單元測試,這些單元測試一起核實函式在各種情形下的行為都符合要求。
良好的測試用例考慮到了函式可能收到的各種輸入,包含針對所有這些情形的測試。
全覆蓋式測試 用例包含一整套單元測試,涵蓋了各種可能的函式使用方式。
11.1.2 可通過的測試
test_name_function.py
import unittest from name_function import get_formatted_name #建立了一個名為NamesTestCase 的類,用於包含一系列針對get_formatted_name() 的單元測試。 #最好讓它看起來與要測試的函式相關,幷包含字樣Test #這個類必須繼承unittest.TestCase 類 class NamesTestCase(unittest.TestCase): """測試name_function.py""" #我們執行testname_function.py時,所有以test 打頭的方法都將自動執行 def test_first_last_name(self): """能夠正確地處理像Janis Joplin這樣的姓名嗎?""" formatted_name = get_formatted_name('janis', 'joplin') #使用了unittest 類最有用的功能之一:一個斷言 方法。斷言方法用來核實得到的結果是否與期望的結果一致 self.assertEqual(formatted_name, 'Janis Joplin') unittest.main()
. ---------------------------------------------------------------------- Ran 1 test in 0.002s OK
11.1.3 不能通過的測試
test_name_function.py
import unittest from name_function import get_formatted_name #建立了一個名為NamesTestCase 的類,用於包含一系列針對get_formatted_name() 的單元測試。 #最好讓它看起來與要測試的函式相關,幷包含字樣Test #這個類必須繼承unittest.TestCase 類 class NamesTestCase(unittest.TestCase): """測試name_function.py""" #我們執行testname_function.py時,所有以test 打頭的方法都將自動執行 def test_first_last_middle_name(self): """能夠正確地處理像Wolfgang Amadeus Mozart這樣的姓名嗎?""" formatted_name = get_formatted_name('wolfgang', 'mozart', 'amadeus') self.assertEqual(formatted_name, 'Wolfgang Amadeus Mozart') unittest.main()
E ====================================================================== ERROR: test_first_last_middle_name (__main__.NamesTestCase) 能夠正確地處理像Wolfgang Amadeus Mozart這樣的姓名嗎? ---------------------------------------------------------------------- Traceback (most recent call last): File "d:\40.勉強資料\python\test_name_function.py", line 11, in test_first_last_middle_name formatted_name = get_formatted_name('wolfgang', 'mozart', 'amadeus') TypeError: get_formatted_name() takes 2 positional arguments but 3 were given ---------------------------------------------------------------------- Ran 1 test in 0.003s FAILED (errors=1)
11.1.4 測試未通過時怎麼辦
測試未通過時怎麼辦呢?如果你檢查的條件沒錯,測試通過了意味著函式的行為是對的,
而測試未通過意味著你編寫的新程式碼有錯。因此,測試未通過時,不要修改測試,而應修復導致測試不能通過的程式碼:
檢查剛對函式所做的修改,找出導致函式行為不符合預期的修改
name_function.py
def get_formatted_name(first, last, middle=''): """生成整潔的姓名""" if middle: full_name = first + ' ' + middle + ' ' + last else: full_name = first + ' ' + last return full_name.title()
11.1.5 新增新測試
import unittest from name_function import get_formatted_name #建立了一個名為NamesTestCase 的類,用於包含一系列針對get_formatted_name() 的單元測試。 #最好讓它看起來與要測試的函式相關,幷包含字樣Test #這個類必須繼承unittest.TestCase 類 class NamesTestCase(unittest.TestCase): """測試name_function.py""" #我們執行testname_function.py時,所有以test 打頭的方法都將自動執行 def test_first_last_name(self): """能夠正確地處理像Janis Joplin這樣的姓名嗎?""" formatted_name = get_formatted_name('janis', 'joplin') #使用了unittest 類最有用的功能之一:一個斷言 方法。斷言方法用來核實得到的結果是否與期望的結果一致 self.assertEqual(formatted_name, 'Janis Joplin') def test_first_last_middle_name(self): """能夠正確地處理像Wolfgang Amadeus Mozart這樣的姓名嗎?""" formatted_name = get_formatted_name('wolfgang', 'mozart', 'amadeus') self.assertEqual(formatted_name, 'Wolfgang Amadeus Mozart') unittest.main()
11.2 測試類
11.2.1 各種斷言方法
方法用途
assertEqual(a, b) 核實a == b
assertNotEqual(a, b) 核實a != b
assertTrue(x) 核實x 為True
assertFalse(x) 核實x 為False
assertIn(item , list ) 核實 item 在 list 中
assertNotIn(item , list ) 核實 item 不在 list 中
11.2.2 一個要測試的類
class AnonymousSurvey(): """收集匿名調查問卷的答案""" def __init__(self, question): """儲存一個問題,併為儲存答案做準備""" self.question = question self.responses = [] def show_question(self): """顯示調查問卷""" print(self.question) def store_response(self, new_response): """儲存單份調查答卷""" self.responses.append(new_response) def show_results(self): """顯示收集到的所有答卷""" print("Survey results:") for response in self.responses: print('- ' + response)
11.2.3 測試Anonymous
import unittest from survey import AnonymousSurvey class TestAnonmyousSurvey(unittest.TestCase): """針對AnonymousSurvey類的測試""" def test_store_single_response(self): """測試單個答案會被妥善地儲存""" question = "What language did you first learn to speak?" my_survey = AnonymousSurvey(question) my_survey.store_response('English') self.assertIn('English', my_survey.responses) def test_store_three_responses(self): """測試三個答案會被妥善地儲存""" question = "What language did you first learn to speak?" my_survey = AnonymousSurvey(question) responses = ['English', 'Spanish', 'Mandarin'] for response in responses: my_survey.store_response(response) for response in responses: self.assertIn(response, my_survey.responses) unittest.main()
11.2.4 方法setUp()
在前面的test_survey.py中,我們在每個測試方法中都建立了一個AnonymousSurvey 例項,並在每個方法中都建立了答案。
unittest.TestCase 類包含方法setUp() ,讓我們只需建立這些物件一次,並在每個測試方法中使用它們。
如果你在TestCase 類中包含了方法setUp() ,Python將先執行它,再執行各個以test_打頭的方法。這樣,在你編寫的每個測試方法中都可使用在方法setUp() 中建立的物件了
import unittest from survey import AnonymousSurvey class TestAnonymousSurvey(unittest.TestCase): """針對AnonymousSurvey類的測試""" #可在setUp() 方法中建立一系列例項並設定它們的屬性,再在測試方法中直接使用這些例項。 #相比於在每個測試方法中都建立例項並設定其屬性,這要容易得多
#方法setUp() 做了兩件事情:建立一個調查物件;建立一個答案列表 def setUp(self): """ 建立一個調查物件和一組答案,供使用的測試方法使用 """ question = "What language did you first learn to speak?" self.my_survey = AnonymousSurvey(question) self.responses = ['English', 'Spanish', 'Mandarin'] def test_store_single_response(self): """測試單個答案會被妥善地儲存""" self.my_survey.store_response(self.responses[0]) self.assertIn(self.responses[0], self.my_survey.responses) def test_store_three_responses(self): """測試三個答案會被妥善地儲存""" for response in self.responses: self.my_survey.store_response(response) for response in self.responses: self.assertIn(response, self.my_survey.responses) unittest.main()