1. 程式人生 > >Python程式設計從入門到實踐筆記——測試程式碼(一篇足夠)

Python程式設計從入門到實踐筆記——測試程式碼(一篇足夠)

11 測試程式碼

  編寫函式或類時,還可為其編寫測試。通過測試,可確定程式碼面對各種輸入都能夠按要求的那樣工作。在程式中新增新程式碼時,你也可以對其進行測試,確認它們不會破壞程式既有的行為。
  在本章中,你將學習如何使用Python模組unittest中的工具來測試程式碼。你將學習編寫測試用例,核實一系列輸入都將得到預期的輸出。你將看到測試通過了是什麼樣子,測試未通過又是什麼樣子,還將知道測試未通過如何有助於改進程式碼。你將學習如何測試函式和類,並將知道該為專案編寫多少個測試。

"""11.1 測試函式"""
"""
下面是一個簡單的函式,它接受名和姓並返回整潔的姓名:
"""
"""name_function.py""" def get_formatted_name(first, last): """Generate a neatly formatted full name.""" full_name = first + ' ' + last return full_name.title()
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
"""
為核實get_formatted_name()像期望的那樣工作,我們來編寫一個
使用這個函式的程式。程式names.py讓使用者輸入名和姓,並顯示整潔的全名: 
"""
"""names.py""" from name_function import get_formatted_name print "Enter 'q' at any time to quit." while True: first = raw_input("\nPlease give me a first name: ") if first == 'q': break last = raw_input("Please give me a last name: ") if last == 'q': break formatted_name = get_formatted_name(first, last) print("\tNeatly formatted name: "
+ formatted_name + '.')
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
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

  
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11

  從上述輸出可知,合併得到的姓名正確無誤。現在假設我們要修改get_formatted_name(),使其還能夠處理中間名。這樣做時,我們要確保不破壞這個函式處理只有名和姓的姓名的方式。為此,我們可以在每次修改get_formatted_name()後都進行測試:執行程式names.py,並輸入像Janis Joplin這樣的姓名,但這太煩瑣了。
  所幸Python提供了一種自動測試函式輸出的高效方式。倘若我們對get_formatted_name()進行自動測試,就能始終信心滿滿,確信給這個函式提供我們測試過的姓名時,它都能正確地工作。

11.1.1 單元測試和測試用例

  Python標準庫中的模組unittest提供了程式碼測試工具。
  單元測試用於核實函式的某個方面沒有問題;測試用例是一組單元測試,這些單元測試一起核實函式在各種情形下的行為都符合要求。
  良好的測試用例考慮到了函式可能收到的各種輸入,包含針對所有這些情形的測試。全覆蓋式測試用例包含一整套單元測試,涵蓋了各種可能的函式使用方式。對於大型專案,要實現全覆蓋可能很難。通常,最初只要針對程式碼的重要行為編寫測試即可,等專案被廣泛使用時再考慮全覆蓋。

11.1.2 可通過的測試

  要為函式編寫測試用例,可先匯入模組unittest以及要測試的函式,再建立一個繼承unittest.TestCase的類,並編寫一系列方法(需要以test或Test開頭)對函式行為的不同方面進行測試。 下面是一個只包含一個方法的測試用例,它檢查函式get_formatted_name()在給定名和姓時能否正確地工作:

"""test_name_function.py"""

import unittest
#from name_function import get_formatted_name

def get_formatted_name(first, last): 
    """Generate a neatly formatted full name.""" 
    full_name = first + ' ' + last 
    return full_name.title() 

class NamesTestCase(unittest.TestCase):  # 這個類必須繼承unittest.TestCase類
    """測試name_function.py"""   

    def test_first_last_name(self):
        formatted_name = get_formatted_name('janis', 'joplin')

        # ①
        self.assertEqual(formatted_name, 'Janis Joplin')

if __name__ == '__main__':  
#    unittest.main()
    unittest.main(argv=['first-arg-is-ignored'], exit=False)
  
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
.

Janis Joplin



----------------------------------------------------------------------
Ran 1 test in 0.002s

OK

  
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10

①處,我們使用了unittest類最有用的功能之一:一個斷言方法。斷言方法用來核實得到的結果是否與期望的結果一致。

  在這裡,我們期望formatted_name的值為"Janis Joplin"。為檢查是否確實如此,我們呼叫unittest的方法assertEqual(),並向它傳遞formatted_name"Janis Joplin"進行比較。程式碼行unittest.main()讓Python執行這個檔案中的測試。

