Go1.24で導入されたt.Contextにシュッと対応する
Go 1.24 から testing
pkg に Context
メソッドが追加されました。
みなさんもう対応されましたか。対応してデメリットはないので、コマンドラインから全置換して対応する方法を紹介します。
TL;DR
- テストコード中で
context.Background
/context.TODO
メソッドを使っているところはt.Context
メソッドに置き換えて問題ない -
find
コマンドとsed
で全置換する -
t.Clenaup
コマンド内では使えないので、そこはcontext.Background
メソッドに戻す - 全置換後はlintで
context.Background
/context.TODO
メソッドを使ったテストコードが追加されるのを防ぐ
find . -type d -name "generated" -prune \
-o -type f -name "*_test.go" ! -name "main_test.go" \
-exec sed -i '' 's/context.TODO()/t.Context()/g' {} +
testing
パッケージに追加されたContext
メソッドの概要
2025年2月にGo1.24がリリースされました。
Go1.24で追加されたメソッドの中にtesting
パッケージのContext
メソッドがあります。
Context returns a context that is canceled just before Cleanup-registered functions are called.
Cleanup functions can wait for any resources that shut down on Context.Done before the test or benchmark completes.
テストやベンチマークの中で、t.Context
やb.Context
メソッドを使うと、テスト完了時に自動でコンテキストキャンセルされるコンテキストが取得できる機能です。
これによりテスト実行時のリソース管理がとても楽になりました。
func TestFoo(t *testing.T) {
ctx := t.Context()
}
テストコードの中でcontext.Background
メソッドやcontext.TODO
メソッドを使ってコンテキストを取得していたコードは基本的にt.Context
に置き換えて問題ありません。
コマンドラインから全置換する
しかし、既存プロダクトで既にたくさんのテストコードが書かれている場合(素晴らしい!!)、テストファイルをひとつひとつ手で直していくのはとても時間がかかります。
私はコマンドラインからシュッと全置換して対応しました。そのコマンドを紹介します。
なお、実行環境はmacOS
環境です。他のOS環境では微妙にオプションが異なるかもしれません。
使ったコマンドはfind
コマンドとsed
コマンドです。
次のコマンドをリファクタリングしたいリポジトリのルートで実行します。
find . -type d -name "generated" -prune \
-o -type f -name "*_test.go" ! -name "main_test.go" \
-exec sed -i '' 's/context.Background()/t.Context()/g' {} +
やっていることは以下です。
- カレントディレクトリ以下を対象に検索
-
genenrated
という名前のディレクトリは除外する- OpenAPIなどで自動生成しているファイルがあるディレクトリがあるならばその名前にします
-
*_test.go
というファイル名を対象とする -
main_test.go
というファイルは対象外とする - 検出したファイルの中にある
context.Background
メソッドの呼び出しをすべてt.Context
メソッドに置換する
main_test.go
ファイルを除外しているのはtesting.M
にはContext
メソッドが追加されていないためです。
TestMain(m *testing.M)
関数の中でコンテキストを使っている場合はm.Context()
に置換してもエラーになります。
同様にcontext.TODO
メソッドも全置換します。
find . -type d -name "generated" -prune \
-o -type f -name "*_test.go" ! -name "main_test.go" \
-exec sed -i '' 's/context.TODO()/t.Context()/g' {} +
context
パッケージのimport
が不要になるパターンが大半なので、goimports
コマンドをかけておきます。
goimports -w .
これで大雑把ですが、すべてのテストファイルのt.Context
対応が"ほぼ"完了します。
一部、おそらくケアが必要なパターンがあるのでここでテストコードを実行しておくとよいです。
t.Cleanup
メソッド内ではcontext.Background
メソッドを使う
先ほどのコマンドでコンテキストを全置換した後、テストコードを実行してみます。
もしかしたら一部のテストコードが失敗するようになる可能性があります。
それは、t.Cleanup
メソッド内でコンテキストを使っている場合です。
冒頭で引用したtesting.Context
メソッドの仕様を読めばわかるのですが、たとえt.Cleanup
内でt.Context
メソッドを呼んだとしても、t.Context
メソッドから作成するコンテキストはt.Cleanup
実行時にはキャンセル済みになります。
Context returns a context that is canceled just before Cleanup-registered functions are called.
コンテキストを渡す必要があるメソッドを使ってCleanup処理をしている場合は、t.Cleanup
の中で呼ぶ処理だけcontext.Background
メソッドを使ってコンテキストを作るように戻します。
lintでテストコード内のContextの使い方をチェックする
一度テストコードをすべてメンテしたあとに、新しくcontext.Background
/context.TODO
メソッドを使うテストコードが追加されないように、lintでチェックするようにします。
golangci-lintをCIで実行している場合は、usetesting
を有効にするだけでチェックできます。
linters-settings:
usetesting:
# Enable/disable `context.Background()` detections.
# Disabled if Go < 1.24.
# Default: true
context-background: false
# Enable/disable `context.TODO()` detections.
# Disabled if Go < 1.24.
# Default: true
context-todo: false
おわりに
testing
パッケージに追加されたt.Context
メソッドの対応方法を紹介しました。
testing
パッケージにはt.Setenv
メソッドやt.TempDir
メソッドなど他にも便利なメソッドがあります。
いろいろなパターンのテストコードがどんどん書きやすくなっていて嬉しいです。
その他のGo.1.24で追加された機能は@CIARANAさんの記事などが非常に参考になりました。
余談: AIエージェントでやればいいんじゃないの?
Cursor などの AIエージェントで実行すればt.Cleanup
のケアなども正確に修正できそうです。
しかし、業務コードのリポジトリはテストファイルの数が3桁、4桁になることもあります。
そのようなリポジトリで今回の修正をAIエージェントにやらせると、時間もコストもかなりかかると思われます。
Discussion
t.Cleanup内はcontext.WithoutCancelも選択肢にありますね。
なるほど、それは思いついてませんでした 🙇 ありがとうございます!!