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も選択肢にありますね。
なるほど、それは思いついてませんでした 🙇 ありがとうございます!!