說明:

  書中原始碼在本地可以執行,但是在jupyter notebook中執行報錯”AttributeError: ‘module’ object has no attribute”,看到Stack Overflow上的問答,參考修改後可以在jupyter notebook中執行。

unittest.main(argv=['first-arg-is-ignored'], exit=False)

unittest.main(argv=['ignored', '-v'], exit=False)

1.1.3 不能通過的測試

  修改get_formatted_name(),使其能夠處理中間名,但這樣做時,故意讓這個函式無法正確地處理像Janis Joplin這樣只有名和姓的姓名。

  下面是函式get_formatted_name()的新版本,它要求通過一個實參指定中間名:

"""name_function.py"""
def get_formatted_name(first, middle, last): 
    """生成整潔的姓名""" 
    full_name = first + ' ' + middle + ' ' + last 
    return full_name.title() 
  
  • 1
  • 2
  • 3
  • 4
  • 5

  執行程式test_name_function.py

"""test_name_function.py"""

import unittest
#from name_function import get_formatted_name

def get_formatted_name(first, middle, last): 
    """生成整潔的姓名""" 
    full_name = first + ' ' + middle + ' ' + last 
    return full_name.title() 

class NamesTestCase(unittest.TestCase):  # 這個類必須繼承unittest.TestCase類
    """測試name_function.py"""   

    def test_first_last_name(self):
        formatted_name = get_formatted_name('janis', 'joplin')

        self.assertEqual(formatted_name, 'Janis Joplin')

if __name__ == '__main__':  
#    unittest.main()
    unittest.main(argv=['first-arg-is-ignored'], exit=False)
  
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21

E

ERROR: test_first_last_name (main.NamesTestCase)

Traceback (most recent call last):
File “<ipython-input-22-c6f1d3890843>”, line 15, in test_first_last_name
formatted_name = get_formatted_name(‘janis’, ‘joplin’)
TypeError: get_formatted_name() takes exactly 3 arguments (2 given)


Ran 1 test in 0.041s

FAILED (errors=1)

  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13

11.1.4 測試未通過時怎麼辦

  測試未通過時怎麼辦呢?如果你檢查的條件沒錯,測試通過了意味著函式的行為是對的,而測試未通過意味著你編寫的新程式碼有錯。因此,測試未通過時,不要修改測試,而應修復導致測試不能通過的程式碼:檢查剛對函式所做的修改,找出導致函式行為不符合預期的修改。
  在這個示例中,get_formatted_name()以前只需要兩個實參——名和姓,但現在它要求提供名、中間名和姓。就這裡而言,最佳的選擇是讓中間名變為可選的。這樣做後,使用類似於Janis Joplin的姓名 進 行 測 試 時 , 測 試 就 會 通 過 了 , 同 時 這 個 函 數 還 能 接 受 中 間 名 。 下 面 來 修 改get_formatted_name(),將中間名設定為可選的,然後再次執行這個測試用例。如果通過了,我們接著確認這個函式能夠妥善地處理中間名。

  將中間名設定為可選的,可在函式定義中將形參middle移到形參列表末尾,並將其預設值指定為一個空字串。我們還要新增一個if測試,以便根據是否提供了中間名相應地建立姓名:

"""name_function.py"""

def get_formatted_name(first, last, middle=''):
    """Generate a neatly-formatted full name."""
    if middle:
        full_name = first + ' ' + middle + ' ' + last
    else:
        full_name = first + ' ' + last
    return full_name.title()

  
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10

  再次執行test_name_function.py:

"""test_name_function.py"""

import unittest
from name_function import get_formatted_name

class NamesTestCase(unittest.TestCase):  # 這個類必須繼承unittest.TestCase類
    """測試name_function.py"""   

    def test_first_last_name(self):
        formatted_name = get_formatted_name('janis', 'joplin')
        print formatted_name
        self.assertEqual(formatted_name, 'Janis Joplin')

if __name__ == '__main__':  
#    unittest.main()
    unittest.main(argv=['first-arg-is-ignored'], exit=False)
  
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
.

Janis Joplin



----------------------------------------------------------------------
Ran 1 test in 0.003s

OK

  
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10

11.1.5 新增新測試

  確定get_formatted_name()又能正確地處理簡單的姓名後,我們再編寫一個測試,用於測試包含中間名的姓名。為此,我們在NamesTestCase類中再新增一個方法:

