Goで一度に複数パッケージのテストをしたい
今、Node.jsからGoへの移行に挑戦しており、四苦八苦しております。
普通に動くコードを書く分には、私がもともとCやC++を書いていたこともあり、Goの書き方はNode.jsよりも好きなのですが、テストを実行するときに躓いてしまいました。
今、やりたいのは、あるフォルダ、たとえばsrc
というフォルダの下にある全てのパッケージのテストを一気にするということです。
例えば、以下のようなファイル構造があったとします。
./src
|- calculate/
|- calculate.go
|- calculate_test.go
|- go.mod
|- outLog/
|- outLog.go
|- outLog_test.go
|- go.mod
main.go
go.mod
calculate
とoutLog
にgo.mod
を作らない(moduleの初期化(?)をしない)のであれば
go test -v ./src/...
でテストが可能です。
しかし、main.go
でcalculate
やoutLog
を使うには、moduleの初期化を行わないといけないので、これでは各モジュールのテストが出来ないのです。
そこで、moduleの初期化を行った上で、全てのmoduleのテストを一気にしてしまう方法を知りたいです。
ググっても、なかなかいい解決方法が出てこなかったので、暗中模索で色々試してみたことを共有したいと思います(結局、強引な解決策しか出てこなかったです…)。
ただし、Goにはinterfaceもありますし、DI(Dependency Injection)も使えそうなので[1]、各モジュールとの依存関係を切って、各モジュールで独立して(責務分離した上で)テストしたほうが良いのだろうなと思うのですが、初心者の私には一気に、そこまで実現できないので、そこまでいく途中経過として共有したいと思います。
各モジュールを初期化(go mod init ~)せずtestしてみる
とても簡単なテストです。ちなみに初期化していないので、main.goでは使えません。
package main
import "fmt"
func main() {
fmt.Print("何も出来ない😭")
}
package outlog
import (
"log"
"os"
"time"
)
type OutLog struct {
To_console bool
To_file bool
}
func (ol OutLog) Error(mess string) (rec string, err error) {
// 現在時刻取得
t := time.Now()
// ログのライン
line := "[" + t.Format(time.RFC3339) + "][Error] - " + mess
// ファイルへの出力
if ol.To_file {
f, err := os.OpenFile("log.txt", os.O_CREATE|os.O_RDWR|os.O_APPEND, 0600)
if err != nil {
log.Fatal(err)
return "", err
}
defer f.Close()
_, er := f.WriteString(line + "\n")
if er != nil {
log.Fatal(er)
return "", er
}
}
// コンソールへの出力
if ol.To_console {
log.Println(line)
}
return line, nil
}
package outlog
import "testing"
func TestConsole(t *testing.T) {
outLog := OutLog{true, false}
rec, err := outLog.Error("いるかはいるか?🐬")
if rec == "" || err != nil {
t.Fatal("エラーの出力失敗")
}
}
package calculate
func Calculate(n int) int {
return n * n
}
package calculate
import "testing"
func TestCal(t *testing.T) {
result := Calculate(2)
if result != 2*2 {
t.Fatal("calculateの答えが違うよ。")
}
}
これらのファイルで、main.go
があるディレクトリ上で以下のコマンドをterminalで実行すると
go test -v ./src/...
こんな感じの結果が出ます。
=== RUN TestCal
--- PASS: TestCal (0.00s)
PASS
ok playground/main/src/calculate (cached)
=== RUN TestConsole
2022/06/17 14:02:59 [2022-06-17T14:02:59+09:00][Error] - いるかはいるか?🐬
--- PASS: TestConsole (0.00s)
PASS
ok playground/main/src/outLog (cached)
各モジュール(パッケージ)を初期化する
これでは、作ったモジュールを、どこでも使えないので、初期化します。
初期化は、各モジュールのディレクトリで
go mod init [適当な名前]
go mod tidy
を実行すればいいです。
そして、各モジュールにパスを通すために、main.go
のディレクトリで
go mod edit -replace [適当な名前]=[モジュールまでの相対パス]
go mod tidy
を実行します。今回は、playground/calculate
とplayground/outLog
という名前にしたので、main.go
の中身は以下のようになりました。
package main
import (
"fmt"
"playground/calculate"
outlog "playground/outLog"
)
func main() {
ol := outlog.OutLog{To_console: true, To_file: false}
ol.Error("エラーが出力できた🎉")
res := calculate.Calculate(3)
fmt.Printf("計算結果:%d", res)
}
先ほどと同様に以下のコマンドを、main.go
があるディレクトリで実行すると
go test -v ./src/...
今度は、以下の結果が出ます。テストできるパッケージがないと怒られますね。
go: warning: "./src/..." matched no packages
no packages to test
なんとか出来ないかなーと、色々調べた結果、stackoverflowのbcmillsさんの回答[2]を見る限りどうも出来なさそうです…(残念)
この場合の、無理やりな回避方法を、いくつか書きます。
main.go
と同じディレクトリにテストファイルを置く
おそらく、要は実行するpackage上でテストを実行すれば問題ないはずなので、以下のようなファイルをmain.go
と同じファイルに置けばテスト出来るはずです。
package main
import (
"playground/calculate"
outlog "playground/outLog"
"testing"
)
func TestCal(t *testing.T) {
result := calculate.Calculate(2)
if result != 2*2 {
t.Fatal("calculateの答えが違うよ。")
}
}
func TestConsole(t *testing.T) {
outLog := outlog.OutLog{To_console: true, To_file: false}
rec, err := outLog.Error("いるかはいるか?🐬")
if rec == "" || err != nil {
t.Fatal("エラーの出力失敗")
}
}
calculate
とoutLog
のテストを分けたいのであれば、別ファイルにすれば動きます。
この方法は、同一ディレクトリに沢山テストファイルが貯まってしまうのが嫌ですね。
go test -v [パッケージ名]
でテストする
これが、一番、妥協策な気がするのですが、依存するパッケージが多くなるほどコマンドが長くなって辛いです。
例えば、今回、main.go
のgo.mod
は以下のようになっていて
module playground/main
go 1.18
replace playground/outLog => ./src/outLog
replace playground/calculate => ./src/calculate
require (
playground/calculate v0.0.0-00010101000000-000000000000
playground/outLog v0.0.0-00010101000000-000000000000
)
この、playground/calculate
とplayground/outLog
をテストしたいということで、main.go
と同じディレクトリで
go test -v playground/calculate playground/outLog
を実行すれば
=== RUN TestCal
--- PASS: TestCal (0.00s)
PASS
ok playground/calculate (cached)
=== RUN TestConsole
2022/06/17 15:55:19 [2022-06-17T15:55:19+09:00][Error] - いるかはいるか?🐬
--- PASS: TestConsole (0.00s)
PASS
ok playground/outLog (cached)
という結果が出てきます。
これだと、main.go
と同じディレクトリに、テストファイルが溢れることがないので、良いのかなーと思います。
まとめ
すごく、強引に「えいっ!」と解決した感が凄いので、他に良い解決策をご存知の方がいらっしゃいましたら、ご教授いただけると幸いです。
次はDIに挑戦してみます。
おまけ
ここのコードは、以下のGitHubに公開しています(ブランチで分けてます)。
-
【必須科目 DI】DIの仕組みをGoで実装して理解する / yoshinori_hisakawa https://qiita.com/yoshinori_hisakawa/items/a944115eb77ed9247794 (2022-06-17閲覧) ↩︎
-
How to run tests from go project root folder? / answer by bcmills https://stackoverflow.com/questions/65758349/how-to-run-tests-from-go-project-root-folder (2022-06-17閲覧) ↩︎
Discussion