介紹 pytest:Python 測試框架的強大選擇

更新日期: 2025 年 2 月 13 日

在現代軟體開發中,測試是確保程式正確性、穩定性與可維護性的關鍵步驟。

Python 作為一個廣泛使用的語言,擁有多種測試框架,其中 pytest 是最受歡迎的一款。

pytest 以其簡潔的語法、強大的功能及靈活的擴展性,使開發者能夠更輕鬆地編寫與執行測試。

本篇文章將詳細介紹 pytest,包含其 安裝方式、基本用法、進階功能、API 測試方法,讓你能夠快速上手並提升測試效率。


什麼是 pytest?

pytest 是 Python 生態系統中最流行的測試框架之一,主要用來執行 單元測試(Unit Test)功能測試(Functional Test)

相較於 Python 內建的 unittestpytest 提供了更簡單的語法、強大的測試工具,以及更好的擴展性,讓測試變得更加直覺且高效。

pytest 的主要特色

  1. 簡潔直覺的語法:可以用函式來定義測試,而無需繼承測試類別。
  2. 自動測試發現pytest 會自動尋找符合規範的測試函式,無需手動註冊。
  3. 強大的 fixture 機制:可以輕鬆設定與清理測試環境。
  4. 內建參數化測試:能夠輕鬆測試多組輸入輸出組合,減少重複程式碼。
  5. 詳細的錯誤報告:當測試失敗時,pytest 會提供清楚的錯誤訊息與變數值,方便除錯。
  6. 豐富的外掛系統:支援許多擴充套件,如 pytest-djangopytest-cov(測試覆蓋率分析)等。

安裝 pytest

pytest 可以透過 pip 安裝,執行以下指令:

pip install pytest

安裝完成後,透過以下指令確認 pytest 是否安裝成功:

pytest --version

如果成功安裝,會顯示 pytest 的版本資訊。


基本用法

撰寫第一個測試

pytest 允許我們用簡單的函式來定義測試,不需要建立類別。以下是一個基本測試範例:

# test_sample.py

def test_addition():
    assert 1 + 1 == 2

def test_subtraction():
    assert 5 - 3 == 2

然後,在終端機執行 pytest

pytest

pytest 會自動尋找所有 test_ 開頭的函式並執行測試,並顯示測試結果:

============================= test session starts =============================
collected 2 items

test_sample.py ..                                                    [100%]

============================== 2 passed in 0.12s ==============================

..」 表示兩個測試通過,整體測試成功。

assert 是什麼?

assert 是 Python 的內建關鍵字,用來進行「斷言(Assertion)」,通常用於測試或除錯,確保某個條件為 True,否則會拋出 AssertionError 並終止程式。

pytest 測試中,assert 是最常見的語法,因為 pytest 會自動擷取 AssertionError,並顯示詳細的錯誤訊息,幫助開發者快速定位問題。

assert 的基本用法

語法:

assert 條件表達式, "錯誤訊息(可選)"

如果 條件表達式True,程式會繼續執行;如果為 False,則會拋出 AssertionError,並可選擇性顯示錯誤訊息。

範例 1:簡單的 assert

assert 1 + 1 == 2  # 正確,不會報錯
assert 2 * 2 == 5  # 這行會拋出 AssertionError

執行後:

Traceback (most recent call last):
  File "<stdin>", line 2, in <module>
AssertionError

範例 2:帶錯誤訊息的 assert

assert 10 > 5, "10 應該要大於 5"  # 正確,不會報錯
assert 3 > 5, "3 不可能大於 5"  # 這行會拋出 AssertionError,並顯示錯誤訊息

執行後:

Traceback (most recent call last):
  File "<stdin>", line 2, in <module>
AssertionError: 3 不可能大於 5

assertpytest 測試中的應用

pytest 測試中,assert 主要用來檢查函式回傳值是否符合預期。

範例 3:基本測試

def add(a, b):
    return a + b

def test_add():
    assert add(2, 3) == 5  # 測試通過
    assert add(1, -1) == 0  # 測試通過

