淺談單元測試的重要性

前言

在軟體開發的世界裡,我們常常聽到「測試驅動開發」、「單元測試」等術語。對於剛進入軟體開發的新手來說,這些概念可能比較抽象,所以今天就來聊聊單元測試的重要性吧!

什麼是單元測試?

Unit tests help you to catch bugs early and document your code’s behavior.

單元測試是針對程式中最小可測試單位(通常是一個函數或方法)進行的自動化測試。它的核心理念是隔離:將被測試的單元與其他部分隔離,僅驗證該單元在給定輸入下是否產生預期輸出。

單元測試可以做到什麼?

有別於以往的瀑布式開發,現在很多公司慢慢走向更具彈性和更精簡的敏捷式開發(Agile),透過不斷地檢視和調整來確保品質和產出,因此單元測試在敏捷開發中,就更為重要了。

  1. 提早發現錯誤,節省修復成本
    軟體開發中有個著名的「1-10-100法則」:在開發階段修復一個 bug 的成本是 1,在測試階段是 10,而在生產環境中則高達 100。單元測試能在開發的最早期就發現問題,大幅降低後續的維護成本。
  2. 加入新功能時驗證持續整合
    當我們有新的需求要開發時,我們可以將測試整合到 CI/CD 中,在部署前運行這些單元測試來避免改 A 壞 B 的情況發生。
  3. 確保重構時的安全性
    重構是程式碼在不停迭代後必定會經歷的過程,如果在重構前能夠有完整的單元測試,就能夠確保重構的過程沒有破壞原有功能。
  4. 作為文件
    一個良好且完整的單元測試就是最好的文件,新加入的團隊成員可以透過測試案例、預期結果和邊界條件就能夠快速了解各個函式或 API 的作用。

怎樣才是一個好的單元測試?

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

延伸閱讀

👉 【軟體測試實務】QA 工程師的三種歸屬:獨立、依附,還是左右為難?

👉 【軟體測試實務】從「你怎麼沒測到?」談 QA 的價值與責任歸屬