コマンドラインツールを作成すると、Stdin,Stdout,Stderr を扱うときがあって、それらのテストをやろうとした時どうすればいいのか考えますよね。
今回もツールを作っててハマったので記録しておきます。
構造体に io.Reader, io.Writer を定義する
例えば
person.go
type Person struct {
Stdin io.Reader
Stdout io.Writer
Stderr io.Writer
}
func New() *Person {
return &Person{
Stdin: os.Stdin,
Stdout: os.Stdout,
Stderr: os.Stderr,
}
}
のような感じで構造体に定義しておきます。
こうすることで、
person := person.New()
のようなコードを書いたとして標準入力なら
scanner := bufio.NewScanner(person.Stdin)
標準出力なら
person.Stdout.Write([]byte("Hello"))
といったコードを書くことができます。
テストを書く
上記のような構造体を使うことで、Stdin,Stdout,Stderr のテストを次のように行うことができます。ポイントはそのまま os.Stdin, os.Stdout, os.Stderr を使うのではなく、別の io.Reader, io.Writer に置き換えることでテストが可能になります。
この時、bytes.Buffer を使うと便利です。
person_test.go
import (
"fmt"
"bytes"
"scanner"
"testing"
)
func TestPerson(t *testing.T) {
// 別の io.Reader, io.Writer に置き換える
expected := "Hello"
input := bytes.NewBufferString(expected + "\n")
output := new(bytes.Buffer)
person := &Person{
Stdin: input,
Stdout: output,
Stderr: output,
}
// standard output
fmt.Fprint(person.Stdout, expected)
if expected != output.String() {
t.Errorf("got: %s, expected: %s", output.String(), expected)
}
// standard input
scanner := bufio.NewScanner(person.Stdin)
for scanner.Scan() {
text := scanner.Text()
if expected != text {
t.Errorf("got: %s, expected: %s", text, expected)
}
}
}
stdout では fmt.Fprint()
で person.Stdout
に書き込んだ文字列を output
変数を通じて取り出しています。output.String()
stdin では bytes.NewBufferString()
で書き込んだ文字列を scanner := bufio.NewScanner(person.Stdin)
から scanner.Text()
を通じて取得しています。
今回はこのような感じでテストしました。
間違っているところや直した方ががいいポイントがあれば、是非教えてください!!