執行 pytest

test_sample.py ..  [100%]  # 代表 2 個測試通過

assertif 的差異

比較項目assertif 判斷
用途驗證條件是否成立,若 False 則拋出錯誤用於流程控制,不會自動拋錯
是否影響程式執行會終止程式並拋出 AssertionError不會終止程式,除非手動拋錯
使用時機通常用於測試或除錯用於一般程式邏輯

範例 4:assert vs if

x = 10

# 使用 assert
assert x > 5, "x 應該要大於 5"  # 如果 x <= 5,程式會拋出錯誤並終止

# 使用 if
if x <= 5:
    print("錯誤:x 應該要大於 5")  # 只是輸出訊息,程式仍然繼續執行

進階功能

使用 Fixture 設定測試環境

什麼是 Fixture?

在測試中,我們經常需要 準備測試數據初始化一些資源,例如:

  • 建立 測試資料(像是模擬的使用者、商品清單等)。
  • 建立 資料庫連線打開檔案
  • 設定 API 的模擬回應(Mock API)。
  • 在測試結束後進行清理(像是關閉資料庫連線、刪除測試檔案等)。

pytest 中,Fixture 是一種特殊的函式,它可以在測試前執行 初始化,並在測試後執行 清理工作,確保測試環境乾淨且穩定。

Fixture 的基本用法

假設我們要測試一個函式 sum(),但每次測試都需要準備一組相同的數據 [1, 2, 3, 4, 5]

如果不使用 Fixture,我們的測試可能會這樣寫:

def test_sum():
    sample_data = [1, 2, 3, 4, 5]  # 手動建立測試數據
    assert sum(sample_data) == 15

這種寫法沒問題,但如果 多個測試都需要相同的數據,我們就需要 重複寫很多次,這樣不夠優雅。

使用 Fixture 來優化測試

import pytest

# 定義一個 fixture,準備測試數據
@pytest.fixture
def sample_data():
    return [1, 2, 3, 4, 5]

# 測試函式會自動使用 sample_data
def test_sum(sample_data):
    assert sum(sample_data) == 15

執行步驟解析

  1. pytest 會發現 @pytest.fixture 裝飾的 sample_data(),並視為 測試用的前置準備
  2. pytest 執行 test_sum(sample_data) 時,它會自動 執行 sample_data(),並把結果傳給 test_sum()
  3. test_sum() 接收 sample_data,然後執行 assert sum(sample_data) == 15,檢查結果是否正確。

這樣的好處是:

可重複使用:其他測試函式也可以使用 sample_data,不用每次都手動建立數據。
可擴展:我們可以進一步修改 Fixture,例如在測試結束後執行清理工作(像是關閉資料庫連線)。

Fixture 的進階用法

如果我們需要在測試開始前準備資料,測試結束後清理資料,可以這樣做:

import pytest

@pytest.fixture
def setup_and_cleanup():
    print("=== 測試開始前:初始化 ===")
    data = {"name": "Alice", "age": 25}  # 準備測試數據
    yield data  # yield 之前的程式碼在測試前執行
    print("=== 測試結束後:清理 ===")  # yield 之後的程式碼在測試後執行

def test_user_data(setup_and_cleanup):
    user = setup_and_cleanup  # 取得 fixture 回傳的測試數據
    assert user["name"] == "Alice"
    assert user["age"] == 25

執行結果:

=== 測試開始前:初始化 ===
.
=== 測試結束後:清理 ===

yield 之前的部分 在測試開始前執行yield 之後的部分 在測試結束後執行,這樣我們可以在測試後執行清理工作。

參數化測試(Parameterized Testing)

什麼是參數化測試?

有時候,我們需要針對「不同輸入數據」測試相同的函式。例如,我們希望測試 add(a, b) 的行為:

def add(a, b):
    return a + b

我們可能會這樣測試:

def test_add():
    assert add(1, 2) == 3
    assert add(5, 5) == 10
    assert add(10, -1) == 9

