在軟體開發的世界裡,「沒有測試的程式,就像沒有安全帶的車」。
即使是微小的錯誤,也可能引發巨大的損失:從火箭爆炸、航天任務失敗,到日常應用的嚴重故障。
在 Python 中,單元測試 (Unit Test) 是最常見也最重要的測試方法。它專注於驗證程式中最小的功能單元,例如函數或方法,確保它們在各種情境下都能正確運作。
為什麼測試如此重要?
軟體錯誤可能導致災難性後果:
- 1996年:阿麗安5號火箭爆炸,損失5億美元
- 1999年:火星氣候軌道器失敗,損失1.25億美元
- 2015年:豐田汽車因軟體錯誤召回數百萬輛車
- 2016年:航空管制系統故障導致全球航班延誤
「軟體測試的目標不僅是找出錯誤,更是要盡早發現它們。」
適當的測試可以防止災難性的後果,保護使用者安全並節省成本。

測試的類型
手動測試
由人員進行測試,模擬真實使用者的行為。
- 執行程式並觀察結果
- 透過使用者介面互動
- 檢查輸出是否符合預期
優點:直觀、無需編寫額外代碼
缺點:耗時、容易出現人為錯誤
自動化測試
使用程式碼自動執行測試過程。
- 單元測試:測試最小功能單元
- 整合測試:測試多個單元的互動
- 系統測試:測試整個應用程式
優點:快速、可重複、更少人為錯誤
缺點:需要額外編寫測試代碼
在本課程中,我們將重點關注自動化測試,特別是單元測試。
什麼是單元測試?
單元測試是測試程式中最小功能單元的過程,通常是單個函數或方法。它就像檢查門把手、鉸鏈和鎖以確保整扇門正常運作。
單元測試的好處:
- 及早發現問題,降低修復成本
- 提高程式碼品質和可維護性
- 幫助理解程式碼行為
- 作為程式碼的活文檔
- 增強重構的信心
每個單元測試應該針對一個特定的功能,並驗證它在各種輸入下的行為是否正確。

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,而不是失敗或成功。

預期失敗
有時我們知道某個測試會失敗(例如已知的錯誤或故意失敗的設計),但我們不希望這種預期的失敗影響測試結果。
@unittest.expectedFailure
def test_已知問題(self):
self.assertEqual(有錯誤的函數(), 預期結果)
使用@unittest.expectedFailure
裝飾器:
- 如果測試失敗:標記為「預期失敗」(expected failure),視為通過
- 如果測試通過:標記為「意外成功」(unexpected success),視為失敗
這對於處理以下情況很有用:
- 記錄已知但尚未修復的錯誤
- 跟踪進行中的開發工作
- 測試負面情況(即測試應該失敗的情況)
結語
測試不是額外的工作,而是讓你更快、更有信心開發的工具。
從 assert
到 unittest
,再到進階的 pytest
與 CI/CD,自動化測試將成為每位 Python 開發者不可或缺的技能。