Golangで標準入力を使用する関数のテスト

概要

Golangの関数内で標準入力の値を受け取る関数に単体テストを行う方法をまとめました。

fmt.Scanで標準入力の値を受け取る関数

fmt.Scanを使用して標準入力から値を受け取る関数は、そのままだと単体テストができません。
下記のinputStdin関数は、関数内で標準入力の内容を受け取り
標準入力の内容をそのまま返す関数です。

1func inputStdin() string {
2	var inputValue string
3	fmt.Println("入力してください")
4	fmt.Print(">")
5	fmt.Scan(&inputValue)
6	return inputValue
7}

inputStdinに単体テストを行うとエラーになります。

1func TestInputStdin(t *testing.T) {
2	got := inputStdin()
3	want := "test"
4	if got != want {
5		t.Errorf("got %s, want %s", got, want)
6	}
7}
1go test
2入力してください
3>--- FAIL: TestInputStdin (0.00s)
4    main_test.go:12: got , want test

fmt.Scanの確認

fmt.Scanのコードを確認してみると、os.Stdinを第一引数に渡してFscanを呼び出しています。 https://cs.opensource.google/go/go/+/refs/tags/go1.18.5:src/fmt/scan.go;l=63

1func Scan(a ...any) (n int, err error) {
2	return Fscan(os.Stdin, a...)
3}

Fscanのコードを確認してみると、第一引数にio.Readerインターフェースの要件を満たす型を渡すことができます。
引数rのio.Readerにos.Stdin以外の引数を渡すとテストできそうです。

1func Fscan(r io.Reader, a ...any) (n int, err error) {
2	s, old := newScanState(r, true, false)
3	n, err = s.doScan(a)
4	s.free(old)
5	return
6}

fmt.Fscanに書き換えて単体テスト

fmt.Scanだとos.Stdinから値を読み込んでしまうため、代わりにfmt.Fscanを使用し
任意のio.Readerから値を取得できるようにします。

 1func inputStdin2(in io.Reader) string {
 2
 3	if in == nil {
 4		in = os.Stdin
 5	}
 6
 7	var inputValue string
 8	fmt.Println("入力してください")
 9	fmt.Print(">")
10	fmt.Fscan(in, &inputValue)
11	return inputValue
12}
1func inputStdin2(in io.Reader) string {

引数にio.Readerインターフェースの要件を満たすものを渡します。

1	if in == nil {
2		in = os.Stdin
3	}

引数のinがnilのときはos.Stdinを入力に使用します。

1fmt.Fscan(in, &inputValue)

fmt.Scanの代わりにfmt.Fscanを使用し、第一引数にinを渡します。
第二引数に値を書き込みたい変数を渡します。

inputStdin2に単体テストを行ってみます。

1func TestInputStdin2(t *testing.T) {
2	in := bytes.NewBufferString("test")
3	got := inputStdin2(in)
4	want := "test"
5	if got != want {
6		t.Errorf("got %s, want %s", got, want)
7	}
8}
1in := bytes.NewBufferString("test")

bytes.NewBufferStringを利用してBuffer構造体を作成します。
Buffer構造体はio.Readerのインターフェース要件を満たしているため、inputStdin2の引数に渡すことができます。 https://cs.opensource.google/go/go/+/refs/tags/go1.18.5:src/bytes/buffer.go;l=297

テストを行うとokと表示されます。

1go test
2入力してください
3>PASS
4ok      stdin-unittest  0.223s

参考URL

https://zenn.dev/hsaki/books/golang-io-package/viewer/fmt

https://stackoverflow.com/questions/17456901/how-to-write-tests-against-user-input-in-go

関連ページ