filtration system showing tests being skipped based on conditions

單元測試(Unit Test)(020):從 assert 到 unittest

在軟體開發的世界裡,「沒有測試的程式,就像沒有安全帶的車」
即使是微小的錯誤,也可能引發巨大的損失:從火箭爆炸、航天任務失敗,到日常應用的嚴重故障。

在 Python 中,單元測試 (Unit Test) 是最常見也最重要的測試方法。它專注於驗證程式中最小的功能單元,例如函數或方法,確保它們在各種情境下都能正確運作。

為什麼測試如此重要?

軟體錯誤可能導致災難性後果:

  • 1996年:阿麗安5號火箭爆炸,損失5億美元
  • 1999年:火星氣候軌道器失敗,損失1.25億美元
  • 2015年:豐田汽車因軟體錯誤召回數百萬輛車
  • 2016年:航空管制系統故障導致全球航班延誤

「軟體測試的目標不僅是找出錯誤,更是要盡早發現它們。」

適當的測試可以防止災難性的後果,保護使用者安全並節省成本。

rocket explosion due to software error, dramatic failure

測試的類型

手動測試

由人員進行測試,模擬真實使用者的行為。

  • 執行程式並觀察結果
  • 透過使用者介面互動
  • 檢查輸出是否符合預期

優點:直觀、無需編寫額外代碼

缺點:耗時、容易出現人為錯誤

自動化測試

使用程式碼自動執行測試過程。

  • 單元測試:測試最小功能單元
  • 整合測試:測試多個單元的互動
  • 系統測試:測試整個應用程式

優點:快速、可重複、更少人為錯誤

缺點:需要額外編寫測試代碼

在本課程中,我們將重點關注自動化測試,特別是單元測試。

什麼是單元測試?

單元測試是測試程式中最小功能單元的過程,通常是單個函數或方法。它就像檢查門把手、鉸鏈和鎖以確保整扇門正常運作。

單元測試的好處:

  • 及早發現問題,降低修復成本
  • 提高程式碼品質和可維護性
  • 幫助理解程式碼行為
  • 作為程式碼的活文檔
  • 增強重構的信心

每個單元測試應該針對一個特定的功能,並驗證它在各種輸入下的行為是否正確。

puzzle pieces representing software units being tested individually

assert 陳述式:最簡單的測試方法

Python提供了一個簡單的測試機制:assert陳述式。它用於驗證條件是否為真。

# 基本語法
assert 條件, '條件不滿足時的錯誤訊息'

# 例子
def 乘以十(數字):
    return 數字 * 100  # 注意這裡有錯誤

結果 = 乘以十(20)
assert 結果 == 200, '預期乘以十(20)會返回200,但得到' + str(結果)

# 運行結果
AssertionError: 預期乘以十(20)會返回200,但得到2000

當條件評估為False時,將引發AssertionError並顯示錯誤訊息。

assert陳述式是快速檢查程式狀態的有力工具,但對於複雜測試場景有局限性。

unittest框架

Python的標準庫提供了unittest模組,一個功能豐富的測試框架。它解決了單獨使用assert的幾個問題:

測試收集和執行

自動收集和執行測試,提供完整的測試報告

測試隔離

即使某個測試失敗,其他測試仍會繼續執行

測試組織

將相關測試分組到測試類中,便於管理和維護

豐富的斷言方法

提供比簡單assert更強大的測試方法

測試夾具

為測試提供設置和清理機制

執行後,框架會報告測試通過或失敗,幫助我們快速定位錯誤。

unittest 常用斷言方法

  • 相等性
    • assertEqual(a, b):檢查是否相等
    • assertNotEqual(a, b):檢查是否不相等
  • 布林判斷
    • assertTrue(x)assertFalse(x)
  • 成員關係
    • assertIn(x, y)assertNotIn(x, y)
  • 數值比較
    • assertLess(a, b)assertGreaterEqual(a, b)
    • assertAlmostEqual(a, b, places=2):浮點數比較
  • 異常與警告
    • assertRaises(Exception, func, args...)
    • assertWarns(Warning, func, args...)

參數化測試

參數化測試讓我們可以用一個測試方法測試多個輸入,減少代碼重複。

def test_乘以十(self):
    測試數據 = [0, 1000000, -10]
    for 數字 in 測試數據:
        with self.subTest(數字):
            預期結果 = 數字 * 10
            訊息 = f'預期乘以十({數字})會返回{預期結果}'
            self.assertEqual(乘以十(數字), 預期結果, 訊息)

subTest上下文管理器將每次迭代視為單獨的測試,如果一個失敗,其他測試仍會繼續執行。

這種方法的優點:

  • 程式碼更簡潔,易於維護
  • 輕鬆擴展測試覆蓋範圍
  • 測試報告更清晰

參數化測試使我們能夠輕鬆地測試多種情況,而不必為每種情況編寫單獨的測試方法。

提示:為subTest提供參數(如示例中的數字)可以使測試報告更清晰。

跳過測試

有時我們需要在特定條件下跳過某些測試,unittest提供了兩種方式:

使用裝飾器

import sys

@unittest.skipUnless(sys.platform.startswith("linux"), 
                   "此測試僅在Linux上運行")
def test_linux功能(self):
    print("此測試僅在Linux上運行")

@unittest.skipIf(not sys.platform.startswith("linux"), 
                "此測試僅在Linux上運行")
def test_其他linux功能(self):
    print("此測試僅在Linux上運行")

使用skipTest方法

def test_linux功能(self):
    if not sys.platform.startswith("linux"):
        self.skipTest("測試僅在Linux上運行")

常用的測試跳過裝飾器:

  • @unittest.skip(reason):無條件跳過
  • @unittest.skipIf(condition, reason):條件為真時跳過
  • @unittest.skipUnless(condition, reason):條件為假時跳過

跳過的測試在結果中會被標記為skipped,而不是失敗或成功。

filtration system showing tests being skipped based on conditions

預期失敗

有時我們知道某個測試會失敗(例如已知的錯誤或故意失敗的設計),但我們不希望這種預期的失敗影響測試結果。

@unittest.expectedFailure
def test_已知問題(self):
    self.assertEqual(有錯誤的函數(), 預期結果)

使用@unittest.expectedFailure裝飾器:

  • 如果測試失敗:標記為「預期失敗」(expected failure),視為通過
  • 如果測試通過:標記為「意外成功」(unexpected success),視為失敗

這對於處理以下情況很有用:

  • 記錄已知但尚未修復的錯誤
  • 跟踪進行中的開發工作
  • 測試負面情況(即測試應該失敗的情況)

結語

測試不是額外的工作,而是讓你更快、更有信心開發的工具。

assertunittest,再到進階的 pytest 與 CI/CD,自動化測試將成為每位 Python 開發者不可或缺的技能。

發佈留言

發佈留言必須填寫的電子郵件地址不會公開。 必填欄位標示為 *

High Quality

Lorem ipsum dolor sit amet, consectetur adipiscing elitsed do eiusmod tempor.

Fast Delivery

Lorem ipsum dolor sit amet, consectetur adipiscing elitsed do eiusmod tempor.

Best Warranty

Lorem ipsum dolor sit amet, consectetur adipiscing elitsed do eiusmod tempor.