golangでIOへのテストを行う

まとめ

  • fmt.Print等にちゃんと出力されるかテストしたい
    • 結論としては直接は無理
  • io.Writerを利用するように変えることで簡単にテスト可能
    • 渡されたio.Writerに書き込むようにする
    • 通常はos.Stdout、テストの時はbytes.Bufferを渡す
      • どちらもio.Writerを実装している

標準出力への書き込みをテストしたい

fmt.Print等で文字列を出力する場合、予期したものが出力されるかをテストしたい場合があります。
ですが、fmt.Printはそのまま出力まで行ってしまうらしく、こちら側で制御することは難しそうです。

このような場合、fmt.Printを使うのではなく、明示的に標準出力へ書き込むようにし、
テストの時は書き込み先を切り替えることで簡単にテストができるようになります。

fmt.Fprintで出力先を指定する

golangでは任意の書き込み先に対して書き込むfmt.Fprint関数が用意されています。
この関数は、io.Writerに対してフォーマット指定した文字列を書き込めます。
https://golang.org/pkg/fmt/#Fprint

io.WriterはWrite(p []byte) (n int, err error)関数だけを持ったインターフェースです。
そのため、これを実装していればfmt.Fprintの書き込み先として使えます。
http://golang.org/pkg/io/#Writer

golangでは、io.Writerを実装した標準出力をos.Stdoutとして提供しています。
そのため、os.Stdoutにfmt.Fprintで書き込むことにより、
出力先を変更可能な状態で標準出力に出力できます。

メモリ上に出力する

golangでは、byets.Bufferもio.Writerを実装しており、こちらは書き込まれた文字列をメモリ上に保持してくれます。
そして、String()関数により、書き込まれた文字列をstringとして取得できます。
http://golang.org/pkg/bytes/#Buffer

これを利用し、普段はos.Stdoutに書き込むようにし、テストの時に書き込み先をbyets.Bufferに変更することで、
標準出力に出力されたかどうかをテストすることができるようになります。

サンプルコード

print.go

import (
    "fmt"
    "io"
    "os"
)

func testPrint(w io.Writer) {
    fmt.Fprint(w, "write test\n")
}

func main() {
    testPrint(os.Stdout)
}

print_test.go

package main
import (
    "bytes"
    "testing"
)

func TestPrint(t *testing.T) {
    buf := &bytes.Buffer{}
    testPrint(buf)
    outputString := buf.String()
    
    correctString := "write test\n"
    if correctString != outputString {
        t.Errorf("output string shud be %s but %s", correctString, outputString)
        t.FailNow()
    }
}