iT邦幫忙

2023 iThome 鐵人賽

DAY 12
0

昨天我們開始 coding,寫了一個簡單的、包含基本功能的 RSS 閱讀器後端 API。我們在把程式上線前,先做一點測試吧。

為什麼要測試

別小看了測試,它就像是你在料理前先試吃食材的味道。想想看你會把沒試過味道的糖跟鹽就直接灑到湯裡嗎?
東西沒測就上線,就準備周末解 bug。

為什麼單元測試重要?

單元測試是一個程式開發過程中不可或缺的部分,它是測試金字塔的最底層。你可能以為測試應該丟給 QA 或是 TE 去做,但正好相反,單元測試是由開發者來完成的。這就像是畫家對自己每一筆劃都要求完美,而不是交給評論家來批評。

Go中的測試框架

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)
}

接下來,我們來實作幾個單元測試:

  1. 測試提供無效 JSON 的情況
    • 我們可以模擬一個帶有無效 JSON 的 HTTP 請求,然後確保函數回傳 http.StatusBadRequest
  2. 測試提供無效 RSS URL 的情況
    • 在這個測試中,我們可以傳遞一個無法解析的 RSS URL,然後確保函數回傳 http.StatusBadRequest
  3. 測試成功訂閱一個 RSS feed,並檢查返回的 JSON 是否包含正確的信息
    • 我們可以使用一個有效的 RSS URL 進行測試,然後驗證返回的 JSON 包含我們預期的 RSS 項目。

在進行這些單元測試之前,請確保已經安裝了必要的 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
$ ... > 

還有其他問題

  1. 其實應該把 RSS 處理的部分獨立一個 module
  2. 測試的 code.

最佳實踐

  1. 保持測試簡單和明確: 每個測試案例都應只測試一個具體的功能。
  2. 使用描述性的測試函數名稱: 這樣可以更容易地理解每個測試的目的。
  3. 測試邊界條件: 不僅要測試"正常"的情況,也要測試邊界條件和可能的錯誤情況。

總結

單元測試是確保程式碼品質的重要手段,尤其在多人開發大型專案時更是如此。希望今天的內容能幫助你了解如何在 Go 中進行單元測試。


上一篇
Day 11: 在 VSCode 中建立 Go 專案,打造你的 RSS 閱讀器 API
下一篇
Day 13:談談 Go 的套件管理:從 GOPATH 到 go mod
系列文
30天打造自己的RSS閱讀器:Go語言與DevOps的實戰應用30
圖片
  直播研討會
圖片
{{ item.channelVendor }} {{ item.webinarstarted }} |
{{ formatDate(item.duration) }}
直播中

尚未有邦友留言

立即登入留言