Upgrade to Pro — share decks privately, control downloads, hide ads and more …

近頃の気になるGo testingパッケージ

Avatar for kuro kuro
July 01, 2025
100

近頃の気になるGo testingパッケージ

CA.go#16 LT大会で使用したスライドです。
参考資料
- Go 1.20 Release Notes(https://go.dev/doc/go1.20)
- Go 1.24 Release Notes(https://go.dev/doc/go1.24)
- Go 1.25 Release Notes(https://go.dev/doc/go1.25)
- issue: testing: Add T.Output() etc(https://github.com/golang/go/issues/59928)
- issue: testing: add TB.Chdir(https://github.com/golang/go/issues/62516)
- issue: testing/synctest: replace Run with Test(https://github.com/golang/go/issues/73567)
- Go 1.22リリース連載 vet, log/slog, testing/slogtest | フューチャー技術ブログ(https://future-architect.github.io/articles/20240205a/)

Avatar for kuro

kuro

July 01, 2025
Tweet

Transcript

  1. 従来のベンチマーク func BenchmarkOld(b *testing.B) { data := prepareExpensiveData() b.ResetTimer() for

    i := 0; i < b.N; i++ {  result := processData(data) } } b.ResetTimer() を忘れがち b.N の誤用 5 B.Loop() (Go 1.24)
  2. 新しいベンチマーク func BenchmarkNew(b *testing.B) { data := prepareExpensiveData() for b.Loop()

    { result := processData(data) } } タイマーを自動でリセットして、終了時に停止 b.N を使わなくて済む 6 B.Loop() (Go 1.24)
  3. Go1.24 で synctest.Run(f func()) →Go1.25 で synctest.Test(t *testing.T, f func(*testing.T))

    に変更された。( 厳密には deprecated になった) synctest.Test の引数となるtesting.T によって、T.Cleanup() の実 行タイミングや T.Context() のDone チャネルをバブルと結びつける ことができるようになった。 synctest.Test() の命名の理由の一つに testing.T を使うからという のがあるらしい。 9 synctest.Test() になった理由
  4. func TestContextWithTimeout(t *testing.T) { synctest.Test(t, func(t *testing.T) { const timeout

    = 5 * time.Second ctx, cancel := context.WithTimeout(t.Context(), timeout) defer cancel() // タイムアウト直前まで待機 time.Sleep(timeout - time.Nanosecond) synctest.Wait() if err := ctx.Err(); err != nil { t.Fatalf("before timeout: ctx.Err() = %v, want nil\n", err) // タイムアウトを超過させる time.Sleep(time.Nanosecond) synctest.Wait() if err := ctx.Err(); err != context.DeadlineExceeded { t.Fatalf("after timeout: ctx.Err() = %v, want DeadlineExceeded\n", err) } }) } 10 コード例
  5. func TestWithContext(t *testing.T) { ctx := t.Context() // テスト終了時に自動キャンセル server

    := startTestServer(ctx) // テスト本体の処理 } テスト終了時に自動でContext がキャンセルされる。 12 T.Context() (Go 1.24)
  6. func TestFileOperations(t *testing.T) { t.Chdir("testdata") files, _ := os.ReadDir(".") for

    _, file := range files { data, _ := os.ReadFile(file.Name()) processFile(data) } } テストごとに一時的なディレクトリを作成することができる。 テスト終了時に自動でディレクトリがクリーンアップされる。 (t.Cleanup() 不要) ただ、並列テストで干渉し合う可能性がある。 13 T.Chdir() (Go 1.24)
  7. func InitDB() *sql.DB { if testing.Testing() { return sql.Open("sqlite3", ":memory:")

    } return sql.Open("postgres", os.Getenv("DATABASE_URL")) } ビルドタグや環境変数なしで、テスト時の切り替えが可能 14 testing.Testing() (Go 1.21)
  8. func TestPaymentAPI(t *testing.T) { t.Attr("component", "payment") t.Attr("priority", "critical") result :=

    processPayment() if !result.Success { t.Attr("error_code", result.ErrorCode) } } // 出力: // === ATTR TestPaymentAPI component payment // === ATTR TestPaymentAPI priority critical 任意のメタデータを埋め込むことができる。 16 T.Attr() (Go 1.25)
  9. func TestWithCustomOutput(t *testing.T) { // ファイル名・行番号なしのログ出力 writer := t.Output() //

    JSONログを出力 encoder := json.NewEncoder(writer) encoder.Encode(map[string]interface{}{ "test": "TestWithCustomOutput", "timestamp": time.Now(), }) } t.Log() と同じ出力ストリームに対するio.Writer を提供する。 17 T.Output() (Go 1.25)
  10. func TestHandler(t *testing.T) { var buf bytes.Buffer handler := slog.NewJSONHandler(&buf,

    nil) err := slogtest.TestHandler(handler, func() []map[string]any { var entries []map[string]any for _, line := range bytes.Split(buf.Bytes(), []byte("\n")) { if len(line) == 0 { continue } var m map[string]any json.Unmarshal(line, &m) entries = append(entries, m) } return entries }) if err != nil { t.Error(err) } } 19 コード例
  11. func TestAPIServer(t *testing.T) { // 手動でディレクトリ管理 oldDir, _ := os.Getwd()

    os.Chdir("testdata") defer os.Chdir(oldDir) // 環境変数でテストモード判定 os.Setenv("TEST_MODE", "true") defer os.Unsetenv("TEST_MODE") // コンテキストの手動管理 ctx, cancel := context.WithCancel(context.Background()) defer cancel() server := startServer(ctx) // 基本的なログ出力 t.Logf("Server: %s", server.Addr()) 21 Before
  12. // Rate limiterのテスト limiter := NewRateLimiter(10) // 10 req/sec start :=

    time.Now() var wg sync.WaitGroup for range 50 { wg.Add(1) go func() { defer wg.Done() limiter.Allow() }() } wg.Wait() // 実際に約5秒待つ // ログ出力 t.Logf("Duration: %v", time.Since(start)) if err := server.Shutdown(); err != nil { t.Errorf("shutdown failed: %v", err) } } 22
  13. func TestAPIServer(t *testing.T) { // メタデータを埋め込む t.Attr("component", "api-server") t.Attr("type", "integration")

    // 自動的にディレクトリ復元 t.Chdir("testdata") // テスト環境の自動判定 if !testing.Testing() { t.Skip("This should only run in test mode") } ctx := t.Context()  // 自動キャンセル server := startServer(ctx) // 構造化ログ出力 logger := slog.New(slog.NewJSONHandler(t.Output(), nil)) logger.Info("test started", "server", server.Addr()) 23 After
  14. synctest.Test(t, func(t *testing.T) { limiter := NewRateLimiter(10) start := time.Now()

    for range 50 { go func() { limiter.Allow() }() } synctest.Wait() // バブル内のゴルーチンを待機 elapsed := time.Since(start) logger.Info("test completed", "duration", elapsed) }) if t.Failed() { t.Attr("error_context", "rate_limiter_test") } } 24
  15. 1. T.Chdir() - ディレクトリの自動管理 2. testing.Testing() - テスト環境の判定 3. T.Context()

    - コンテキスト管理 4. T.Attr() - メタデータの埋め込み 5. T.Output() - 構造化ログ出力 6. synctest - 並行処理でも安定したテスト 25 リファクタのポイント
  16. 参考資料 Go 1.20 Release Notes Go 1.24 Release Notes Go

    1.25 Release Notes issue: testing: Add T.Output() etc issue: testing: add TB.Chdir issue: testing/synctest: replace Run with Test Go 1.22 リリース連載 vet, log/slog, testing/slogtest | フューチャー 技術ブログ 27