從 unittest 切換到 pytest 之後,我最有感的不是哪個殺手級功能,而是不用再記一堆 assertXxx。
寫 assert result == expected 就好,pytest 自己知道怎麼把失敗訊息展開得一清二楚。
為什麼選 pytest 而不是 unittest
unittest 是標準庫,不需要安裝,但它有幾個地方讓人不舒服:
- 測試類別要繼承
TestCase,不能直接寫函式 - 斷言要用
self.assertEqual、self.assertIn、self.assertRaises……記不完 setUp/tearDown不夠靈活,scope 固定在 class 層級
pytest 三件事讓我回不去:
- assert 直接寫,失敗時自動展開變數值
- fixture 按需注入,scope 可以是 function / class / module / session
- parametrize 一個裝飾器搞定多組輸入
安裝
| |
最簡單的測試
| |
| |
失敗時的輸出長這樣:
| |
不用猜哪個值是哪個,pytest 自己展開。
Fixture:比 setUp 好用
unittest 的 setUp 每次測試前都執行,範圍固定是 class。
pytest 的 fixture 可以控制 scope,也可以跨模組共用:
| |
yield 前是 setup,yield 後是 teardown,清楚很多。
Scope 控制
| |
| scope | 生命週期 |
|---|---|
function | 預設,每次測試重建 |
class | 同 class 的測試共用 |
module | 同檔案共用 |
session | 整個測試 session 共用 |
我習慣把資料庫連線設成 session,每個 test function 用自己的 transaction 再 rollback,這樣跑全套測試不會慢到受不了。
conftest.py
fixture 放在 conftest.py 就能跨測試檔案用,不需要 import:
| |
| |
test_users.py 和 test_orders.py 都能直接用 admin_user,不需要 import。
parametrize:一次測多組輸入
| |
每一組輸入會跑成獨立的測試,失敗時直接告訴你哪組輸入出問題:
| |
我用這個測邊界條件最方便,把正常值、0、負數、極大值都列進去,一個函式搞定。
測例外
| |
如果要確認例外訊息:
| |
只跑部分測試
| |
--lf(last failed)是我最常用的,改完 bug 馬上只跑剛才失敗的測試,不用等全套。
常用選項
| |
開發階段我幾乎都加 -x,一次看一個失敗,不讓輸出淹沒畫面。
小結
pytest 跟 unittest 的差距不在功能,而在寫起來的舒適度。assert 直接寫、fixture 按需組合、parametrize 測多組輸入,這三個習慣建立起來之後,測試就變成很自然的事,不再是每次都要打開文件查 API 的負擔。
如果你的測試還需要大量假資料,可以搭配 polyfactory 根據 type hint 自動生成,不用手刻每一筆 fixture data。
