Go 的測試到底要放哪?
test package
在 Line 群上有至少多位網友問過類似下面的問題.
那各位在比較正式的專案還會用 service locator / ioc container 先把依賴或依賴的工廠集中起來嗎? (所以測試的時候 要特別去引用 service locator 把依賴從 測試取出?)
xxx_test.go 的 package name 會加上 test 嗎? (目前觀察到的差異是加上 test 會 access 不到小寫開頭的變數)
常見的有
跟待測試模組同資料夾, 檔名是
_test.go, 且跟待測試模組同package, 稱為內部測試文件.跟待測試模組同資料夾, 檔名是
_test.go, 且跟待測試模組不同package, 他的package 名稱會是 `xxx_test,稱為外部測試文件.通常在根目錄有個獨立的
test資料夾.
go list
當使用 Go 語言進行開發時,測試是一個不可或缺的部分。go list 命令是一個強大的工具,可以用來檢視和操作 Go 模組的資訊。
go list 通常會搭配 -f 一起使用, -f 指定輸出格式,通常使用 Go 模板語法。.
| Term | 回傳類型 | 描述 |
| ImportPath | string | 當前 package 下套件的導入路徑 |
| TestGoFiles | []string | 列舉當前package中的內部測試文件(同package名稱,_test.go 結尾)。 |
| XTestGoFiles | []string | 當前package中的外部測試文件(不同packge名,_test.go 結尾) |
| TestImports | []string | 內部測試文件中導入的所有package的導入路徑。 |
| XTestImports | []string | 外部測試文件中導入的所有package的導入路徑。 |
有以上這些我就能在有 go.mod 的專案中來快速查找有沒有測試文件, 分別又是內部或是外部測試文件.
像是我在 opentelemetry-go-contrib 專案中分別輸入以下兩個指令, 會出現不同的查找結果.
go list ./… 這個命令將列出當前目錄及所有子目錄中的所有包的導入路徑。
go list -f '{{.ImportPath}}: {{.XTestGoFiles}}' ./... 列出所有外部測試文件 (XTestGoFiles)。
go list -f '{{.ImportPath}}: {{.TestGoFiles}}' ./... 列出所有內部測試文件 (TestGoFiles)。
> opentelemetry-go-contrib/detectors/aws/ecs
go list ./...
# go.opentelemetry.io/contrib/detectors/aws/ecs
# go.opentelemetry.io/contrib/detectors/aws/ecs/test
go list -f '{{.ImportPath}}: {{.XTestGoFiles}}' ./...
# go.opentelemetry.io/contrib/detectors/aws/ecs: [version_test.go]
# go.opentelemetry.io/contrib/detectors/aws/ecs/test: []
go list -f '{{.ImportPath}}: {{.TestGoFiles}}' ./...
# go.opentelemetry.io/contrib/detectors/aws/ecs: [ecs_test.go]
# go.opentelemetry.io/contrib/detectors/aws/ecs/test: [ecs_test.go]
當然也能自由組合, 例如 go list -f '{{.ImportPath}}: TestGoFiles: {{.TestGoFiles}} XTestGoFiles: {{.XTestGoFiles}} Imports: {{.Imports}} Deps: {{.Deps}}' ./..., 列出每個 package 的內部測試文件、外部測試文件和所有依賴。 能自己玩看看。
在 go testing 套件的一開始說明有一段, 這裡就在講述內部測試文件與外部測試文件.
To write a new test suite, create a file that contains the TestXxx functions as described here, and give that file a name ending in "_test.go". The file will be excluded from regular package builds but will be included when the "go test" command is run.
The test file can be in the same package as the one being tested, or in a corresponding package with the suffix "_test".
If the test file is in the same package, it may refer to unexported identifiers within the package.
If the file is in a separate "_test" package, the package being tested must be imported explicitly and only its exported identifiers may be used. This is known as "black box" testing.
內部測試文件
跟待測試模組同資料夾, 檔名是_test.go, 且跟待測試模組同package。也就是說.
tree
.
├── go.mod
├── go.sum
├── mux.go
├── mux_test.go
cat mux_test.go
> package mux
雖然兩個是同一個package, 但此時你能做實驗, 在 exporter_test.go裡面宣告的public 成員其實在 非 _test.go 的一般模組中是無法被引用的. 這點在 go 官方網站講到 build 時也有提到.
When compiling packages, build ignores files that end in '_test.go'.
外部測試文件
待補
獨立 test 資料夾
我一樣以 opentelemetry-go-contrib 專案來做說明.
cd opentelemetry-go-contrib/instrumentation/github.com/gorilla/mux/otelmux
tree
.
├── go.mod
├── go.sum
├── mux.go
├── mux_test.go
├── test
│ ├── doc.go
│ ├── go.mod
│ ├── go.sum
│ ├── mux_test.go
│ ├── version.go
│ └── version_test.go
├── version.go
└── version_test.go
在 Go 專案中,測試通常與被測試的程式碼放在同一個模組中,使用相同或不同的 package 名稱來進行內部測試或外部測試。然而,有時候為了特定需求,我們可能會將測試代碼放在一個獨立的模組中。這意味著測試程式碼有自己獨立的 go.mod 文件,並且可以擁有不同的依賴,這與主模組的依賴隔離開來。
為什麼使用獨立測試模組?
隔離依賴:
避免傳遞性依賴:主模組(例如
otelmux的主要模組)不需要依賴預設的 SDK,這樣使用otelmux的用戶不會因為主模組的依賴而被迫引入預設 SDK。獨立管理測試依賴:測試模組可以單獨管理其依賴,如預設 SDK,這些依賴僅在測試時使用,不會影響主模組的依賴樹。
支持更複雜的測試場景:
整合測試:獨立模組可以更容易地進行整合測試,因為它們可以引入更多的依賴和工具來模擬真實的使用場景。
專門的測試工具:可以引入專門用於測試的工具套件(如
testutil),而不會干擾主模組的依賴。
提升模組的可維護性和靈活性:
減少主模組的依賴:保持主模組的依賴輕量,提升其可維護性和靈活性。
獨立更新測試程式碼:測試模組可以獨立更新和升級,而不會影響主模組的穩定性。
避免循環導入(Import Cycle):
如您所提到的,循環導入會導致 Go 編譯失敗。獨立測試模組可以避免這種情況,因為測試模組不需要反向引用主模組。
測試模組可以作為獨立的實體存在,避免因測試需求而導致的循環依賴問題。
[缺] 增加項目結構的複雜性:
需要管理多個
go.mod文件和模組,增加了項目的結構複雜性。對於新手來說,可能需要花更多時間來理解和管理多模組項目。
[缺] 可能的重複程式碼:
- 在某些情況下,主模組和測試模組可能需要共享一些程式碼,這可能導致程式碼重複或需要額外的程式碼共享機制。
[缺] 需要額外的配置和維護:
- 需要為獨立測試模組設定與配置 CI/CD pipeline、測試覆蓋率報告等,增加了維護的工作量。