雖然這樣也可以,但這些 assert 都寫在同一個函式內,當其中一個測試失敗時,後面的測試不會執行,而且這樣的寫法也不夠靈活。

使用 @pytest.mark.parametrize 來簡化測試

pytest 提供 參數化測試(Parameterized Testing),讓我們可以用 一個測試函式 測試 多組輸入數據

import pytest

@pytest.mark.parametrize("a, b, expected", [
    (1, 2, 3),   # 測試 add(1, 2) 是否等於 3
    (5, 5, 10),  # 測試 add(5, 5) 是否等於 10
    (10, -1, 9)  # 測試 add(10, -1) 是否等於 9
])
def test_add(a, b, expected):
    assert add(a, b) == expected

執行步驟解析

  1. pytest.mark.parametrize("a, b, expected", [...]) 定義了 多組測試數據,每組數據有 三個參數(a, b, expected)
  2. pytest 會針對 每組數據 執行 test_add(),傳入對應的 a, b, expected
  3. 測試時,assert add(a, b) == expected 會對比函式輸出與期望值,確保正確性。

測試輸出結果

test_sample.py ...  [100%]

... 表示 3 個測試全部通過。

如果某個測試失敗呢?

假設 add(10, -1) 回傳了 8(應該是 9),pytest 會清楚顯示錯誤:

E       assert 8 == 9
E        +  where 8 = add(10, -1)

這樣我們能 快速定位問題,不需要手動檢查多個 assert


