GolangのTable Driven Testとt.Parallel()には罠がある

2021/04/24に公開

概要

テストの各条件を明記して
あとは同じような処理でループ回しちゃうTable Drivenな書き方は好きだけど

Testxxxとt.Run()をどちらもt.Parallel()で並行処理させると
想定外の挙動してしまうよね。っていうお話

想定外の動き

こんな感じでTable Drivenなテストを書いてみる

package main

import (
	"fmt"
	"testing"
)

func TestSomething(t *testing.T) {
	t.Parallel()
	tests := []struct{
		name string
		value int
	}{
		{
			name: "test1",
			value: 1,
		},
		{
			name: "test2",
			value: 2,
		},
		{
			name: "test3",
			value: 3,
		},
	}
	for _, tt:= range tests {
		t.Run(tt.name, func(t *testing.T) {
			t.Parallel()
			fmt.Println(fmt.Sprintf("%+v", tt.value))
		})
	}
}

1, 2, 3が出力されてくれるかと思いきや...

$ go test -v
=== RUN   TestSomething
=== PAUSE TestSomething
=== CONT  TestSomething
=== RUN   TestSomething/test1
=== PAUSE TestSomething/test1
=== RUN   TestSomething/test2
=== PAUSE TestSomething/test2
=== RUN   TestSomething/test3
=== PAUSE TestSomething/test3
=== CONT  TestSomething/test1
3
=== CONT  TestSomething/test3
3
=== CONT  TestSomething/test2
3
--- PASS: TestSomething (0.00s)
    --- PASS: TestSomething/test1 (0.00s)
    --- PASS: TestSomething/test3 (0.00s)
    --- PASS: TestSomething/test2 (0.00s)
PASS

3しか出てこない!!なんでや!!!

原因

この辺の影響みたい
https://github.com/golang/go/wiki/CommonMistakes

loop内でgoroutineするとうまくfor内のvalueが渡されないケースがあるみたいで
そいつに引っかかるらしい

確かに、ポインターとか絡めてループすると
なんか意図したループにならないケースあったけどこの辺の影響だったのか・・・

解決法

ループ内で変数を再宣言する
これtt := tt

	for _, tt:= range tests {
		tt := tt
		t.Run(tt.name, func(t *testing.T) {
			t.Parallel()

ちゃんと全部でた🤩

$ go test -v
=== RUN   TestSomething
=== PAUSE TestSomething
=== CONT  TestSomething
=== RUN   TestSomething/test1
=== PAUSE TestSomething/test1
=== RUN   TestSomething/test2
=== PAUSE TestSomething/test2
=== RUN   TestSomething/test3
=== PAUSE TestSomething/test3
=== CONT  TestSomething/test1
1
=== CONT  TestSomething/test3
3
=== CONT  TestSomething/test2
2
--- PASS: TestSomething (0.00s)
    --- PASS: TestSomething/test1 (0.00s)
    --- PASS: TestSomething/test3 (0.00s)
    --- PASS: TestSomething/test2 (0.00s)
PASS

まとめ

「テスト全部通ってるやん😎」と思ったら
最後のケースしか実行されてなかった。
というだいぶえぐいハマり方をしたのでメモ残しとく

Discussion