関連記事
GitHub - devlights/blog-summary: ブログ「いろいろ備忘録日記」のまとめ
概要
以下、自分用のメモです。たまに、使いたいときに忘れているのでここにメモメモ。。。
以下のコンテキストはコマンドラインアプリの場合とします。
たまに、main関数自体のテストを通したいときがあります。Exampleテストでも良いのですが、例としてのテストでは無いのでExampleと名前が付くのがちょっと嫌。
Testほにゃららのような形でmain関数の出力を検証したい。本来であれば、中で呼び出される関数群を適切に設計・実装し、それらをテストすれば事足りるのですが、現実そんなにうまくいかない場合も多いです。
そういうときは、テストコード内で一時的に標準出力をパイプなりに差し替えてしまって、関数実行後に出力内容を検証すれば、あまり凝ったことせずにmainの出力をテスト出来ます。
今回は、os.Pipe を利用した版です。
サンプル
main.go
実装に特に意味はありません。
package main import ( "flag" "fmt" "strings" ) type ( vars []string ) func (me *vars) String() string { return fmt.Sprint(*me) } func (me *vars) Set(v string) error { *me = append(*me, v) return nil } var ( _ flag.Value = (*vars)(nil) ) func main() { var ( vs vars ) flag.Var(&vs, "v", "values") flag.Parse() fmt.Println(strings.Join(vs, ",")) }
main_test.go
テストの実装。ここで実行時に標準出力のファイルディスクリプタを差し替えて処理を実行させます。
完了したら、元に戻す。
package main import ( "bytes" "io" "os" "sync" "testing" ) func TestMainOutput(t *testing.T) { // 元の標準出力を退避させ、パイプのWriter側に差し替え old := os.Stdout r, w, _ := os.Pipe() os.Stdout = w defer func() { // 元に戻す。このプログラムはこのまま終了するので別にしなくて良いが習慣として。 os.Stdout = old }() // コマンドライン引数 os.Args = append(os.Args, "-v", "hello", "-v", "world", "-v", "へろー", "-v", "ワールド") var wg sync.WaitGroup wg.Add(1) // パイプはノンバッファリングなので非同期処理が必須 go func() { defer wg.Done() defer w.Close() main() }() wg.Wait() // 出力内容を確認 // 出力量が分かっている場合は以下でも良いが // 不明な場合は [io.Reader.Read()] をちゃんとループ処理して // 読み込む処理にする必要がある。 want := []byte("hello,world,へろー,ワールド\n") got, _ := io.ReadAll(r) if !bytes.Equal(want, got) { t.Errorf("want: %s\tgot: %s", want, got) } }
実行
$ task task: [default] go build -o app . task: [default] ./app -v "hello" -v "world" -v "へろー" -v "ワールド" hello,world,へろー,ワールド task: [default] go test . ok github.com/devlights/try-golang/examples/singleapp/main_stdout_ospipe 0.002s
参考情報
Goのおすすめ書籍
過去の記事については、以下のページからご参照下さい。
サンプルコードは、以下の場所で公開しています。