Python API 測試通常從哪裡開始,以及為什麼這樣沒問題

別讓任何人說服你放棄 requests 與 pytest。測試就是一般的 Python 程式碼,fixture 可以層層組合,parametrize 能乾淨地涵蓋邊界值,而且團隊裡每個人都看得懂。針對自家服務、範圍明確的測試套件,這仍然是絕佳的答案。

最後會演變成工作量的四件事

  • 工作階段。 token fixture 一直都很好用,直到它在測試跑到一半時過期——接著你就得寫更新邏輯,還要讓多個 worker 之間共用快取。

  • 擷取到的值。 一次呼叫拿到的識別碼,會餵給下一次呼叫。在相依關係還不寬的時候,fixture 應付得來;一旦這張圖變寬,你要維護的就變成 fixture 關係圖本身。

  • 執行順序。 pytest 不會依相依關係排序。團隊於是找上排序外掛,結果前面一個測試失敗,畫面上就滿是彼此無關的紅字。

第四件,是平行執行下的資料清理。只要用上 pytest-xdist,靠名稱樣式刪除就不再安全,teardown 必須確切知道每一次執行建立了哪些東西。

真實的代價

這些事單獨看都不難。湊在一起,就成了一套內部框架:一個沒人想碰的 conftest、一張只有一個人看得懂的 fixture 關係圖,以及一套變紅之後被隔離、而不是被修好的測試。這種結局夠常見,值得事先納入規劃。

專門打造的那一層負責處理什麼

不論你用哪一種工具,決定一套 API 測試能不能撐過第一年的,都是這四件事:會過期的工作階段、只在執行期間才存在的值、彼此相依的呼叫,以及沒人清理的資料。TestSprite 把它們分別稱為 Auto-Authentication、Dynamic Variables、Dependency Chains 與 Auto-Cleanup,而 API 測試文件 則說明了實際的運作方式。

測試覆蓋也來自另一個地方。比起只涵蓋某個人記得的那些端點,從規格產生、或對執行中的服務做一輪探索後產生的測試計畫,會包含那些從來不會有人手寫的錯誤路徑與邊界案例。

如果你希望測試留在自己的程式碼儲存庫裡

有些團隊希望測試套件放在儲存庫裡,跟其他程式碼一樣走審查流程,這是完全合理的偏好。CLI 支援以 Python 撰寫後端測試,每個測試各自宣告相依關係,teardown 也獨立成一個案例,因此相依關係是明確寫出來的,而不是隱含在層層巢狀的 fixture 裡。細節請見 CLI 程式碼儲存庫。

吞掉整個團隊的 conftest

一套規模已經超出自己負荷的 Python 測試,有個一眼就認得出來的形狀,而且全擠在同一個檔案裡。conftest 一開始只有一個 session fixture 和一個 base URL。接著多了 token fixture,然後為了不要每個測試都去取一次 token 而加了快取,再因為 xdist 的 worker 在快取上互相競爭而加了鎖。然後是各種 factory,接著為了讓 teardown 知道要刪掉什麼而加了註冊表,最後因為有些 factory 相依於其他 factory 而加上排序標記。

每一步單獨看都沒錯,結果卻是六百行只有一個人看得懂的程式碼。最明顯的徵兆是:有人說某個測試失敗了,而第一個問題居然是 conftest 有沒有動過。

這件事值得специально講清楚,因為從來沒有人做過「要蓋這套東西」的決定。它是一點一滴累積出來的。及早決定哪些部分你打算自己扛、哪些不想扛,代價遠低於到了第九個月才發現答案。

讓測試在程式碼變動的地方執行

只在發布前才跑的 Python 測試套件,抓到問題時都已經太晚。真正關鍵的改變,是讓它在每一個 pull request 上執行。

有兩條路可走,而選擇的重點其實在於由誰負責 pipeline。從儀表板連接 GitHub App 完全不需要改動你的儲存庫,因為它是針對你的建置本來就會產生的部署做出反應。而加上一個 GitHub Actions 步驟,則會把這項檢查放進儲存庫裡,讓它和建置流程的其他部分一樣接受審查。

TestSprite 幫你扛下的部分

把 pytest 測試套件變成基礎設施的那四件事,都由產品本身處理掉。Auto-Authentication 會依環境取得並更新工作階段。Dynamic Variables 會把某個回應裡的識別碼帶進下一個請求。Dependency Chains 依每個案例需要什麼、產生什麼來推導執行順序,因此一個案例失敗時,相依於它的案例會被略過,而不是跟著失敗。Auto-Cleanup 只移除這次執行所建立的東西,而這正是平行執行能夠安全進行的原因。

測試覆蓋也從另一個方向而來。API Discovery 會列出執行中的服務實際開放的端點,而自動產生的測試計畫會包含那些在衝刺尾聲永遠寫不出來的錯誤路徑與邊界值。

你得到的,是一套沒有人害怕去改的測試,也不會有只有一個人看得懂的 conftest。單元測試與邏輯測試繼續留給 pytest——在那裡它確實是最好的工具;這裡談的是「廣度」該放在哪裡。

我們該停用 pytest 嗎?

不用。團隊刻意寫下的測試繼續用它,尤其是單元與邏輯層級的測試。這裡討論的是大範圍的 API 測試覆蓋該放在哪裡。

那 httpx 和非同步呢?

併發寫起來更順手,但還是同樣那四個問題。工作階段、擷取到的值、執行順序與資料清理,是「測試有狀態服務」這件事本身的特性,跟用哪個 HTTP 用戶端無關。

測試資料該怎麼處理?

每個測試需要什麼就建立什麼,然後只移除那些,並與該次執行綁定。共用的預先建立資料看起來很方便,最後卻會變成測試只有在特定順序下才會通過的原因。

自動產生的測試覆蓋能和 pytest 並存嗎?

可以,而且這是常見的做法。廣度交給自動產生的測試,刻意指定的案例交給 pytest。

在這件事上,Python 還是個好選擇嗎?

是。限制不在語言,而在於你願意自己扛下多少測試基礎設施。

簡短版

在狀態出現之前,requests 與 pytest 都是對的。

Python API 測試的起步非常漂亮,最後卻會圍繞著工作階段、擷取到的值、執行順序與資料清理,長成一套內部框架。刻意指定的測試繼續交給 pytest,廣度與這四個狀態問題則交給專門打造的那一層。