1. 程式人生 > >讀書筆記「Python程式設計:從入門到實踐」_11.測試函式

讀書筆記「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讓使用者輸入名和姓,並顯示整潔的全名:

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 + '.')

  我們可以在每次修改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()