昨天我們開始 coding,寫了一個簡單的、包含基本功能的 RSS 閱讀器後端 API。我們在把程式上線前,先做一點測試吧。
別小看了測試,它就像是你在料理前先試吃食材的味道。想想看你會把沒試過味道的糖跟鹽就直接灑到湯裡嗎?
東西沒測就上線,就準備周末解 bug。
單元測試是一個程式開發過程中不可或缺的部分,它是測試金字塔的最底層。你可能以為測試應該丟給 QA 或是 TE 去做,但正好相反,單元測試是由開發者來完成的。這就像是畫家對自己每一筆劃都要求完美,而不是交給評論家來批評。
Go語言提供了一個內建的測試框架,就像一個貼心的朋友,不需要你去額外下載其他的工具。你只需要按照一定的命名規則,例如xxx_test.go
來命名你的測試檔案,然後運行 go test
就能執行測試。
其實在開始寫單元測試前,我們昨天的 code 其實是沒辦法(或著說是很難)拿用進行單元測試的,因為
too-simple-rss-reader
的測試點在這個專案裡,我們可以進行以下單元測試:
subscribeFeed
:測試無效的 JSON、無效的 RSS URL 和成功訂閱一個 RSS feed 的各種情況。listSubscribedFeeds
:測試無訂閱和有訂閱的兩種狀況下回傳的列表。deleteFeed
:測試刪除不存在和存在的 feed。markItemRead
:測試標記不存在和存在的 feed 或項目。由於要寫的測項太多,為了簡單起見我就只針對 subscribeFeed
來寫測項。
在我們開始寫單元測試之前,我們先來重構 subscribeFeed
函數。這次重構的目的是為了分離 HTTP 處理和 RSS 解析的邏輯,以便我們可以只專注於測試 RSS feed 的訂閱處理邏輯。
首先,讓我們創建一個新的函數 parseAndAddFeed
,該函數會接收一個 URL 和一個 Feed map,並負責將解析的 RSS 項目添加到這個 map 中。
// parseAndAddFeed 會解析 RSS 並加入到 feedMap
func parseAndAddFeed(url string) (Feed, error) {
fp := gofeed.NewParser()
parsedFeed, err := fp.ParseURL(url)
if err != nil {
return Feed{}, err
}
feed := Feed{URL: url, Read: false}
feed.Items = make([]FeedItem, len(parsedFeed.Items))
for i, item := range parsedFeed.Items {
feed.Items[i] = FeedItem{Title: item.Title, Link: item.Link, Read: false}
}
feedMapMutex.Lock()
feedMap[url] = feed
feedMapMutex.Unlock()
return feed, nil
}
然後我們重構 subscribeFeed()
:
func subscribeFeed(w http.ResponseWriter, r *http.Request) {
var feed Feed
err := json.NewDecoder(r.Body).Decode(&feed)
if err != nil {
http.Error(w, "Invalid JSON", http.StatusBadRequest)
return
}
newFeed, err := parseAndAddFeed(feed.URL)
if err != nil {
http.Error(w, "Invalid RSS URL", http.StatusBadRequest)
return
}
json.NewEncoder(w).Encode(newFeed)
}
接下來,我們來實作幾個單元測試:
http.StatusBadRequest
。http.StatusBadRequest
。在進行這些單元測試之前,請確保已經安裝了必要的 Go 測試工具和庫。然後,你可以創建一個名為 main_test.go
的新文件,並添加以下測試代碼。
package main
import (
"testing"
)
func TestParseAndAddFeed(t *testing.T) {
// 測試提供無效 RSS URL 的情況
t.Run("invalid RSS URL", func(t *testing.T) {
_, err := parseAndAddFeed("invalid_rss_url")
if err == nil {
t.Error("Expected an error for invalid RSS URL, got none")
}
})
// 測試成功訂閱一個 RSS feed
t.Run("valid RSS URL", func(t *testing.T) {
// 使用一個預先知道的,有效的 RSS URL 進行測試
validRSSURL := "your_valid_rss_url_here"
feed, err := parseAndAddFeed(validRSSURL)
if err != nil {
t.Errorf("Didn't expect an error, got %v", err)
}
if feed.URL != validRSSURL {
t.Errorf("Expected feed URL to be %s, got %s instead", validRSSURL, feed.URL)
}
if len(feed.Items) == 0 {
t.Errorf("Expected feed items to be populated, got an empty list")
}
})
}
當執行 go test
時,單元測試將會自動執行,幫助你確保 subscribeFeed()
函數的各個方面都達到預期。記得,在 commit 或 push code 前都應該執行單元測試,以保證功能沒有被改壞。
在命令行中,切換到你的 RSSReader
專案資料夾,然後執行:
go test
如果一切順利,會看到測試通過的訊息。
$ ... > go test
PASS
ok github.com/.../too-simple-rss-reader 1.818s
$ ... >
單元測試是確保程式碼品質的重要手段,尤其在多人開發大型專案時更是如此。希望今天的內容能幫助你了解如何在 Go 中進行單元測試。