【pytest 教學】#01 為什麼要寫測試?pytest 能幫你什麼?

測驗:為什麼要寫測試?pytest 能幫你什麼?

共 5 題,點選答案後會立即顯示結果

1. 自動化測試相較於手動測試,最主要的優勢是什麼?

  • A. 可以完全取代人工判斷
  • B. 只需要執行一次就夠了
  • C. 可重複執行,不會遺漏測試案例
  • D. 執行時不需要任何設定

2. 文章提到測試的三個核心好處,下列何者不是其中之一?

  • A. 信心:改完程式碼後能快速確認功能正常
  • B. 文件:測試程式碼展示函式的使用範例
  • C. 重構保護:改善程式碼結構後能驗證功能沒壞
  • D. 效能優化:測試能自動讓程式跑得更快

3. 在測試金字塔中,哪種測試類型數量最多且執行最快?

  • A. 單元測試
  • B. 整合測試
  • C. 端對端測試
  • D. 效能測試

4. pytest 相較於 Python 內建的 unittest,主要優勢是什麼?

  • A. pytest 是 Python 內建模組,不需安裝
  • B. pytest 語法更簡潔,直接使用 assert 即可
  • C. pytest 只能測試 Python 3 的程式碼
  • D. pytest 必須繼承特定類別才能撰寫測試

5. pytest 的命名慣例中,測試檔案和函式的命名規則為何?

  • A. 檔案用 spec_ 開頭,函式用 it_ 開頭
  • B. 檔案和函式都用 check_ 開頭
  • C. 檔案用 test_ 開頭或 _test 結尾,函式用 test_ 開頭
  • D. 沒有特定規則,任何命名都可以

一句話說明

pytest 是 Python 的測試框架,讓你自動驗證程式碼是否正確運作。


為什麼需要自動化測試?

手動測試的痛點

想像你寫了一個計算折扣的函式:

def calculate_discount(price, discount_rate):
    return price * (1 - discount_rate)
Code language: JavaScript (javascript)

每次改動後,你可能會這樣手動測試:

# 在 Python 互動模式輸入
>>> calculate_discount(100, 0.1)
90.0  # 看起來對!
Code language: CSS (css)

問題來了

  • 每次改動都要手動跑一遍
  • 改了 A 功能,忘記測 B 功能
  • 三天後你忘了要測哪些情況
  • 其他人改你的程式碼,不知道要測什麼

自動化測試就是:把這些「手動驗證」寫成程式碼,讓電腦自動幫你跑。


測試的價值:三個核心好處

1. 信心

改完程式碼後,一行指令就知道有沒有弄壞東西:

pytest  # 0.5 秒後告訴你:全部通過 / 哪裡壞了
Code language: PHP (php)

2. 文件

測試程式碼就是「這個函式怎麼用」的範例:

def test_calculate_discount():
    assert calculate_discount(100, 0.1) == 90.0   # 100 元打 9 折 = 90 元
    assert calculate_discount(200, 0.25) == 150.0 # 200 元打 75 折 = 150 元
Code language: PHP (php)

翻譯:「看測試就知道函式該怎麼用」

3. 重構保護

想改善程式碼結構?改完跑測試,通過就代表功能沒壞。


測試金字塔:三種測試層級

AI 在寫測試時,你可能會看到不同類型的測試。這裡簡單介紹:

        /\
       /  \     端對端測試(E2E)
      /----\    整個系統跑一遍,最慢但最真實
     /      \
    /--------\  整合測試
   /          \ 多個元件一起測,中等速度
  /------------\
 /              \  單元測試
/________________\ 單一函式,最快最多
測試類型 測什麼 範例
單元測試 單一函式 calculate_discount() 計算正確嗎
整合測試 多個元件 資料庫 + API 一起運作正常嗎
端對端測試 整個流程 使用者從登入到結帳都順利嗎

Vibe Coder 重點:大多數時候你看到的會是「單元測試」,也就是測單一函式的那種。


pytest vs unittest:為什麼選 pytest?

Python 內建了 unittest 測試框架,但 pytest 更受歡迎。看看差異:

unittest 寫法(Python 內建)

import unittest

class TestDiscount(unittest.TestCase):
    def test_basic_discount(self):
        self.assertEqual(calculate_discount(100, 0.1), 90.0)
Code language: CSS (css)

pytest 寫法

def test_basic_discount():
    assert calculate_discount(100, 0.1) == 90.0

差異一目瞭然

比較 unittest pytest
需要寫 class 不用
斷言語法 self.assertEqual(a, b) assert a == b
錯誤訊息 基本 超詳細
插件生態 超多

pytest 勝出的原因

  • 語法簡潔,少寫很多樣板程式碼
  • assert 就是 Python 原生語法,不用學新東西
  • 錯誤訊息更清楚,直接告訴你哪裡不對

安裝 pytest

pip install pytest

或用 uv(更快):

uv pip install pytest

確認安裝成功:

pytest --version
# pytest 8.x.x
Code language: CSS (css)

撰寫並執行第一個測試

步驟 1:建立測試檔案

建立 test_discount.py

# test_discount.py

def calculate_discount(price, discount_rate):
    """計算折扣後價格"""
    return price * (1 - discount_rate)

def test_basic_discount():
    """測試基本折扣計算"""
    assert calculate_discount(100, 0.1) == 90.0