測試 API(搭配 httpx

除了單元測試,pytest 也能用來測試 API,這對於開發 RESTful API 的專案非常有幫助。

我們可以使用 httpx 來發送 HTTP 請求,然後檢查 API 是否回傳正確的結果。

安裝 httpx

pip install httpx

API 測試範例

假設我們有一個 API,提供 /users/1 端點,回傳特定使用者的資料:

import httpx

def test_get_user():
    response = httpx.get("https://jsonplaceholder.typicode.com/users/1")
    assert response.status_code == 200
    assert response.json()["id"] == 1
    assert response.json()["name"] == "Leanne Graham"

這段測試程式碼會發送 GET 請求到 https://jsonplaceholder.typicode.com/users/1,然後檢查:

  • HTTP 狀態碼是否為 200(請求成功)。
  • 回傳的 JSON 資料是否包含 id=1name="Leanne Graham"

模擬 API 回應?

在測試 API 時,我們通常會向某個伺服器發送 HTTP 請求,但在某些情況下,我們 不希望真的發送請求,例如:

  • 伺服器還沒開發完成:前端或其他模組需要測試,但 API 還未實作。
  • 避免影響正式環境:測試時如果真的發送請求,可能會對正式資料產生影響(例如新增資料、刪除用戶等)。
  • 提高測試速度:如果每次測試都要連線到遠端伺服器,會導致測試變慢。
  • 測試錯誤處理機制:我們可以模擬伺服器回應錯誤(例如 500 Internal Server Error),看看程式是否能正確處理異常。

為了解決這些問題,我們可以 「模擬 API 回應」,讓 httpx.get()httpx.post() 不會真的發送 HTTP 請求,而是直接回傳一個 假裝來自伺服器的回應

使用 pytest-httpx 來模擬 API 回應

pytest-httpx 是一個 pytest 的外掛,可以幫助我們 攔截 HTTP 請求並回傳模擬的回應,這樣測試時就不會真的發送請求。

步驟 1:安裝 pytest-httpx

首先,你需要安裝 pytest-httpx

pip install pytest-httpx

這個外掛可以幫助我們模擬 HTTP 回應,而不是真的發送請求。

範例:模擬 API 回應

假設我們有一個 API,提供 /data 端點,會回傳以下 JSON:

{
    "message": "success"
}

我們的程式會透過 httpx.get("https://api.example.com/data") 來取得這個資料。

但是,我們不想真的發送請求,而是要模擬這個 API 回應,這時可以用 pytest-httpx 來攔截請求並回傳假數據。

步驟 2:建立測試程式
import httpx
import pytest

@pytest.fixture
def mock_httpx_response(httpx_mock):
    # 使用 httpx_mock 來攔截請求,並模擬回應
    httpx_mock.add_response(
        url="https://api.example.com/data",  # 指定要攔截的 API URL
        json={"message": "success"},  # 模擬 API 回傳的 JSON 資料
        status_code=200  # 設定 HTTP 狀態碼為 200(成功)
    )

def test_api(mock_httpx_response):
    # 這裡的 httpx.get() 不會真的發送 HTTP 請求,而是直接回傳模擬的回應
    response = httpx.get("https://api.example.com/data")
    
    # 驗證回應的狀態碼是否正確
    assert response.status_code == 200

    # 驗證回應的 JSON 內容是否正確
    assert response.json() == {"message": "success"}

這段程式碼在做什麼?

1. httpx_mock.add_response() 攔截請求並回應模擬數據
  • httpx_mock.add_response()攔截所有發送到 https://api.example.com/data 的請求,並回傳設定好的模擬數據。
  • json={"message": "success"} 指定 回傳的 JSON 內容
  • status_code=200 指定 回傳的 HTTP 狀態碼
2. test_api() 測試函式
  • httpx.get("https://api.example.com/data") 被執行時,httpx_mock攔截這個請求,不會真的發送到網路上,而是直接回傳 {"message": "success"}
  • assert response.status_code == 200 確保狀態碼正確。
  • assert response.json() == {"message": "success"} 確保回應內容正確。

範例:模擬不同的 API 回應

有時候,我們想測試 API 發生錯誤的情況(例如 404 Not Found 或 500 伺服器錯誤),這時也可以透過 httpx_mock.add_response() 來模擬不同的錯誤回應。

1. 模擬 404 Not Found
@pytest.fixture
def mock_404_response(httpx_mock):
    httpx_mock.add_response(
        url="https://api.example.com/notfound",
        status_code=404,
        json={"error": "Not Found"}
    )

def test_api_404(mock_404_response):
    response = httpx.get("https://api.example.com/notfound")
    assert response.status_code == 404
    assert response.json() == {"error": "Not Found"}

這樣 httpx.get("https://api.example.com/notfound") 不會真的發送請求,而是直接回傳 404 錯誤。

2. 模擬 500 Internal Server Error
@pytest.fixture
def mock_500_response(httpx_mock):
    httpx_mock.add_response(
        url="https://api.example.com/server-error",
        status_code=500,
        json={"error": "Internal Server Error"}
    )

def test_api_500(mock_500_response):
    response = httpx.get("https://api.example.com/server-error")
    assert response.status_code == 500
    assert response.json() == {"error": "Internal Server Error"}

這樣我們可以測試當 API 發生 500 錯誤時,我們的程式是否能正確處理

這樣做有什麼好處?

  1. 測試不會影響真實伺服器
    • 我們 不會真的發送請求,這樣測試不會影響正式環境(例如不會誤刪資料)。
  2. 測試速度更快
    • 如果 API 需要 1 秒 才回應,測試時可能會拖慢速度。但用 pytest-httpx,回應是模擬的,測試可以在 毫秒內完成
  3. 可以測試 API 異常情境
    • 你可以 模擬 404、500 或其他錯誤,確保你的程式能正確處理 API 失敗的情況。

結論

pytest 是一款功能強大且靈活的 Python 測試框架,適用於 單元測試、功能測試及 API 測試
透過其 簡潔的語法、自動測試發現、fixture 機制、參數化測試

pytest 大幅提升測試的易用性與可維護性,讓開發者能更輕鬆地確保程式的品質與穩定性。

此外,搭配 httpxpytest-httpxpytest 也能用來測試 API,確保你的後端服務能夠正常運作。

希望這篇文章能幫助你更好地理解 pytest,讓你的測試工作更加順利! 🚀

Similar Posts