Vibe Coder

測試基礎:用 pytest 讓你敢放心改程式碼

測試是讓你敢放心改程式碼的底氣。用 pytest 寫單元測試、用 Mock 隔離外部 API 呼叫、再用 FastAPI 的 TestClient 測試端點,五秒就能跑完所有測試,不用每次改完都手動點五分鐘,也不會漏掉任何一個情境。
測試基礎:用 pytest 讓你敢放心改程式碼

測試基礎:用 pytest 讓你敢放心改程式碼

沒有測試,每次改功能都可能不小心壞掉另一個地方,而你不會知道,直到使用者回報。這篇帶你用 pytest 寫出第一批測試,學會用 Mock 隔離外部 API,再用 FastAPI 的 TestClient 直接測試端點行為,讓 AI 幫你重構程式碼的時候,你也能放心。

你將學到什麼

為什麼要寫測試

一個簡單的思考框架:不想被叫起床修 bug,就要有測試。

pytest 快速上手

最簡單的測試長什麼樣,以及四個常用指令。

Mock 隔離外部依賴

不真的呼叫外部 API,測試才能快、穩定、不花錢。

測試策略:寫哪些測試

核心邏輯、API 端點、邊界情況,按優先度分配你的時間。

為什麼要寫測試

一個簡單的思考框架:

  • 不想被叫起床修 bug,就要有測試
  • 想讓 AI 放心地幫你重構,就要有測試
  • 想讓客戶相信你的交付品質,就要有測試
  • 想三個月後還能改自己的程式碼,就要有測試

pytest 快速上手

# tests/test_weather.py
import pytest
from app.weather import parse_weather_response

def test_parse_temperature_celsius():
    """溫度應該正確解析為攝氏"""
    response = {"main": {"temp": 301.15}, "weather": [{"description": "clear"}]}
    result = parse_weather_response(response)
    assert result["temperature"] == 28

def test_parse_missing_main_raises():
    """API 回傳缺少 main 欄位時應拋出 ValueError"""
    with pytest.raises(ValueError):
        parse_weather_response({})  # 缺少 main 欄位
pytest                        # 跑所有測試
pytest tests/test_weather.py  # 跑特定檔案
pytest -v                     # 顯示每個測試的名稱
pytest --cov=app              # 顯示覆蓋率

Mock:隔離外部依賴

測試不應該真的呼叫 OpenWeatherMap API(太慢、有費用、網路可能不通),用 Mock 替換外部呼叫。

from unittest.mock import patch, MagicMock

def test_get_weather_success(mocker):
    # 替換 httpx.get,讓它回傳假的 API 回應
    mock_response = MagicMock()
    mock_response.json.return_value = {
        "main": {"temp": 301.15},
        "weather": [{"description": "clear sky"}]
    }
    mocker.patch("httpx.get", return_value=mock_response)

    from app.weather import get_weather
    result = get_weather("Taipei")
    assert result["temperature"] == 28
    assert "description" in result

FastAPI 測試客戶端

from fastapi.testclient import TestClient
from app.main import app

client = TestClient(app)

def test_weather_endpoint_returns_200(mocker):
    # Mock 外部 API
    mocker.patch("app.weather.fetch_weather", return_value={
        "temperature": 28, "description": "晴天"
    })
    response = client.get("/weather/Taipei",
        headers={"Authorization": "Bearer test-token"})
    assert response.status_code == 200
    assert response.json()["temperature"] == 28

def test_weather_endpoint_requires_auth():
    # 沒有 token 應該回傳 401
    response = client.get("/weather/Taipei")
    assert response.status_code == 401

測試策略:寫哪些測試

優先度測試什麼理由
最高核心業務邏輯(天氣解析、認證)壞了影響最大
API 端點(正常回傳、認證、錯誤)客戶直接使用的介面
邊界情況(空值、異常輸入)最常出現 bug 的地方
第三方套件的行為不需要測試你沒寫的程式碼
最高 · 核心業務邏輯壞了影響最大高 · API 端點客戶直接使用的介面中 · 邊界情況最常出現 bug 的地方低 · 第三方套件不需要測試你沒寫的程式碼
測試投入的優先順序:核心邏輯優先,第三方套件不用測。
覆蓋率不是越高越好一百趴覆蓋率不代表沒有 bug,但零趴代表完全沒保護。合理目標是六到八成,把時間花在核心邏輯和 API 端點,而不是追求數字好看。

延伸學習

把這篇文章分享給需要的人FacebookLINEThreadsX

常見問答

專案很小,值得花時間寫測試嗎?
值得。手動測試每次點五分鐘,pytest 五秒跑完所有測試而且不會漏掉任何一個;更重要的是,讓 AI 重構或修改程式碼後,測試能告訴你它有沒有保留原有的行為。
測試要真的呼叫外部 API 嗎?
不應該。測試不應該真的呼叫像 OpenWeatherMap 這樣的外部 API,因為太慢、可能有費用、網路也可能不通,要用 Mock 替換外部呼叫,讓測試在沒有網路的環境下也能跑通。
覆蓋率要做到 100% 嗎?
不需要。100% 覆蓋率不代表沒有 bug,但 0% 代表完全沒保護,一般設定 60% 到 80% 是合理的目標,優先把資源放在核心業務邏輯和 API 端點上。
應該優先測試哪些東西?
優先度最高的是核心業務邏輯(例如天氣解析、認證),其次是 API 端點的正常回傳、認證、錯誤情況,再來是邊界情況;第三方套件的行為則不需要你自己測試。