Python unittest 完全指南|從基礎到進階的詳細解析

1. Python unittest 是什麼?

unittest 是 Python 標準函式庫中包含的單元測試框架,對於確保程式碼品質至關重要。它允許開發者對程式碼的各個部分進行獨立測試,以便及早發現錯誤。此外,在持續開發過程中,它有助於確保程式碼的變更不會破壞既有功能。

單元測試的重要性

隨著程式碼變得更加複雜,確保不同部分能夠正確協同運作變得越來越困難。透過引入單元測試,可以更容易防止小幅度變更導致的意外錯誤,並保持整個程式的穩定性。

2. unittest 的基本用法

unittest 的基本概念是建立一個繼承自 unittest.TestCase 的類別,並在其中定義測試方法。在測試方法內,可以使用 assertEqual() 等斷言方法來比較預期結果與實際結果。

基本測試範例

以下範例測試 add(a, b) 函式的運作方式:

import unittest

# 被測試的函式
def add(a, b):
    return a + b

# 測試類別
class TestAddFunction(unittest.TestCase):

    def test_add_integers(self):
        result = add(2, 3)
        self.assertEqual(result, 5)

if __name__ == '__main__':
    unittest.main()

在這段程式碼中,我們測試 add() 函式是否正常運作。assertEqual() 方法用來確認預期值與實際結果是否相符。透過這種方式,可以驗證函式在不同情境下的正確性。

擴展測試

可以使用多個測試方法來測試函式對不同輸入的行為,例如測試浮點數或字串的相加:

def test_add_floats(self):
    result = add(2.5, 3.5)
    self.assertAlmostEqual(result, 6.0, places=2)

def test_add_strings(self):
    result = add("Hello, ", "World!")
    self.assertEqual(result, "Hello, World!")

透過這種方式,測試函式對不同類型的數據輸入的表現,確保它能夠在各種情境下正常運作。

RUNTEQ(ランテック)|超実戦型エンジニア育成スクール

3. setUp() 與 tearDown() 的使用方法

如果需要在測試執行前後自動執行特定處理,可以使用 setUp()tearDown() 方法。這些方法可用於在測試開始前進行必要的初始化,以及在測試結束後進行清理工作。

setUp() 的範例

setUp() 方法會在每個測試方法執行前自動被呼叫,可以將共用的初始化過程集中在這裡。

def setUp(self):
    self.temp_value = 42

tearDown() 的範例

tearDown() 方法會在每個測試方法執行後被呼叫,用來執行後續處理,例如釋放資源或刪除暫存檔案。

def tearDown(self):
    self.temp_value = None

透過這種方式,可以減少測試代碼的冗餘性,使測試程式碼更加清晰且易於維護。

4. 使用 Mock 進行依賴測試

當測試的程式碼依賴外部資源(例如資料庫、API 服務等)時,可以使用 Mock 來取代這些依賴,從而提高測試執行速度並確保測試結果的可預測性。Python 的 unittest.mock 模組能夠幫助我們輕鬆實現這一點。

Mock 的實際範例

以下範例將一個執行時間較長的函式 time_consuming_function() 替換為 Mock:

from unittest.mock import patch

class TestAddFunction(unittest.TestCase):

    @patch('my_module.time_consuming_function')
    def test_add_with_mock(self, mock_func):
        mock_func.return_value = 0
        result = add(2, 3)
        self.assertEqual(result, 5)

在這個範例中,透過 Mock 取代 time_consuming_function,讓測試在不執行該函式的情況下完成,這樣可以縮短測試時間,同時仍能確保正確的測試結果。

年収訴求

5. 例外處理與自訂斷言

unittest 中,也可以測試例外處理。例如,可以使用 assertRaises() 來確認特定情境下是否正確拋出例外。

測試例外處理

以下範例測試當除數為 0 時,是否會正確拋出 ZeroDivisionError

def test_divide_by_zero(self):
    with self.assertRaises(ZeroDivisionError):
        divide(1, 0)

這段程式碼會測試 divide(1, 0) 是否拋出 ZeroDivisionError,確保程式能夠正確處理異常狀況。

建立自訂斷言

當標準斷言方法無法滿足測試需求時,可以建立自訂斷言方法來提高測試的可讀性與靈活性。

def assertIsPositive(self, value):
    self.assertTrue(value > 0, f'{value} 不是正數')

透過自訂斷言,可以更清楚地表達測試邏輯,使測試更加直觀。

6. unittest 的測試探索功能

unittest 提供了測試探索(Test Discovery)功能,能夠自動搜尋專案中的所有測試檔案並執行測試。這在大型專案中特別實用。

如何使用測試探索

可以使用以下指令來執行測試探索:

python -m unittest discover

這個指令會自動搜尋當前目錄下所有符合 test_*.py 格式的測試檔案並執行。如果需要指定特定的目錄或檔案格式,可以使用以下選項:

python -m unittest discover -s tests -p "test_*.py"

這樣可以省去手動指定測試檔案的麻煩,使得在大型專案中管理測試變得更加高效。

侍エンジニア塾

7. 使用 unittest 提升測試效能的技巧

如果測試執行時間過長,可能會影響開發效率。以下是幾個使用 unittest 提高測試效能的方法。

優化檔案 I/O

需要讀寫檔案的測試可以透過在記憶體中執行來提高效能。例如,使用 StringIO 來模擬檔案操作,避免磁碟 I/O 延遲。

from io import StringIO

class TestFileOperations(unittest.TestCase):

    def test_write_to_memory(self):
        output = StringIO()
        output.write('Hello, World!')
        self.assertEqual(output.getvalue(), 'Hello, World!')

透過這種方式,可以有效提高測試的執行速度。

使用 Mock 來減少外部依賴

為了最小化對外部資源(如 API、資料庫)的存取,可以使用 Mock 來替代,以減少測試時間。例如,以下範例使用 Mock 來模擬 API 回應:

from unittest.mock import MagicMock

class TestApiCall(unittest.TestCase):

    def test_api_response(self):
        mock_api = MagicMock(return_value={'status': 'success'})
        response = mock_api()
        self.assertEqual(response['status'], 'success')

透過這種方式,可以避免不必要的網路或資料庫存取,進一步提升測試效能。

8. 總結與下一步

本文介紹了如何使用 Python 的 unittest 來進行單元測試,從基本概念到高級技巧,包括測試設定、Mock 依賴、效能優化等。

主要重點整理

  • 基本使用方式: 繼承 unittest.TestCase,並使用斷言方法來撰寫測試。
  • setUp() / tearDown(): 在測試執行前後執行共用邏輯,提高程式碼可讀性與維護性。
  • Mock 技術: 用於取代外部依賴,確保測試快速且穩定。
  • 測試探索: 自動發現並執行測試,適用於大型專案。
  • 效能優化技巧: 使用記憶體內部處理與 Mock 減少 I/O 操作,提高測試速度。

下一步

掌握 unittest 的基礎後,可以進一步學習進階測試技術,例如參數化測試(parametrized testing)來一次測試多組輸入,或使用測試覆蓋率工具來檢查測試涵蓋範圍。此外,也可以探索其他測試框架如 pytest,根據專案需求選擇最適合的測試工具。

單元測試是開發的重要一環,透過測試可以更早發現錯誤並確保程式碼品質,因此應當積極導入並持續優化測試流程。