"""test_name_function.py"""

import unittest
from name_function import get_formatted_name

class NamesTestCase(unittest.TestCase):  # 這個類必須繼承unittest.TestCase類
    """測試name_function.py"""   

    def test_first_last_name(self):
        formatted_name = get_formatted_name('janis', 'joplin')
        print formatted_name
        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')

if __name__ == '__main__':  
#    unittest.main()
    unittest.main(argv=['first-arg-is-ignored'], exit=False)
  
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
...

Janis Joplin



----------------------------------------------------------------------
Ran 3 tests in 0.007s

OK

  
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10

  我們將這個方法命名為test_first_last_middle_name()。方法名必須以test_打頭,這樣它才會在我們執行test_name_function.py時自動執行。這個方法名清楚地指出了它測試的是get_formatted_name()的哪個行為,這樣,如果該測試未通過,我們就會馬上知道受影響的是哪種型別的姓名。在TestCase類中使用很長的方法名是可以的;這些方法的名稱必須是描述性的,這才能讓你明白測試未通過時的輸出;這些方法由Python自動呼叫,你根本不用編寫呼叫它們的程式碼。
  為測試函式get_formatted_name(),我們使用名、姓和中間名呼叫它,再使用assertEqual()檢查返回的姓名是否與預期的姓名(名、中間名和姓)一致。

習題11-1 城市和國家

  編寫一個函式,它接受兩個形參:一個城市名和一個國家名。這個函式返回一個格式為 City, Country 的字串,如 Santiago, Chile 。將這個函式儲存在一個名為 city_functions.py 的模組中。
  建立一個名為 test_cities.py 的程式,對剛編寫的函式進行測試(別忘了,你需要匯入模組 unittest 以及要測試的函式)。編寫一個名為 test_city_country()的方法,核實使用類似於 ‘santiago’ 和 ‘chile’ 這樣的值來呼叫前述函式時,得到的字串是正確的。
執行 test_cities.py,確認測試 test_city_country()通過了。

"test_cities.py"

import unittest
from  city_functions import city_country

class CityCountryTest(unittest.TestCase):

    def test_city_country(self):
        formatted_string = city_country('santiago', 'chile')
        self.assertEqual(formatted_string, 'Santiago, Chile')

if __name__ == '__main__':
    unittest.main(argv=['first-arg-is-ignored'], exit=False)
  
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
...

Janis Joplin



----------------------------------------------------------------------
Ran 3 tests in 0.007s

OK

  
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10

習題11–2 人口數量

  修改前面的函式,使其包含第三個必不可少的形參 population,並返回一個格式為 City, Country – population xxx 的字串,如 Santiago, Chile – population 5000000。執行 test_cities.py,確認測試 test_city_country()未通過。
  修改上述函式,將形參population 設定為可選的。再次執行 test_cities.py,確認測試 test_city_country()又通過了。
  再編寫一個名為 test_city_country_population()的測試,核實可以使用類似於 ‘santiago’、’chile’ 和 ‘population=5000000’ 這樣的值來呼叫這個函式。再次執行test_cities.py,確認測試 test_city_country_population()通過了。

"test_cities.py"
"加一個形參population"

import unittest
from  city_functions import city_country

class CityCountryTest(unittest.TestCase):

    def test_city_country(self):
        formatted_string = city_country('santiago', 'chile')
        self.assertEqual(formatted_string, 'Santiago, Chile')

if __name__ == '__main__':
    unittest.main(argv=['first-arg-is-ignored'], exit=False)
  
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14

ERROR: test_city_country (main.CityCountryTest)

Traceback (most recent call last):
File “<ipython-input-1-40099b039d57>”, line 9, in test_city_country
formatted_string = city_country(‘santiago’, ‘chile’)
TypeError: city_country() takes exactly 3 arguments (2 given)


Ran 1 test in 0.002s

FAILED (errors=1)

  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13

"test_cities.py"
"形參可選"

import unittest
from  city_functions import city_country

class CityCountryTest(unittest.TestCase):

    def test_city_country(self):
        formatted_string = city_country('santiago', 'chile')
        self.assertEqual(formatted_string, 'Santiago, Chile')

if __name__ == '__main__':
    unittest.main(argv=['first_arg-is-ignored'], exit=False)
  
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14

.

Ran 1 test in 0.002s

