Python入門實例——測試代碼


測試函數

Python 模塊 unittest 主要用來做測試代碼使用,這樣能夠在自己編寫代碼後使用 Python 腳本編寫簡單測試代碼完成自動測試。例如下面這樣的簡單函數:

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

函數 get_formatted_name() 將名和姓合併成姓名,在名和姓之間加上一個空格,並將它們的首字母都大寫,再返回結果。

接下來,爲了覈實該函數能夠像期望的那樣正常工作,編寫如下測試例程來實現測試該函數的功能:

from name_func 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
    format_name = get_formatted_name(first, last)
    print("\nNeatly formatted name: " + formatted_name + ".")

執行結果如下:

在這裏插入圖片描述
上面這種方式只是最簡單的通過自己輸入參數並將輸出打印出來觀察是否正確,但是這太繁瑣了。所幸 Python 提供了一種自動測試函數輸出的高效方式 —— unittest 模塊方式。

單元測試和測試用例

還是用於測試上面的函數,創建測試用例進行單元測試,測試代碼如下:

import unittest
from name_func import get_formatted_name

class NamesTestCase(unittest.TestCase):
    '''測試 name_func.py '''
    def test_first_last_name(self):
        '''能夠正確處理姓名'''
        formatted_name = get_formatted_name('janis', 'jopin')
        self.assertEqual(formatted_name, 'Janis Jopin')
        
unittest.main()

測試代碼中,首先導入了模塊 unittest 和測試函數對象 get_formatted_name() 。創建一個名爲 NamesTestCase 的類,用於包含一系列針對測試對象的單元測試(可以隨意給這個類命名)。這個類必須繼承 unittest.TestCase類 ,這樣 python 才知道如何運行編寫好的測試。

運行最後一行的 unittest.main() 可以使得 Python 運行這個文件中的測試。運行結果如下:

在這裏插入圖片描述

執行結果中第一行的 . (句點)表明有一個測試通過了;接下來的一行指出了 Python 運行了一個測試,消耗的時間不到 0.003 秒,最後的 OK 表明該測試用例中的所有單元測試都通過了。

在開發時不可避免會對原來寫好的函數進行修改,但是在源代碼修改後,針對之前版本的測試代碼也需要相應的修改了。下面對原來的函數進行如下修改:

# 添加中間姓名
def get_formatted_name(first, middle, last):
    '''Generate a neattly formatted full name.'''
    full_name = first + ' ' + middle + ' ' + last
    return full_name.title()

再次執行剛剛編寫的測試用例代碼,顯示結果如下:

在這裏插入圖片描述可以看到顯示的信息很多。

第一行輸出的字母 E 指出測試用例中有一個單元測試導致了錯誤,接下來輸出表示 NamesTestCase 中的 test_first_last_name() 導致了錯誤。當測試用例包含衆多單元測試時,知道是哪個測試未通過至關重要。輸出顯示後面會彈出一個標準的 traceback ,指出了函數測試錯誤的問題,這裏是因爲缺少了一個必不可少的位置實參。

針對這個問題,我們其實可以將原函數修改成兩個版本的兼容版本,具體如下:

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

在這個版本中,函數既可以接收三個參數的調用也同時可以接收舊版本的兩參數調用,並且我們可以在原來的測試代碼中添加新的測試用例,修改如下:

import unittest
from name_func import get_formatted_name

class NamesTestCase(unittest.TestCase):
    '''測試 name_func.py '''
    def test_first_last_name(self):
        '''測試兩參數正確處理姓名函數'''
        formatted_name = get_formatted_name('janis', 'jopin')
        self.assertEqual(formatted_name, 'Janis Jopin')
    
    def test_first_middle_last_name(self):
        '''測試三參數正確處理姓名函數'''
        formatted_name = get_formatted_name('janis', 'jopin', 'middle')
        self.assertEqual(formatted_name, 'Janis Middle Jopin')
        
unittest.main()

執行這個新的測試用例,結果如下:

在這裏插入圖片描述

從結果可以看出,測試用例通過了。

測試類

前面已經說明針對單個函數的測試如何編寫,但是 Python 作爲面向對象的語言,更多的則是以類存在,所以在 Python 測試中其實更多的也是針對類的測試。在編寫開發新的類後,如何能夠證明你開發的新類能夠正常工作或者能夠使得代碼開發正常。