def test_no_discount():
    """測試沒有折扣的情況"""
    assert calculate_discount(100, 0) == 100.0

def test_full_discount():
    """測試全額折扣"""
    assert calculate_discount(100, 1) == 0.0
Code language: PHP (php)

步驟 2:執行測試

pytest test_discount.py
Code language: CSS (css)

輸出結果

========================= test session starts ==========================
collected 3 items

test_discount.py ...                                              [100%]

========================== 3 passed in 0.01s ===========================

翻譯

  • collected 3 items:找到 3 個測試
  • ...:三個點 = 三個測試都通過
  • 3 passed:全部通過

步驟 3:看看測試失敗長什麼樣

故意寫一個會失敗的測試:

def test_wrong_expectation():
    assert calculate_discount(100, 0.1) == 80.0  # 故意寫錯
Code language: PHP (php)

執行結果:

========================= FAILURES =========================
_________________ test_wrong_expectation __________________

    def test_wrong_expectation():
>       assert calculate_discount(100, 0.1) == 80.0
E       assert 90.0 == 80.0

test_discount.py:17: AssertionError
==================== 1 failed, 3 passed ====================

pytest 的錯誤訊息超清楚

  • 告訴你哪一行失敗
  • 顯示實際值 90.0 vs 預期值 80.0

pytest 命名慣例

pytest 會自動找到測試,但你要遵守命名規則:

項目 規則 範例
檔案名稱 test_ 開頭或 _test 結尾 test_discount.py, discount_test.py
函式名稱 test_ 開頭 def test_basic_discount():
類別名稱(可選) Test 開頭 class TestDiscount:

一句話:檔案和函式都用 test_ 開頭就對了。


Vibe Coder 檢查點

看到 AI 寫的測試時確認:

  • [ ] 測試檔案是 test_ 開頭嗎?
  • [ ] 測試函式是 test_ 開頭嗎?
  • [ ] 有用 assert 檢查結果嗎?
  • [ ] 測試案例涵蓋正常情況和邊界情況嗎?
  • [ ] 能用 pytest 指令執行嗎?

常見 pytest 輸出翻譯

符號 意思
. 測試通過
F 測試失敗(Fail)
E 測試出錯(Error,程式碼有 bug)
s 測試跳過(Skip)
x 預期失敗且真的失敗(xfail)

本篇重點整理

  1. 手動測試痛點:容易遺漏、難以重複、沒有文件
  2. 自動化測試價值:信心、文件、重構保護
  3. 測試金字塔:單元測試最多最快,端對端測試最少最慢
  4. pytest 優勢:語法簡潔、assert 直覺、錯誤訊息清楚
  5. 基本使用pip install pytest + 寫 test_ 開頭的函式 + 執行 pytest

下一篇預告

下一篇我們會學習 pytest 的核心功能:assert 的各種用法,以及如何組織測試檔案結構。

進階測驗:為什麼要寫測試?pytest 能幫你什麼?

測驗目標:驗證你是否能在實際情境中應用所學。
共 5 題,包含情境題與錯誤診斷題。

1. 你剛接手一個同事的專案,想了解 calculate_total() 函式應該怎麼使用。專案中有完整的測試檔案。最有效率的做法是什麼? 情境題

  • A. 直接閱讀函式的原始碼來理解
  • B. 查看測試程式碼,因為測試就是函式的使用範例
  • C. 在 Python 互動模式中隨意嘗試不同參數
  • D. 等同事回來再問他

2. 你正在重構一個計算運費的函式,想改善程式碼結構但不改變功能。在重構前,你應該先做什麼? 情境題

  • A. 先備份整個專案資料夾
  • B. 直接開始重構,有問題再說
  • C. 確認現有測試涵蓋主要情況,重構後執行測試驗證功能沒壞
  • D. 先刪除舊的測試,重構後再寫新測試

3. 團隊決定導入自動化測試,你需要為一個電商網站選擇測試策略。根據測試金字塔原則,應該如何分配測試數量? 情境題

  • A. 以端對端測試為主,涵蓋完整購物流程最重要
  • B. 三種測試平均分配,各佔三分之一
  • C. 只需要整合測試,單元測試和端對端測試都不需要
  • D. 單元測試最多,整合測試次之,端對端測試最少

4. 小明建立了以下測試檔案,但執行 pytest 時完全沒有找到任何測試。問題最可能出在哪裡? 錯誤診斷

# 檔案名稱:discount_check.py def check_basic_discount(): assert calculate_discount(100, 0.1) == 90.0 def check_no_discount(): assert calculate_discount(100, 0) == 100.0
  • A. 缺少 import pytest 語句
  • B. 檔案名稱和函式名稱都沒有用 test_ 開頭
  • C. 沒有使用 self.assertEqual() 方法
  • D. 測試函式沒有包在 class 裡面

5. 執行 pytest 後看到以下輸出,這代表什麼意思? 錯誤診斷

test_discount.py .F.s ==================== 1 failed, 2 passed, 1 skipped ====================
  • A. 所有測試都失敗了
  • B. 有 4 個測試通過,輸出的是測試名稱縮寫
  • C. 共 4 個測試:2 個通過、1 個失敗、1 個跳過
  • D. 測試檔案有語法錯誤,無法正常執行

發佈留言

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