OK

  • 1
  • 2
  • 3
  • 4
  • 5

"test_cities.py"
"測試人口"

import unittest
from  city_functions import city_country

class CityCountryTest(unittest.TestCase):

    def test_city_country(self):
        formatted_string = city_country('santiago', 'chile')
        self.assertEqual(formatted_string, 'Santiago, Chile')

    def test_city_country_population(self):
        formatted_string = city_country('santiago', 'chile', 5000000)
        self.assertEqual(formatted_string, 'Santiago, Chile - population 5000000')

if __name__ == '__main__':
    unittest.main(argv=['first-arg-is-ignored'], exit=False)
  
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18

Ran 2 tests in 0.005s

OK

  • 1
  • 2
  • 3
  • 4
  • 5

11.2 測試類

11.2.1 各種斷言方法

  Python在unittest.TestCase類中提供了很多斷言方法。表中描述了6個常用的斷言方法。使用這些方法可核實返回的值等於或不等於預期的值、返回的值為TrueFalse、返回的值在列表中或不在列表中。你只能在繼承unittest.TestCase的類中使用這些方法。

方法 用途
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  一個要測試的類 """

"""survey.py"""

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

  
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25
  • 26
"""編寫一個使用AnonymousSurvey類的程式"""

"""language_survey.py"""

from survey import AnonymousSurvey

#定義一個問題,並建立一個表示調查的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 = raw_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()

  
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
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

  
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
"""11.2.3  測試 AnonymousSurvey 類 """

import unittest
from survey import AnonymousSurvey

class TestAnonymousSurvey(unittest.TestCase):
    """Tests for the class AnonymousSurvey."""


    def test_store_single_response(self):
        """Test that a single response is stored properly."""
        question = "What language did you first learn to speak?"
        my_survey = AnonymousSurvey(question) 
        my_survey.store_response('English') 

        self.assertIn('English', my_survey.responses)


if __name__ == '__main__':           
    unittest.main(argv=['first-arg-is-ignored'], exit=False)


  
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22

.

Ran 1 test in 0.001s

OK

  • 1
  • 2
  • 3
  • 4
  • 5

  這很好,但只能收集一個答案的調查用途不大。下面來核實使用者提供三個答案時,它們也將被妥善地儲存。為此,我們在TestAnonymousSurvey中再新增一個方法:

import unittest
from survey import AnonymousSurvey

class TestAnonymousSurvey(unittest.TestCase):
    """Tests for the class AnonymousSurvey."""


    def test_store_single_response(self):
        """Test that a single response is stored properly."""
        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):
        """Test that three individual responses are stored properly."""
        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)         



if __name__ == '__main__':           
    unittest.main(argv=['first-arg-is-ignored'], exit=False)


  
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25
  • 26
  • 27
  • 28
  • 29
  • 30
  • 31
  • 32

Ran 2 tests in 0.004s

OK

  • 1
  • 2
  • 3
  • 4
  • 5

  效果很好,但這些測試有些重複的地方。下面使用unittest的另一項功能來提高它們的效率。

11.2.4 方法 setUp()

  在前面的test_survey.py中,我們在每個測試方法中都建立了一個 AnonymousSurvey 例項,並在每個方法中都建立了答案。unittest.TestCase 類包含方法 setUp() ,讓我們只需建立這些物件一次,並在每個測試方法中使用它們。如果你在 TestCase 類中包含了方法 setUp() ,Python將先執行它,再執行各個以test_打頭的方法。這樣,在你編寫的每個測試方法中都可使用在方法 setUp() 中建立的物件了。
  下面使用setUp()來建立一個調查物件和一組答案,供方法test_store_single_response()test_store_three_responses()使用:

import unittest
from survey import AnonymousSurvey

class TestAnonymousSurvey(unittest.TestCase):
    """Tests for the class AnonymousSurvey."""

    def setUp(self):
        """
        Create a survey and a set of responses for use in all test methods.
        """
        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):
        """Test that a single response is stored properly."""
        self.my_survey.store_response(self.responses[0])
        self.assertIn(self.responses[0], self.my_survey.responses)


    def test_store_three_responses(self):
        """Test that three individual responses are stored properly."""
        for response in self.responses:
            self.my_survey.store_response(response)
        for response in self.responses:
            self.assertIn(response, self.my_survey.responses)


if __name__ == '__main__':
    unittest.main(argv=['first-arg-is-ignored'], exit=False)

  
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25
  • 26
  • 27
  • 28
  • 29
  • 30
  • 31
  • 32

Ran 2 tests in 0.003s

OK

  • 1
  • 2
  • 3
  • 4
  • 5

  方法setUp()做了兩件事情:建立一個調查物件;建立一個答案列表。儲存這兩樣東西的變數名包含字首self(即儲存在屬性中),因此可在這個類的任何地方使用。這讓兩個測試方法都更簡單,因為它們都不用建立調查物件和答案。方法test_store_single_response()核 實 self.responses 中 的 第 一 個 答 案 ——self.responses[0]—— 被 妥 善 地 存 儲 , 而 方 法test_store_three_response()核實self.responses中的全部三個答案都被妥善地儲存。
  再次執行test_survey.py時,這兩個測試也都通過了。如果要擴充套件AnonymousSurvey,使其允許每位使用者輸入多個答案,這些測試將很有用。修改程式碼以接受多個答案後,可執行這些測試,確認儲存單個答案或一系列答案的行為未受影響。
  測試自己編寫的類時,方法setUp()讓測試方法編寫起來更容易:可在setUp()方法中建立一系列例項並設定它們的屬性,再在測試方法中直接使用這些例項。相比於在每個測試方法中都建立例項並設定其屬性,這要容易得多。

注意
  執行測試用例時,每完成一個單元測試,Python都列印一個字元:測試通過時列印一個句點;測試引發錯誤時列印一個E;測試導致斷言失敗時列印一個F。這就是你執行測試用例時,在輸出的第一行中看到的句點和字元數量各不相同的原因。如果測試用例包含很多單元測試,需要執行很長時間,就可通過觀察這些結果來獲悉有多少個測試通過了。

11-3 僱員:
  編寫一個名為 Employee 的類,其方法__init__()接受名、姓和年薪,並將它們都儲存在屬性中。編寫一個名為give_raise()的方法,它預設將年薪增加 5000美元,但也能夠接受其他的年薪增加量。
  為 Employee 編寫一個測試用例,其中包含兩個測試方法:test_give_default_raise()test_give_custom_raise()。使用方法 setUp(),以免在每個測試方法中都建立新的僱員例項。執行這個測試用例,確認兩個測試都通過了。

class Employee():
    def __init__(self,last_name, first_name, salary=10000 ):
        self.last_name = last_name
        self.first_name = first_name
        self.salary = salary

    def give_raise(self,added=5000):
        self.salary += added
        return added
  
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
import unittest
from EmployeeFile import Employee

class TestEmployeeRaise(unittest.TestCase):

    def setUp(self):
        self.test1 = Employee('Tom', 'Smith')
        self.test2 = Employee('Tom', 'Smith',3000)


    def test_give_default_raise(self):
        self.salary1 = self.test1.give_raise()
        self.assertEqual(str(self.salary1), '5000')


    def test_give_custom_raise(self):
        self.salary2 = self.test2.give_raise(3000)
        self.assertEqual(str(self.salary2), '3000')


if __name__ == '__main__':
    unittest.main(argv=['first-arg-is-ignored'], exit=False)

  
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23

Ran 2 tests in 0.003s

OK

  • 1
  • 2
  • 3
  • 4
  • 5

11.3 小結

  測試是很多初學者都不熟悉的主題。作為初學者,並非必須為你嘗試的所有專案編寫測試;但參與工作量較大的專案時,你應對自己編寫的函式和類的重要行為進行測試。這樣你就能夠更加確定自己所做的工作不會破壞專案的其他部分,你就能夠隨心所欲地改進既有程式碼了。如果不小心破壞了原來的功能,你馬上就會知道,從而能夠輕鬆地修復問題。相比於等到不滿意的使用者報告bug後再採取措施,在測試未通過時採取措施要容易得多。
  如果你在專案中包含了初步測試,其他程式設計師將更敬佩你,他們將能夠更得心應手地嘗試使用你編寫的程式碼,也更願意與你合作開發專案。如果你要跟其他程式設計師開發的專案共享程式碼,就必須證明你編寫的程式碼通過了既有測試,通常還需要為你新增的新行為編寫測試。
  請通過多開展測試來熟悉程式碼測試過程。對於自己編寫的函式和類,請編寫針對其重要行為的測試,但在專案早期,不要試圖去編寫全覆蓋的測試用例,除非有充分的理由這樣做。

11 測試程式碼