【Go】Goのテストに入門してみた! ~並行テスト編~
はじめに
前回は、「テストの前処理・後処理」を見ていきました。
今回は、「並行テスト」に入門します!
前回の記事はこちら!
この記事でわかること
- テストを並行実行する方法
- テスト結果の見方
- 並行実行するケース
普通にテストしてみる
擬似的に、以下のような高負荷な処理を実行します。
package main
import (
"testing"
"time"
"unicode/utf8"
)
func Length(s string) int {
// 擬似的に高負荷にする
time.Sleep(5 * time.Second)
return utf8.RuneCountInString(s)
}
func TestLength(t *testing.T) {
tests := []struct {
name string
input string
want int
}{
{"ascii", "Hello", 5},
{"full-width-japanese", "こんにちは", 5},
{"half-width-japanese", "コンニチハ", 5},
{"emoji", "😃😃😃", 3},
{"empty", "", 0},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
if got := Length(tt.input); got != tt.want {
t.Errorf("Length = %d, want %d", got, tt.want)
}
})
}
}
実行すると、以下のような結果になります。
# go test -v
=== RUN TestLength
=== RUN TestLength/ascii
=== RUN TestLength/full-width-japanese
=== RUN TestLength/half-width-japanese
=== RUN TestLength/emoji
=== RUN TestLength/empty
--- PASS: TestLength (25.01s)
--- PASS: TestLength/ascii (5.00s)
--- PASS: TestLength/full-width-japanese (5.00s)
--- PASS: TestLength/half-width-japanese (5.00s)
--- PASS: TestLength/emoji (5.00s)
--- PASS: TestLength/empty (5.00s)
PASS
ok go-test-practice 25.360s
各処理で5秒スリープしているので、5つのサブテストを実行し、合計で25秒かかっています。
並列テストをしてみる
次は、並列でテストを実行してみます。
package main
import (
"testing"
"time"
"unicode/utf8"
)
func Length(s string) int {
// 擬似的に高負荷にする
time.Sleep(5 * time.Second)
return utf8.RuneCountInString(s)
}
func TestLength(t *testing.T) {
tests := []struct {
name string
input string
want int
}{
{"ascii", "Hello", 5},
{"full-width-japanese", "こんにちは", 5},
{"half-width-japanese", "コンニチハ", 5},
{"emoji", "😃😃😃", 3},
{"empty", "", 0},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
t.Parallel()
if got := Length(tt.input); got != tt.want {
t.Errorf("Length = %d, want %d", got, tt.want)
}
})
}
}
実行すると、以下のような結果になります。
# go test -v
=== RUN TestLength
=== RUN TestLength/ascii
=== PAUSE TestLength/ascii
=== RUN TestLength/full-width-japanese
=== PAUSE TestLength/full-width-japanese
=== RUN TestLength/half-width-japanese
=== PAUSE TestLength/half-width-japanese
=== RUN TestLength/emoji
=== PAUSE TestLength/emoji
=== RUN TestLength/empty
=== PAUSE TestLength/empty
=== CONT TestLength/ascii
=== CONT TestLength/emoji
=== CONT TestLength/half-width-japanese
=== CONT TestLength/full-width-japanese
=== CONT TestLength/empty
--- PASS: TestLength (0.00s)
--- PASS: TestLength/emoji (5.00s)
--- PASS: TestLength/empty (5.00s)
--- PASS: TestLength/full-width-japanese (5.00s)
--- PASS: TestLength/ascii (5.00s)
--- PASS: TestLength/half-width-japanese (5.00s)
PASS
ok go-test-practice 5.353s
並行テスト前は、5つのサブテストを順次実行したことで、合計で25秒かかっていました。
しかし、テストを並行実行したことで、約5秒でテストが終了しました。
並行テスト結果の見方
1. RUNとPAUSE
「RUN」でサブテストが開始されますが、t.Parallel() が呼ばれると一時停止状態に「PAUSE」になります。
2. CONT
全てのサブテストが一時停止になり、実行可能状態になったところで、一斉にサブテストを実行し「CONT(Continue)」になります。
並行テストを実行するケース
1. 大量のテストケースがある場合
逐次実行すると時間がかかるテストも、並行で処理すれば実行時間を短縮することができます。
ただし、テスト同士の独立性が担保されていたり、リソースの競合が起きないことに注意しましょう。
2. 外部依存がある場合
外部APIやサービス、データベースアクセスなどを利用する場合のテストも、並行で処理すれば実行時間を短縮することができます。
しかしこれも、テスト同士の独立性が担保されていたり、リソースの競合が起きないことに注意しましょう。
まとめ
今回はテストを並行実行する方法を学びました。
t.Parallel() を使ってテストを並行実行することができます。
処理が重いテストや大量のテストケースを効率的にこなすには、テストを並行実行するのが有効です。
ただし、共有リソースや状態に依存するテストを並列実行すると競合が発生する恐れがあるため、テストの独立性を保つことが重要です。
次回は、「カバレッジ」に入門します!
参考
Discussion