この記事は ディップ with 全部俺 Advent Calendar 2019 の二日目の記事です。
一日目に引き続きGoのテストコードについてまとめていきたいと思います。
テストの並列化の前に並列処理を行う上での注意点を見てみましょう。
ループ変数の補足
並列処理を行うに当たり、ループして同じ処理を繰り返す場合に、意図しない挙動になることがあります。
下記に例を提示します。
実行してみるとわかりますが、自分の意図した挙動と異なるかと思います。
関数実行がループよりも後になり、ループとしてはすでに終了した最後のiが関数内で利用されることでこのようなことが起こります。
これを回避するには下記のように修正します。
これは、ループの内側でループの外側の i を補足し、並列実行される関数に渡します。 このため、意図した挙動になるのです。
テストの並列化
上記にしたことに注意し、前回記事のサブテスト化からサブテストの並列実行に移りましょう。
並列実行が有効な事がわかるように今回の実装では関数内でsleepを掛けます。
実装部
func getFizzBuzz(p int) (r string){ if p % 3 == 0 { r = r + "Fizz" } if p % 5 == 0 { r = r + "Buzz" } if r == "" { r = strconv.Itoa(p) } time.Sleep(500 * time.Millisecond) return }
テスト部
func TestGetFizzBuzz(t *testing.T) { tests := []struct{ name string p int want string }{ {"Number",1,"1"}, {"Fizz",3,"Fizz"}, {"Buzz",5,"Buzz"}, {"FizzBuzz",15,"FizzBuzz"}, } for _, tt := range tests { tt := tt t.Run(tt.name, func(t *testing.T){ t.Parallel() if got := getFizzBuzz(tt.p); got != tt.want{ t.Errorf("Want = %v, got = %v", tt.want, got) } }) } }
サブテスト内で t.Parallel()
を呼び出すことで並列化出来ます。
また、ループが始まってすぐの部分でテストケースの補足を行います。
このこの状態で実行すると
$ go test . -v === RUN TestSum === RUN TestSum/Simple === RUN TestSum/Minus === RUN TestSum/Both --- PASS: TestSum (0.00s) --- PASS: TestSum/Simple (0.00s) --- PASS: TestSum/Minus (0.00s) --- PASS: TestSum/Both (0.00s) === RUN TestGetFizzBuzz === RUN TestGetFizzBuzz/Number === PAUSE TestGetFizzBuzz/Number === RUN TestGetFizzBuzz/Fizz === PAUSE TestGetFizzBuzz/Fizz === RUN TestGetFizzBuzz/Buzz === PAUSE TestGetFizzBuzz/Buzz === RUN TestGetFizzBuzz/FizzBuzz === PAUSE TestGetFizzBuzz/FizzBuzz === CONT TestGetFizzBuzz/Number === CONT TestGetFizzBuzz/Buzz === CONT TestGetFizzBuzz/Fizz === CONT TestGetFizzBuzz/FizzBuzz --- PASS: TestGetFizzBuzz (0.00s) --- PASS: TestGetFizzBuzz/Buzz (0.50s) --- PASS: TestGetFizzBuzz/Fizz (0.50s) --- PASS: TestGetFizzBuzz/Number (0.50s) --- PASS: TestGetFizzBuzz/FizzBuzz (0.50s) PASS ok github.com/bikun-bikun/go-test 0.512s
と sleep 分くらいの実行時間でテストが終了します。 それに対して並列処理を外すとどうなるか見てみましょう
func TestGetFizzBuzz(t *testing.T) { tests := []struct{ name string p int want string }{ {"Number",1,"1"}, {"Fizz",3,"Fizz"}, {"Buzz",5,"Buzz"}, {"FizzBuzz",15,"FizzBuzz"}, } for _, tt := range tests { //tt := tt //コメントアウトで無効にしてみる t.Run(tt.name, func(t *testing.T){ //t.Parallel() //コメントアウトで無効にしてみる if got := getFizzBuzz(tt.p); got != tt.want{ t.Errorf("Want = %v, got = %v", tt.want, got) } }) } }
実際に実行してみると
$ go test . -v === RUN TestSum === RUN TestSum/Simple === RUN TestSum/Minus === RUN TestSum/Both --- PASS: TestSum (0.00s) --- PASS: TestSum/Simple (0.00s) --- PASS: TestSum/Minus (0.00s) --- PASS: TestSum/Both (0.00s) === RUN TestGetFizzBuzz === RUN TestGetFizzBuzz/Number === RUN TestGetFizzBuzz/Fizz === RUN TestGetFizzBuzz/Buzz === RUN TestGetFizzBuzz/FizzBuzz --- PASS: TestGetFizzBuzz (2.01s) --- PASS: TestGetFizzBuzz/Number (0.50s) --- PASS: TestGetFizzBuzz/Fizz (0.50s) --- PASS: TestGetFizzBuzz/Buzz (0.50s) --- PASS: TestGetFizzBuzz/FizzBuzz (0.50s) PASS ok github.com/bikun-bikun/go-test 2.017s
ということで、直列で実行した通りの時間となりました。
CIなどでテストを回す場合や、ローカルでテストを回す場合、テストのボリュームが大きくなるにつれ、テストの時間が増えていきます。
こうした細かい部分で処理時間を短くしていきましょう。