接下來就是針對類的測試。

各種斷言方法

Python 在 unittest.TestCase類 中提供了很多斷言方法。前面能夠看出,斷言方法能夠檢查應該滿足的條件是否確實滿足。如果該條件確實滿足,那麼對程序行爲的假設就得到了確認,也就可以確信這中間沒用錯誤。如果實際上條件並沒有得到滿足,Python 將引發異常。

下面是6個常用到的斷言方法。使用這些方法可以覈實返回的值等於或不等於預期的值、返回的值爲 True 或 False 、 返回的值在列表中或不在列表中。並且只能在繼承 unittest.TestCase 的類中使用這些方法。

方法 用途
assertEqual(a, b) 覈實 a == b
assertNotEqual(a, b) 覈實 a != b
assertTrue(x) 覈實 x 爲 True
assertFalse(x) 覈實 y 爲 False
assertIn(item, list) 覈實 item 在 list 中
assertNotIn(item, list) 覈實 item 不在 list 中

編寫待測試的類

下面編寫一個類進行測試,具體代碼如下:

class AnonymousSurvey():
    '''收集匿名調查問卷的答案'''
    def __init__(self, question):
        '''存儲一個問題,併爲存儲答案做準備'''
        self.question = question
        self.responses = []
        
    def show_question(self):
        '''顯示調查問卷'''
        print(self.question)
        
    def shore_response(self, new_response):
        '''存儲單份調查問卷'''
        self.responses.append(new_response)
        
    def show_results(self):
        '''顯示收集到的所有答案'''
        print("Survey results:")
        for response in self.responses:
            print("- " + response)

在個類首先需要存儲一個指定的調查問題,並創建一個空列表,用於存儲答案。這個類包含打印調查問題的方法、在答案列表中添加新答案的方法以及將存儲在列表中的答案都打印出來的方法。要創建這個類的實例,只需要提供一個問題即可,有了類對象實例後,就可以使用 show_question() 來顯示其中的問題,使用 shore_response() 來存儲答案,並使用 show_results() 來顯示調查結果。

具體使用方式如以下代碼所示:

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 = 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()

執行結果如下:

在這裏插入圖片描述

測試類

接下來就針對這個類的行爲各方面編寫一個測試代碼,通過 assertIn() 來覈實結果,具體代碼:

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.shore_response("English")
        my_survey.shore_response("Chinese")
        
        self.assertIn("English", my_survey.responses)
        
unittest.main()

執行測試代碼結果如下:

在這裏插入圖片描述

添加測試用例,修改如下:

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.shore_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', 'Chinese', 'Mandarin']
        for response in responses:
            my_survey.shore_response(response)
            
        for response in responses:
            self.assertIn(response, my_survey.responses)
        
unittest.main()

執行結果如下:

在這裏插入圖片描述

方法 setUp()

前面測試類的多個測試用例的代碼中,每個測試用例都創建了一個實例對象及相應的操作。其實在 unittest.TestCase() 類中包含方法 setUp() ,這樣我們只需要執行這些操作一次就可以在後面測試方法中使用它們。修改上面的代碼如下:

import unittest
from survey import AnonymousSurvey

class TestAnonmyousSurvey(unittest.TestCase):
    '''針對AnonymousSurvey類的測試'''
    
    def setUp(self):
        '''創建一個調查對象和一組答案,供測試用例使用'''
        question = "What language did you first learn to speak?"
        self.my_survey = AnonymousSurvey(question)
        self.responses = ['English', 'Chinese', 'Mandarin']
    
    def test_store_single_response(self):
        '''測試單個答案會被妥善存儲'''
        self.my_survey.shore_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.shore_response(response)
        for response in self.responses:
            self.assertIn(response, self.my_survey.responses)
        
unittest.main()

執行結果如下:

在這裏插入圖片描述

可以看出兩項測試也都通過了,並且測試代碼編寫的更精簡了。

總結

本篇文章中,使用模塊 unittest 中的工具爲函數和類編寫測試,編寫繼承 unittest.TestCase類 以及編寫測試方法,使用 assert 斷言的方式來覈實函數和類的行爲是否符合預期以判斷測試是否通過。並且使用方法 setUp() 根據類高效創建實例並設置屬性,以便在類的所有測試方法中可以使用它們,從而精簡測試類代碼的編寫。

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章