第十一章
編寫函數(shù)或類時,還可為其編寫測試昨凡。通過測試爽醋,可確定代碼面對各種輸入都能夠按要求的那樣工作。
1土匀、測試函數(shù)
下面是要用于測試的函數(shù)子房,它接受名和姓并返回整潔的姓名,存儲在文件name_function.py中:
def get_formatted_name(first, last):
full_name = first + ' ' + last
return full_name.title()
函數(shù)將名和姓合并成姓名就轧,在名和姓之間加上一個空格证杭,并將它們的首字母都大寫,再返回結果妒御。為核實函數(shù)是否像期望的那樣工作解愤,下面編寫一個使用這個函數(shù)的程序:
from name_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 + ".")
測試如下:
Enter 'q' at any time to quit.
Please give me a first name: janis
Please give me a last name: joplin
Neatly formatted name: Janis Joplin.
Please give me a first name: bob
Please give me a last name: dylan
Neatly formatted name: Bob Dylan.
Please give me a first name: q
從測試的結果可以知道,合并得到的姓名是正確的乎莉。如果現(xiàn)在需要添加處理中間名的功能送讲,就需要在保證不破壞原來功能的基礎上奸笤,添加新的功能,然后再進行測試哼鬓。這樣顯得就太繁瑣了监右,不過Python您提供了一種自動測試函數(shù)輸出的高效方式,可對相應的函數(shù)進行自動測試异希。
(1)單元測試和測試用例
Python標準庫中的模塊unittest提供了代碼測試工具健盒。單元測試用于核實函數(shù)的某個方面沒有問題;測試用例是一組單元測試称簿,這些單元測試一起核實函數(shù)在各種情形下的的行為都符合要求扣癣。良好的測試用例考慮到了函數(shù)可能收到的各種輸入,包含針對所有這些情形的的測試憨降。全覆蓋式測試用例包含一整套單元測試父虑,涵蓋了各種可能的函數(shù)使用方式。對于大型項目授药,要實現(xiàn)全覆蓋可能很難士嚎。通常,最初只要針對代碼的重要行為編寫測試即可烁焙,等項目被廣泛使用時再考慮全覆蓋航邢。
(2)可通過的測試
要為函數(shù)編寫測試用例,可先導入模塊unittest以及要測試的函數(shù)骄蝇,再創(chuàng)建一個unittest.TestCase的類膳殷,并編寫一系列方法對函數(shù)行為的不同方面進行測試。下面是只包含一個方法的測試用例:
import unittest
from name_function import get_formatted_name
class NamesTestCase(unittest.TestCase):
def test_first_last_name(self):
formatted_name = get_formatted_name('janis', 'joplin')
self.assertEqual(formatted_name, 'Janis Joplin')
unittest.main()
# 輸出:
.
----------------------------------------------------------------------
Ran 1 test in 0.000s
OK
首先九火,需要導入模塊unittest和要測試的函數(shù)赚窃。然后創(chuàng)建一個類,用于包含一系列針對被測試函數(shù)的單元測試岔激,這個類必須繼承unittest.TestCase類勒极,這樣Python才知道如何運行你編寫的測試。
這個類只包含了一個方法虑鼎,用于核實姓名能否被正確地格式化辱匿。在運行上述文件時,所有以test_大頭的方法都將自動運行炫彩。
這里使用了unittest類中最有用的功能之一:一個斷言方法匾七。斷言方法用來核實得到的結果是否與期望的結果一致。self.assertEqual()方法就是將第一個參數(shù)和第二個參數(shù)進行比較江兢。
在輸出的結果中昨忆,第一行的句點表明有一個測試通過了。接下來的一行指出Python運行了一個測試杉允,消耗的時間不到0.001秒邑贴。最后的OK表明該測試用例中的所有單元測試都通過了席里。
(3)不能通過的測試
我們故意只在函數(shù)中添加可以處理中間名的形參middle,而沒有修改測試用例中的實參奖磁。運行結果:
E
======================================================================
ERROR: test_first_last_name (__main__.NamesTestCase)
----------------------------------------------------------------------
Traceback (most recent call last):
File "test_name_function.py", line 8, in test_first_last_name
formatted_name = get_formatted_name('janis', 'joplin')
TypeError: get_formatted_name() missing 1 required positional argument: 'last'
----------------------------------------------------------------------
Ran 1 test in 0.002s
FAILED (errors=1)
由于測試沒有通過,返回了很多信息独旷。第一行輸出只有一個字母E署穗,它指出測試用例中有一個單元測試導致了錯誤寥裂。然后可以看到類中的函數(shù)導致了錯誤嵌洼。當測試用例包含很多單元測試時,準確知道那個測試沒通過至關重要封恰。在往下麻养,我們看到一個標準的traceback,它準確指出函數(shù)調用中出現(xiàn)了問題诺舔,因為它缺少了一個必不可少的位置實參鳖昌。最后顯示運行了一個單元測試,并指出整個測試用例都沒有通過低飒。
(4)測試未通過時怎么辦
測試未通過說明你編寫的新代碼有錯许昨,此時,不要修改測試褥赊,而應修復導致測試不能通過的代碼:檢查剛對函數(shù)所做的修改糕档,找出導致函數(shù)行為不符合預期的修改。
對于上述未能通過的測試拌喉,我們知道是新增的中間名參數(shù)導致的速那,所以可以讓中間名變?yōu)榭蛇x的,即添加默認值尿背,然后再適當?shù)靥砑觟f判斷語句端仰,就可以讓測試通過了。
(5)添加新測試
下面再編寫一個測試田藐,用于測試包含中間名的姓名荔烧。
import unittest
from name_function import get_formatted_name
class NamesTestCase(unittest.TestCase):
def test_first_last_name(self):
formatted_name = get_formatted_name('janis', 'joplin')
self.assertEqual(formatted_name, 'Janis Joplin')
def test_first_last_middle_name(self):
formatted_name = get_formatted_name('wolfgang', 'mozart', 'amadeus')
self.assertEqual(formatted_name, 'Wolfgang Amadeus Mozart')
unittest.main()
# 輸出:
..
----------------------------------------------------------------------
Ran 2 tests in 0.000s
OK
這個新添加方法的方法名必須以test_開頭,這樣它才會在我們運行整個測試文件時自動運行汽久。在TestCase類中使用很長的方法名是可以的鹤竭;這些方法名必須具有描述性的,這才能讓你明白測試未通過時的輸出回窘;這些方法由Python自動調用诺擅,你根本不用編寫調用它們的代碼。
2啡直、測試類
(1)unittest Module中常用的6個斷言方法
方法 | 用途 |
---|---|
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中 |
Python在unittest.TestCase類中提供了很多斷言方法烁涌。如果你認為應該滿足的條件實際上并不滿足苍碟,Python將引發(fā)異常。
(2)一個要測試的類
類的測試與函數(shù)的測死相似撮执,但也存在一些不同之處微峰。下面一個幫助管理匿名調查的類:
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)
要創(chuàng)建這個類的實例,只需提供一個問題即可抒钱。下面編寫一個使用它的程序:
from survey import AnonymousSurvey
question = "What language did you first learn to speak?"
my_survey = AnonymousSurvey(question)
my_survey.show_question()
print("Enter 'q' at any time to quit.\n")
while True:
response = input("Language: ")
if response == 'q':
break
my_survey.store_response(response)
print("\nThank you to everyone who participated in the survey!")
my_survey.show_results()
# 輸出:
What language did you first learn to speak?
Enter 'q' at any time to quit.
Language: English
Language: Spanish
Language: English
Language: Mandarin
Language: q
Thank you to everyone who participated in the survey!
Survey results:
- English
- Spanish
- English
- Mandarin
(3)測試AnonymousSurvey類
下面編寫一個測試:如果用戶面對調查問題時只提供一個答案蜓肆,這個答案也能被妥善地存儲。
import unittest
from survey import AnonymousSurvey
class TestAnonymousSurvey(unittest.TestCase):
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)
unittest.main()
# 測試輸出:
.
----------------------------------------------------------------------
Ran 1 test in 0.000s
OK
這里的測試與測試函數(shù)時類似谋币,這里的第一個方法驗證調查問題的單個答案被存儲后仗扬,會包含在調查結果列表中。要測試類的行為蕾额,需要創(chuàng)建其實例早芭。由輸出知道,測試順利通過了诅蝶。
下面來核實用戶提供三個答案時退个,它們也將被妥善地存儲。
def test_store_three_responses(self):
question = "What language did you first learn to speak?"
my_survey = AnonymousSurvey(question)
responses = ['English', 'Spanish', 'Mandarih']
for response in responses:
my_survey.store_response(response)
for response in responses:
self.assertIn(response, my_survey.responses)
# 測試輸出:
..
----------------------------------------------------------------------
Ran 2 tests in 0.001s
OK
在測試類中添加上述的方法调炬,有結果可知语盈,測試也順利通過了。但是這些測試有些重復的地方缰泡。下面使用unittest的另一項功能來提高它們的效率刀荒。
(4)方法setUp()
在前面的示例中,我們在每個測試的方法中都創(chuàng)建了一個AnonymousSurvey實例匀谣,并在每個方法中都創(chuàng)建了答案照棋。unittest.TestCase類中包含了方法setUp(),Python將先運行它武翎,再運行各個以test_開頭的方法烈炭。這樣,在你編寫的每個測試方法中都可使用在方法setUp()中創(chuàng)建的對象宝恶。
下面使用setUp()來創(chuàng)建一個調查對象和一組答案符隙,供兩個測試方法使用:
import unittest
from survey import AnonymousSurvey
class TestAnonymousSurvey(unittest.TestCase):
def setUp(self):
question = "What language did you first learn to speak?"
self.my_survey = AnonymousSurvey(question)
self.responses = ['English', 'Spanish', 'Mandarih']
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()
方法setUp()做了兩件事情:創(chuàng)建一個調查對象;創(chuàng)建一個答案列表垫毙。存儲這兩樣東西的變量名包含前綴self(即存儲在屬性中)霹疫,因此可在這個類的任何地方使用。
測試自己編寫的類時综芥,方法setUp()讓測試方法編寫起來更容易:可在setUp()方法中創(chuàng)建一系列實例并設置它們的屬性丽蝎,再在測試方法中直接使用這些實例。
注意:運行測試用例時,每完成一個單元測試屠阻,Python都打印一個字符:測試通過時打印一個句點红省;測試引發(fā)錯誤時打印一個E;測試導致斷言失敗時打印一個F国觉。這就是你運行測試用例時吧恃,在輸出的第一行中看到的句點和字符數(shù)量各不相同的原因。如果測試用例包含很多單元測試麻诀,需要運行很長時間痕寓,就可通過觀察這些結果來獲悉有多少個測試通過了。