前言
在軟體開發的世界裡,我們常常聽到「測試驅動開發」、「單元測試」等術語。對於剛進入軟體開發的新手來說,這些概念可能比較抽象,所以今天就來聊聊單元測試的重要性吧!
什麼是單元測試?
Unit tests help you to catch bugs early and document your code’s behavior.
單元測試是針對程式中最小可測試單位(通常是一個函數或方法)進行的自動化測試。它的核心理念是隔離:將被測試的單元與其他部分隔離,僅驗證該單元在給定輸入下是否產生預期輸出。
單元測試可以做到什麼?
有別於以往的瀑布式開發,現在很多公司慢慢走向更具彈性和更精簡的敏捷式開發(Agile),透過不斷地檢視和調整來確保品質和產出,因此單元測試在敏捷開發中,就更為重要了。
- 提早發現錯誤,節省修復成本
軟體開發中有個著名的「1-10-100法則」:在開發階段修復一個 bug 的成本是 1,在測試階段是 10,而在生產環境中則高達 100。單元測試能在開發的最早期就發現問題,大幅降低後續的維護成本。 - 加入新功能時驗證持續整合
當我們有新的需求要開發時,我們可以將測試整合到 CI/CD 中,在部署前運行這些單元測試來避免改 A 壞 B 的情況發生。 - 確保重構時的安全性
重構是程式碼在不停迭代後必定會經歷的過程,如果在重構前能夠有完整的單元測試,就能夠確保重構的過程沒有破壞原有功能。 - 作為文件
一個良好且完整的單元測試就是最好的文件,新加入的團隊成員可以透過測試案例、預期結果和邊界條件就能夠快速了解各個函式或 API 的作用。
怎樣才是一個好的單元測試?

- 簡單易讀
好的單元測試需要易於理解和維護,一個較常使用的較常使用的方法是 AAA 原則 (AAA Pattern),它包含:- Arrange(準備):設置測試環境和測試數據等
- Act(執行):調用待測試的功能,也就是呼叫 API(通常是 1~2 行可以完成)
- Assert(斷言):驗證執行結果是否符合預期
- 每個測試只測試一個場景
單一測試應該專注於驗證單一功能或單一場景,例如test_auth_fail_with_invalid_email,就不會包含驗證密碼錯誤的場景,這樣做的好處是:- 測試失敗時,能更容易找到問題
- 測試目的更加明確
- 不依賴其他函式
上述提到,單元測試的核心就是「隔離」,所以需要避免和其他函式或物件相依賴,我們可以使用使用測試替身(Test Double)如模擬對 (Mock),來實現這一點。 - 避免測試互相依賴
除了不依賴函式,測試和測試間也不能夠互相依賴,如果一個測試失敗會造成其他測試也失敗,那就不是一個正確的單元測試。這時我們可以使用setup和teardown來確保每個測試的開始環境都是乾淨的。 - 避免外部 API 調用
像是 Google、AWS 這些外部 API 會讓我們的測試產生變數,如果回應時間不穩定會導致測試速度忽快忽慢,或是參數時常需要調整也會影響整體的測試結果。 - 具有可重複運行和可擴展性
可重複運行是建立在每次執行測試都會得到同個結果的前提上,可擴展性我們則可以使用 Test Suite 或 Test Classes 來達成,如下,我們可以盡量讓同一支 API 的測試都集中在一個 Class 中,方便其他案例的增加。
from app.auth import auth_account
class TestAuth:
def test_authentication_fails_with_invalid_username():
assert auth_account("invalid_user", "valid_pass") == False
def test_authentication_fails_with_invalid_password():
assert auth_account("valid_user", "invalid_pass") == False結語
單元測試應該在開發時就納入時數評估,但因為在 PM 看來短期內很難看到效益又要多花時間,所以很容易被低估,但從長遠看,它能夠幫助我們節省更多除錯和維護的時間,同時提升代碼質量和團隊協作效率。

