前言
HTTP 是廣受使用的傳輸協定之一,其為客戶端 (Client) 與伺服器 (Server) 通訊訂定一個 Request/Response 標準,可以輕易地使用瀏覽器、爬蟲工具等遵守 HTTP 協定的 Client 來對 Server 發出請求以取得資料。在程式開發中,透過 HTTP 呼叫第三方 Server 的 API 是常見的一個行為,例如:LINE Notify、Firebase 等服務,背後皆是使用 HTTP 與其 Server 交換資料。為了確保邏輯正確性且提高代碼品質,正確撰寫 HTTP Client 的測試就變得格外重要。
環境
- go version go1.19.3
測試手段的選擇
正確選擇測試手段是非常重要的,好的測試方法會 帶我們飛 帶來好維護的測試代碼。
Integration Test
每次測試皆對 Server 發出真實請求以取得真實回應,想法直接但有幾個值得思考的問題
- 若真的對 Server 發出請求,要怎麼確保每次 Server 的回覆都固定不變,讓我們的測試可以順利通過?
- 若測試時 Server 出現內部問題 (例如:關機維護),此時測試不會通過,但不代表我們的代碼有錯。
- 若是使用以呼叫次數計費的 API,代表每次運行測試都有一定的金錢成本在。
這些問題的根本原因都是一樣的,是因為 Integration Test 依賴於真實 Server,若我們可以讓測試不依賴真實 Server,則這些問題就不會存在,Mock HTTP Server 為這些問題的一種解決方案。
Unit Test by Mock HTTP Server
某種程度上可說這種方法是 Integration Test,但測試本身不受外部依賴的運作行為干擾,因為現在依賴的是 Mock HTTP Server,使用完全自己假造的 HTTP Server 取代真實 Server 來進行測試,這樣我們可以確保
- 每次請求都會以我們事先撰寫內容作為回應,這個內容是永恆不變的。
- Mock HTTP Server 不會有內部問題,因此不影響測試結果。
- API Server 是我們假造的,所以也免除了費用問題。
綜合上述,Unit Test by Mock HTTP Server 將會是我們採用的測試手段!
情境說明
API Server (http://example.com
) 提供兩個端點 (Endpoints) 讓我們可以取得或操作使用者相關資訊。
GET /api/v1/users
取得使用者資訊請求成功:回傳 200 OK 狀態碼與所有使用者的名稱
1
2
3
4
5[
"Jack",
"Marry",
"Sandy"
]請求失敗:回傳 4xx 或 5xx 狀態碼並帶著錯誤訊息
1
2
3{
"msg": "something wrong!"
}
POST /api/v1/user
創建使用者請求格式:
Content-Type
需為application/json
,且 playload 格式如下1
2
3{
"name": "Jack"
}請求成功:回傳 201 Created 狀態碼與該使用者相關資訊
1
2
3
4{
"id": "ABC-111",
"name": "Jack"
}請求失敗:回傳 4xx 或 5xx 狀態碼並帶著錯誤訊息
1
2
3{
"msg": "something wrong!"
}
這是虛構出來的情境,example.com 是為製作教學文件提供方便而存在的網站,我們可以真的去呼叫 http://example.com/api/v1/users
,但他的回應不會如我們預期。
HTTP Client 撰寫與測試
Source Code 已上傳於 GitHub,歡迎自行 Clone/Fork 來運行!以下摘錄重要的部分,每一行代碼的作用皆詳細註解在程式碼中,請耐心閱讀、吸收並化為自身內力。
取得使用者資訊
1 | type errorResponse struct { |
1 | func TestGetUsers(t *testing.T) { |
創建使用者
1 | type CreateUserRequest struct { |
1 | func TestCreateUser(t *testing.T) { |
這篇文章記載如何有效的測試 HTTP Client,但這個方法只限用於透過 TCP 傳輸的 HTTP Client,若想知道如何測試 Unix Domain Socket based HTTP Client,可以參考 [Golang] Unit Testing of HTTP Client using Unix Domain Socket!