詳解在Go語(yǔ)言單元測(cè)試中如何解決文件依賴問(wèn)題
現(xiàn)如今的 Web 應(yīng)用程序往往采用 RESTful API 接口形式對(duì)外提供服務(wù),后端接口直接向前端返回 HTML 文件的情況越來(lái)越少,所以在程序中操作文件的場(chǎng)景也變少了。不過(guò)有些時(shí)候還是需要對(duì)文件進(jìn)行操作,比如某個(gè) API 接口需要返回應(yīng)用程序的 ChangeLog,那么這個(gè)接口就可以通過(guò)讀取項(xiàng)目的 CHANGELOG.md
文件內(nèi)容,將其發(fā)送給前端。
在編寫單元測(cè)試時(shí),文件就成了被測(cè)試代碼的外部依賴,本文就來(lái)講解下測(cè)試過(guò)程中如何解決文件外部依賴問(wèn)題。
獲取 ChangeLog 程序示例
假設(shè)我們有一個(gè)函數(shù),可以讀取項(xiàng)目的 ChangeLog 信息并返回。
程序代碼實(shí)現(xiàn)如下:
package main import ( "io" "os" ) var ( version = "dev" commit = "none" builtGoVersion = "unknown" changeLogPath = "CHANGELOG.md" ) type ChangeLogSpec struct { Version string Commit string BuiltGoVersion string ChangeLog string } func GetChangeLog() (ChangeLogSpec, error) { data, err := os.ReadFile(changeLogPath) if err != nil { return ChangeLogSpec{}, err } return ChangeLogSpec{ Version: version, Commit: commit, BuiltGoVersion: builtGoVersion, ChangeLog: string(data), }, nil }
GetChangeLog
函數(shù)實(shí)現(xiàn)比較簡(jiǎn)單,首先從 changeLogPath
文件路徑中讀取 ChangeLog 內(nèi)容,然后結(jié)合程序版本號(hào)、COMMIT 信息、Go 版本號(hào)一起組裝成 ChangeLogSpec
結(jié)構(gòu)體,并返回。
使用臨時(shí)文件測(cè)試
現(xiàn)在,我們要對(duì) GetChangeLog
函數(shù)進(jìn)行單元測(cè)試。
可以發(fā)現(xiàn),GetChangeLog
函數(shù)內(nèi)部依賴了 changeLogPath
文件路徑,然后從中讀取內(nèi)容。所以,在編寫測(cè)試時(shí),我們要考慮 changeLogPath
文件如何指定。
我們最先想到的就是指定 changeLogPath
文件的真實(shí)路徑。但是,這可能會(huì)存在問(wèn)題,比如本地環(huán)境和 CI 環(huán)境下 changeLogPath
文件路徑不同,那么在編寫測(cè)試代碼時(shí),就要考慮根據(jù)不同的測(cè)試環(huán)境執(zhí)行不同邏輯。所以,這種方式不應(yīng)該成為首選方案。
不過(guò),我們可以換種思路,Go 語(yǔ)言提供了 os.CreateTemp
方法,可以創(chuàng)建一個(gè)臨時(shí)文件。那么,我們就可以考慮在測(cè)試函數(shù)開(kāi)始時(shí)創(chuàng)建一個(gè)臨時(shí)文件來(lái)保存 ChangeLog,然后為 changeLogPath
變量賦值為臨時(shí)文件路徑,測(cè)試代碼執(zhí)行完成后刪除臨時(shí)文件,這樣就能夠解決單元測(cè)試中依賴外部文件的問(wèn)題。
按照這個(gè)思路,編寫的單元測(cè)試代碼如下:
func TestGetChangeLog(t *testing.T) { // 創(chuàng)建臨時(shí)文件 // 第一個(gè)參數(shù)傳 "",表示在操作系統(tǒng)的臨時(shí)目錄下創(chuàng)建該文件 // 文件文件名會(huì)以第二個(gè)參數(shù)作為前綴,剩余的部分會(huì)自動(dòng)生成,以確保并發(fā)調(diào)用時(shí)生成的文件名不重復(fù) f, err := os.CreateTemp("", "TEST_CHANGELOG") assert.NoError(t, err) defer func() { _ = f.Close() // 盡管操作系統(tǒng)會(huì)在某個(gè)時(shí)間自動(dòng)清理臨時(shí)文件,但主動(dòng)清理是創(chuàng)建者的責(zé)任 _ = os.RemoveAll(f.Name()) }() changeLogPath = f.Name() data := ` # Changelog All notable changes to this project will be documented in this file. ` _, err = f.WriteString(data) assert.NoError(t, err) expected := ChangeLogSpec{ Version: "v0.1.1", Commit: "1", BuiltGoVersion: "1.20.1", ChangeLog: ` # Changelog All notable changes to this project will be documented in this file. `, } actual, err := GetChangeLog() assert.NoError(t, err) assert.Equal(t, expected, actual) }
我們首先通過(guò) os.CreateTemp("", "TEST_CHANGELOG")
創(chuàng)建了一個(gè)臨時(shí)文件,然后將 data
內(nèi)容寫入臨時(shí)文件作為 ChangeLog,再然后將臨時(shí)文件名稱 f.Name()
賦值給 changeLogPath
,之后就可以調(diào)用 GetChangeLog
函數(shù)進(jìn)行測(cè)試了。
對(duì)于程序版本號(hào)、COMMIT 信息、Go 版本號(hào)這幾個(gè)變量,因?yàn)槎际侨肿兞?,所以也屬于外部依賴?/p>
對(duì)于全局變量的依賴,我們可以在 init
函數(shù)中對(duì)其進(jìn)行初始化,這樣就相當(dāng)于在測(cè)試環(huán)境中固定了這幾個(gè)變量的值,便于測(cè)試。
func init() { version = "v0.1.1" commit = "1" builtGoVersion = "1.20.1" }
筆記:你也可以在 TestMain 函數(shù)中對(duì)其進(jìn)行初始化。
使用 go test
來(lái)執(zhí)行測(cè)試函數(shù):
$ go test -v -run="TestGetChangeLog$" === RUN TestGetChangeLog --- PASS: TestGetChangeLog (0.00s) PASS ok github.com/jianghushinian/blog-go-example/test/file 0.562s
測(cè)試通過(guò)。
使用 Go embed 測(cè)試
以上我們介紹了使用臨時(shí)文件的方式來(lái)解決被測(cè)試函數(shù)依賴外部文件的問(wèn)題。
不過(guò)我們?cè)跍y(cè)試中提供的 ChangeLog 內(nèi)容不多:
data := ` # Changelog All notable changes to this project will be documented in this file. `
為了讓單元測(cè)試更加可靠,你也許想測(cè)試 ChangeLog 內(nèi)容比較多的情況下,GetChangeLog
函數(shù)能否正常工作。
我們可以編寫一個(gè)真實(shí)的 CHANGELOG.md
文件,存放于 testdata/CHANGELOG.md
路徑下:
# Kubernetes v0.1.1 ## 主要特性和改進(jìn) - 添加了一些新的主要特性和改進(jìn)。 ## 重要變更 - 這里列出了對(duì)現(xiàn)有功能的重要變更。 ## API 變更 - 在 API 中進(jìn)行的重要變更和更新。 ## 已知問(wèn)題 - 列出了已知的問(wèn)題和限制。 ## Bug 修復(fù) - 修復(fù)了以下已知 Bug。 ## 改進(jìn)和優(yōu)化 - 對(duì)現(xiàn)有功能進(jìn)行了改進(jìn)和優(yōu)化。 ## 安全性更新 - 列出了安全性方面的更新和修復(fù)。 ## 已棄用功能 - 列出了已被棄用的功能。 ## 警告和提醒 - 列出了需要注意的警告和提醒事項(xiàng)。 ## 社區(qū)貢獻(xiàn)者 - 致謝并列出了為此版本做出貢獻(xiàn)的社區(qū)成員。 更詳細(xì)的信息可以查閱 Kubernetes 官方文檔和發(fā)布說(shuō)明。
此時(shí),我們可以使用 Go 提供的 embed 技術(shù)來(lái)將文件內(nèi)容嵌入到 Go 變量中。
embed []byte
embed 可以實(shí)現(xiàn)在 Go 程序編譯時(shí),直接將文件內(nèi)容嵌入到 Go 變量。embed 目前支持嵌入兩種基礎(chǔ)類型的變量,分別是 []byte
和 strings
。嵌入這兩種類型變量方式相同,本小節(jié)就像大家演示下如何通過(guò)將文件嵌入 []byte
變量的方式來(lái)編寫 GetChangeLog
函數(shù)的單元測(cè)試。
為 GetChangeLog
函數(shù)編寫的單元測(cè)試代碼如下:
package main import ( _ "embed" "os" "testing" "github.com/stretchr/testify/assert" ) //go:embed testdata/CHANGELOG.md var changelog []byte func TestGetChangeLog_by_embed(t *testing.T) { f, err := os.CreateTemp("", "TEST_CHANGELOG") assert.NoError(t, err) defer func() { _ = f.Close() _ = os.RemoveAll(f.Name()) }() changeLogPath = f.Name() _, err = f.Write(changelog) assert.NoError(t, err) expected := ChangeLogSpec{ Version: "v0.1.1", Commit: "1", BuiltGoVersion: "1.20.1", ChangeLog: string(changelog), } actual, err := GetChangeLog() assert.NoError(t, err) assert.Equal(t, expected, actual) }
單元測(cè)試中,我們最需要關(guān)注的是這行代碼:
//go:embed testdata/CHANGELOG.md var changelog []byte
//go:embed
是一個(gè)指令注釋,用來(lái)標(biāo)記嵌入指令,注意冒號(hào) :
前后沒(méi)有空格,testdata/CHANGELOG.md
指明要嵌入的文件。
在嵌入指令下方,緊挨著我們定義了變量 var changelog []byte
用來(lái)接收被嵌入文件的內(nèi)容。
程序編譯后,變量 changelog
的值就是 testdata/CHANGELOG.md
文件中的內(nèi)容了。
注意,文件開(kāi)頭的 import
中要導(dǎo)入 embed
,嵌入指令才可以使用。
之后的單元測(cè)試代碼改動(dòng)就比較小了,僅用 changelog
變量替換了原來(lái)代碼中的 data
變量。
使用 go test
來(lái)執(zhí)行測(cè)試函數(shù):
$ go test -v -run="TestGetChangeLog_by_embed" === RUN TestGetChangeLog_by_embed --- PASS: TestGetChangeLog_by_embed (0.00s) PASS ok github.com/jianghushinian/blog-go-example/test/file 0.365s
單元測(cè)試仍能通過(guò)。
embed fs.FS
Go embed 技術(shù)不僅能夠嵌入文件到基礎(chǔ)類型變量,還能直接將文件嵌入為一個(gè)文件系統(tǒng)。
為了演示這一強(qiáng)大的功能,我們修改下 GetChangeLog
函數(shù)代碼,讓其接收一個(gè) io.Reader
類型的參數(shù),然后從這個(gè)參數(shù)中讀取 ChangeLog 內(nèi)容,而不再是通過(guò)讀取指定路徑下的 ChangeLog 內(nèi)容。
修改后程序代碼如下:
func GetChangeLogByIOReader(reader io.Reader) (ChangeLogSpec, error) { data, err := io.ReadAll(reader) if err != nil { return ChangeLogSpec{}, err } return ChangeLogSpec{ Version: version, Commit: commit, BuiltGoVersion: builtGoVersion, ChangeLog: string(data), }, nil }
如下是為新的 GetChangeLogByIOReader
函數(shù)編寫的單元測(cè)試代碼:
//go:embed testdata/CHANGELOG.md var fs embed.FS func TestGetChangeLogByIOReader(t *testing.T) { f, err := fs.Open("testdata/CHANGELOG.md") assert.NoError(t, err) data, err := io.ReadAll(f) assert.NoError(t, err) // 將數(shù)據(jù)的讀取位置重置到開(kāi)頭 _, err = f.(io.ReadSeeker).Seek(0, 0) assert.NoError(t, err) expected := ChangeLogSpec{ Version: "v0.1.1", Commit: "1", BuiltGoVersion: "1.20.1", ChangeLog: string(data), } actual, err := GetChangeLogByIOReader(f) assert.NoError(t, err) assert.Equal(t, expected, actual) }
我們同樣使用 //go:embed testdata/CHANGELOG.md
來(lái)指定嵌入的文件,不過(guò),這次定義的變量 var fs embed.FS
是一個(gè)文件系統(tǒng),里面包含了被嵌入的文件。
在測(cè)試代碼中,使用 fs.Open("testdata/CHANGELOG.md")
打開(kāi)文件內(nèi)容,得到 fs.File
類型對(duì)象,之后就可以像其他 Go 文件對(duì)象一樣操作它。
使用 go test
來(lái)執(zhí)行測(cè)試函數(shù):
$ go test -v -run="TestGetChangeLogByIOReader" === RUN TestGetChangeLogByIOReader --- PASS: TestGetChangeLogByIOReader (0.00s) PASS ok github.com/jianghushinian/blog-go-example/test/file 0.135s
測(cè)試通過(guò)。
總結(jié)
本文向大家介紹了在 Go 中編寫單元測(cè)試時(shí),如何解決文件外部依賴的問(wèn)題。
Go 語(yǔ)言提供了 os.CreateTemp
方法,可以創(chuàng)建一個(gè)臨時(shí)文件,我們可以利用這個(gè)方法來(lái)解決文件外部依賴。
此外,Go 語(yǔ)言還提供了 embed 技術(shù),能夠在程序編譯時(shí)直接將文件內(nèi)容嵌入到 Go 變量中。這項(xiàng)技術(shù)雖然不是為單元測(cè)試而生的,但我們可以借此來(lái)解決文件外部依賴問(wèn)題。本文為大家演示了如何將文件嵌入到 []byte
和文件系統(tǒng),兩種方案用法差異不大,可以根據(jù)需求和喜好進(jìn)行選擇。
本文完整代碼示例我放在了 GitHub 上,歡迎點(diǎn)擊查看。
希望此文能對(duì)你有所幫助。
以上就是詳解在Go語(yǔ)言單元測(cè)試中如何解決文件依賴問(wèn)題的詳細(xì)內(nèi)容,更多關(guān)于Go單元測(cè)試解決文件依賴的資料請(qǐng)關(guān)注腳本之家其它相關(guān)文章!
相關(guān)文章
Go語(yǔ)言基礎(chǔ)單元測(cè)試與性能測(cè)試示例詳解
這篇文章主要為大家介紹了Go語(yǔ)言基礎(chǔ)單元測(cè)試與性能測(cè)試示例詳解,有需要的朋友可以借鑒參考下,希望能夠有所幫助祝大家多多進(jìn)步2021-11-11Go?并發(fā)編程協(xié)程及調(diào)度機(jī)制詳情
這篇文章主要介紹了Go并發(fā)編程協(xié)程及調(diào)度機(jī)制詳情,協(xié)程是Go語(yǔ)言最大的特色之一,goroutine的實(shí)現(xiàn)其實(shí)是通過(guò)協(xié)程,更多相關(guān)內(nèi)容需要的朋友可以參考一下2022-09-09