本文帶大家入門 pytest 模組,開始為自己的 Python 程式碼撰寫測試。測試 Code 不僅可以確保既有功能,也經常可以提前捕捉許多潛在 Bug。一種常用測試手法稱為 Unittest,旨在為每個最小單元功能做全方面測試,以保證功能運作符合預期。
Python 內建就有一個 Unittest 模組可用,不過由於語法繁瑣,強烈推薦大家使用本文主角:pytest!
安裝
$ pip install pytest
測試目標
假設 main.py
中寫了一支 Function 以取得指定 JSON 檔案中的設定值。
import json
def get_config(file: str, key: str, default=None):
with open(file, "r") as fs:
settings = json.loads(fs)
return settings.get(key, default)
撰寫第一支簡單測試
在同目錄新增一個名為 test_main.py
的 Python 檔案。
test_
,是為了讓 pytest 自動辨識為測試對象。import main
def test_config():
a = main.get_config("data.json", "foo", 0)
assert a == 1 # pytest use built-in assert keyword to validate result
這裡有幾個重點:
- 將想要執行的測試 Function 名稱加上
test_
前綴。這樣 pytest 就能認得它。 - 使用 Python
assert
語法檢查程式運行結果。
跑測試
到終端機執行:
$ pytest
如果沒意外,我們會得到第一個 Fail!原因是測試檔案 data.json
不存在。
collected 1 item
test_main.py F [100%]
================================== FAILURES ===================================
_________________________________ test_config _________________________________
def test_config():
> a = main.get_config("data.json", "foo", 0)
test_main.py:4:
_ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _
file = 'data.json', key = 'foo', default = 0
def get_config(file: str, key: str, default=None):
> with open(file, "r") as fs:
E FileNotFoundError: [Errno 2] No such file or directory: 'data.json'
main.py:8: FileNotFoundError
=========================== short test summary info ===========================
FAILED test_main.py::test_config - FileNotFoundError: [Errno 2] No such file or directory: 'data.json'
============================== 1 failed in 0.04s ==============================
預備與收尾
當然,我們可以手動新增測試檔案 data.json
,但每次都要這樣做十分麻煩。又如果測試完成想要刪掉它,就可以用 pytest 提供的預備與收尾 Function 來解決這兩個問題!
跑每一支 Function 時做
setup_function
:執行每一個測試 Function 前執行teardown_function
:執行完每一個測試 Function 後執行
我們在 test_main.py
中添加以下內容:
import os
import json
def setup_function():
with open("data.json", "w") as fs:
json.dump({"foo": 1}, fs)
def teardown_function():
os.remove("data.json")
再跑一次測試,成功!
collected 1 item
test_main.py . [100%]
============================== 1 passed in 0.00s ==============================
跑每一個檔案時做
setup_module
:執行單一測試 Python 檔案中,第一個測試 Function 前執行teardown_module
:執行完單一測試 Python 檔案中,最後一個測試 Function 後執行
改用上面兩個寫一下:
setup_function
改成setup_module
teardown_function
改成teardown_module
再跑一次測試,依然成功!
執行順序說明
若 test_main.py
檔案中,有另一個測試 Function 假如 test_empty_key
。
def test_empty_key():
b = main.get_config("data.json", "bar", -1)
assert b == -1
執行順序就會如下:
- setup_module
- setup_function
- test_config
- teardown_function
- setup_function
- test_empty_key
- teardown_function
- setup_function
- teardown_module
資料夾整理
隨著專案功能逐漸增加,測試檔案也勢必越來越多,全部放在同一層目錄就會顯得臃腫。一種資料夾常見的擺法,就是分別收納至 src
及 tests
資料夾。
./
├── .pyproject.toml <-- 後面段落說明
├── src/
│ └── main.py
└── tests/
└── test_config.py
整理完再跑一次測試看看。發現這次 Fail 了!
collected 0 items / 1 error
=================================== ERRORS ====================================
_____________________ ERROR collecting tests/test_main.py _____________________
ImportError while importing test module '/.../tests/test_main.py'.
Hint: make sure your test modules/packages have valid Python names.
Traceback:
/.../3.11/lib/python3.11/importlib/__init__.py:126: in import_module
return _bootstrap._gcd_import(name[level:], package, level)
tests/test_main.py:1: in
import main
E ModuleNotFoundError: No module named 'main'
=========================== short test summary info ===========================
ERROR tests/test_main.py
!!!!!!!!!!!!!!!!!!! Interrupted: 1 error during collection !!!!!!!!!!!!!!!!!!!!
============================== 1 error in 0.06s ===============================
這是因為檔案移動會造成 pytest 找不到 main.py
檔案。因此我們要加入 pytest 設定,請他去尋找 src
資料夾。
做法是在根目錄新增一特定檔名 pyproject.toml
檔案,內容如下:
[tool.pytest.ini_options]
pythonpath = [
"src",
]
再跑一次測試,就又成功了!
作者挖坑區
若 Function 之間需要共用變數,或者單純想按分類整理測試 Function,也可以選用 Class 方式來撰寫。此外,pytest 還有許多其他強力功能,例如為測試 Function 提供自定義參數(Fixture)、撒測試參數組合(Parametrize)、Mock 掉特定函式等等。
有空會再陸續整理分享。