測試程式碼

PriSecLab@Gufe發表於2020-11-09

學習如何使用Pyhon模組unittest中的工具來測試程式碼

                                                                                    作者:雷蕾

(一)測試函式
學習測試,我們先建立要測試的程式碼(檔名為name_function.py):

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

示例中函式get_formatted_name()將名和姓合併成姓名 full_name:在名和姓之間加上一個空格並將其首字母大寫,然後返回結果。
為了核實函式get_formatted_name()像我們期望的那樣工作,我們來編寫一個使用該函式的程式(檔名為names.py):

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 gvie me a last name:")
    if last=='q':
        break
    formatted_name=get_formatted_name(first,last)
    print(f"\tNeatl formatted name: {formatted_name}.")

程式碼行from name_function import get_formatted_name是這個程式從name_function.py中匯入get_formatted_name()。
該示例讓使用者輸入名和姓,並將整潔的姓名按格式列印出。
執行結果:

enter 'q' at any time to quit

please give me a first name:lee
please gvie me a last name:willie
	Neatl formatted name: Lee Willie.

please give me a first name:q

(1)單元測試和測試用例
Python標準庫中的模組unittest提供了程式碼測試的工具。
單元測試用於核實函式的某個方面沒有問題;
測試用例是一組單元測試,它們是一道核實函式在各種情形下的行為都符合要求。良好的測試用例考慮到了函式可能收到的各種輸入,包含針對所有這些情形的測試。
全覆蓋的測試用例包含一整套單元測試,涵蓋了各種可能的函式使用方式。
(2)可通過的測試
要為函式編寫測試用例,可先匯入模組unittest和要測試的函式,再建立一個繼承unittes.TestCase的類,並編寫一系列方法對函式行為的不同方面進行測試。

#可通過的測試
import unittest
from name_function import get_formatted_name
class NamesTestCase(unittest.TestCase):
    '''測試name_function.py'''
    
    def test_first_last_name(self):
        '''能夠在正確處理像Janis Joplin這樣的姓名嗎?'''
        formatted_name=get_formatted_name('janis','joplin')
        self.assertEqual(formatted_name,'Janis Joplin')
        
if __name__=='__main__':
    unittest.main()

在該示例中只包含了一個方法test_first_last_name(),它能夠檢查函式get_formatted_name()在給定的名和姓能否正確工作。
程式碼行import unittest是匯入了模組unittest
程式碼行from name_function import get_formatted_name是這個程式從name_function.py中匯入get_formatted_name()
程式碼行class NamesTestCase(unittest.TestCase):是建立了一個名為NamesTestCase的類,用於一系列針對函式get_formatted_name()的單元測試,在該類中,只包含一個方法test_first_last_name(),它用於測試函式get_formatted_name()的一個方面。
程式碼行 formatted_name=get_formatted_name(‘janis’,‘joplin’)是使用實參’janis’和joplin’呼叫get_formatted_name(),並將結果賦給變數formatted_name。
程式碼行 self.assertEqual(formatted_name,‘Janis Joplin’)使用了unittest類最有用的功能之一:斷言方法(核實得到的結果是否與期望的結果一致),示例中我們期望formatted_name的值為’Janis Joplin’,為檢查是否正確,我們呼叫了unittest的方法assertEqual(),並向它傳遞formatted_name和’Janis Joplin’。
ssertEqual(formatted_name,‘Janis Joplin’)的意思主要是將formatted_name的值與’Janis Joplin’進行比較。
執行結果:

.
----------------------------------------------------------------------
Ran 1 test in 0.008s

OK
>>> 

第一行句點表示有一個測試通過了,接下來的一行指出了python執行了一個測試,消耗的時間為0.008s,最後的ok表示該測試用例中的所有單元測試都通過了。
(3)未通過的測試
當我們修改了要測試的程式碼(檔名為name_function_0.py):

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

在這個示例中,函式比上一個示例多了一個形參’middle’,我們重新測試該程式看會發生什麼?

import unittest
from name_function_0 import get_formatted_name
class NamesTestCase(unittest.TestCase):
    '''測試name_function.py'''
    
    def test_first_last_name(self):
        '''能夠在正確處理像Janis Joplin這樣的姓名嗎?'''
        formatted_name=get_formatted_name('janis','joplin')
        self.assertEqual(formatted_name,'Janis Joplin')
        
if __name__=='__main__':
    unittest.main()

執行結果:

E
======================================================================
ERROR: test_first_last_name (__main__.NamesTestCase)
能夠在正確處理像Janis Joplin這樣的姓名嗎?
----------------------------------------------------------------------
Traceback (most recent call last):
  File "C:\Users\12184\Desktop\python_work\11_測試程式碼\test_name_function_0.py", line 10, 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.010s

FAILED (errors=1)

我們來解讀報錯:
第一行字母指出測試用例中有一個單元測試導致了錯誤,之後指出是 test_first_last_name ()導致了錯誤,在traceback處指出了函式呼叫get_formatted_name(‘janis’,‘joplin’)有問題,TypeError指出是因為缺少了一個必不可少的位置實參(因為有3個形參,而我們只有2個實參值)。
(4)測試未通過怎麼辦
在上述示例中,我們已經知道了測試錯誤的原因,那為了測試通過,最佳的選擇我們可以讓形參’middle’的值變成可選的。
檔名為name_function_01.py

def get_formatted_name(first,last,middle=''):
    '''生成整潔的姓名'''
    if middle:
        full_name=f"{first} {middle} {last}"
    else:
         full_name=f"{first} {last}"
    return full_name.title()

重新測試:

import unittest
from name_function_01 import get_formatted_name
class NamesTestCase(unittest.TestCase):
    '''測試name_function.py'''
    
    def test_first_last_name(self):
        '''能夠在正確處理像Janis Joplin這樣的姓名嗎?'''
        formatted_name=get_formatted_name('janis','joplin')
        self.assertEqual(formatted_name,'Janis Joplin')
        
if __name__=='__main__':
    unittest.main()

執行結果:

----------------------------------------------------------------------
Ran 1 test in 0.015s

OK
>>> 

我們可以看到:通過修改,測試用例通過了。
(二)測試類
在進行測試之前,我們先介紹unittest模組中的各種斷言方法
方法———————————— 用途
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中
上述就是6種常用的斷言方法。
(1)一個要測試的類(檔名為survey.py)

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

為了證明這個類能夠正確工作,我們編寫一個使用它的程式:

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")
while True:
    response=input("language;")
    if response=='q':
        break
    my_survey.store_responses(response)
#顯示調查結果
print("\nthank you to everyone who participated in the survey")
my_survey.show_result()

執行結果:

what language did you first learn to speak?
enter 'q' at any time to quit
language;English
language;Spanish
language;q

thank you to everyone who participated in the survey
Survey result:
-English
-Spanish

(2)測試AnonymousSurvey

import unittest
from survey import AnonymousSurvey
class TestAnonymousSurvey(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_responses('English')
        self.assertIn('English',my_survey.responses)
if __name__=='__main__':
    unittest.main()

執行結果:

----------------------------------------------------------------------
Ran 1 test in 0.011s

OK

相關文章