[インデックス 16033] ファイルの概要
このコミットは、Go言語の標準ライブラリであるtesting
パッケージにおける重要な改善を導入しています。具体的には、テスト実行中にパニック(panic)が発生した場合に、そのテストが失敗として適切に報告されるように修正しています。
コミット
commit 174a17e3c6cee14b575c1871c6ce35c1c0806b45
Author: Ewan Chou <coocood@gmail.com>
Date: Mon Apr 1 22:36:41 2013 +1100
testing: report test as failed if the test panics.
Fixes #5149.
R=golang-dev, dave, minux.ma
CC=golang-dev
https://golang.org/cl/8136043
GitHub上でのコミットページへのリンク
https://github.com/golang/go/commit/174a17e3c6cee14b575c1871c6ce35c1c0806b45
元コミット内容
testing: report test as failed if the test panics.
Fixes #5149.
R=golang-dev, dave, minux.ma
CC=golang-dev
https://golang.org/cl/8136043
変更の背景
Go言語のテストフレームワークでは、テスト関数内で予期せぬエラーが発生した場合、通常はt.Error()
やt.Fail()
などのメソッドを呼び出してテストを失敗としてマークします。しかし、Goのプログラムでは、回復不可能なエラーやプログラミング上のバグを示すために「パニック(panic)」が発生することがあります。
このコミットが修正している問題(Issue #5149)は、テスト関数内でパニックが発生した場合、testing
パッケージがそのパニックを適切に捕捉し、テスト結果として「失敗」を報告しないというものでした。パニックは通常、プログラムの実行を即座に停止させるため、テストがパニックによって中断された場合、テストスイート全体がクラッシュしたり、そのテストが「パス」として誤って報告されたりする可能性がありました。これは、テストの信頼性を著しく損なう問題であり、開発者がテストの失敗原因を正確に特定することを困難にしていました。
この変更の目的は、テスト中に発生したパニックを捕捉し、そのテストを明示的に失敗としてマークすることで、テスト結果の正確性と信頼性を向上させることにあります。これにより、開発者はテストのパニックが原因であることを明確に認識し、デバッグプロセスを効率化できるようになります。
前提知識の解説
Go言語のpanic
とrecover
Go言語には、エラーハンドリングのための2つの主要なメカニズムがあります。
- エラー(error): 予期されるが異常な状況(例: ファイルが見つからない、ネットワーク接続が切れた)を扱うためのもので、関数の戻り値として
error
型を返します。これはGoにおける一般的なエラーハンドリングの慣習です。 - パニック(panic): プログラムの実行を停止させる回復不可能なエラー(例: nilポインタのデリファレンス、配列の範囲外アクセス)を示すために使用されます。パニックが発生すると、現在の関数の実行が中断され、遅延関数(
defer
で登録された関数)が実行された後、呼び出し元の関数へとスタックを遡っていきます。最終的にスタックの最上位に到達すると、プログラムはクラッシュします。
panic
が発生した際に、プログラムがクラッシュするのを防ぎ、パニックから回復するためにrecover
組み込み関数が使用されます。recover
はdefer
関数内で呼び出された場合にのみ有効で、パニックが発生した際にrecover
が呼び出されると、パニックの値が返され、パニックのシーケンスが停止し、プログラムの実行が再開されます。
Go言語のtesting
パッケージ
testing
パッケージは、Go言語の標準テストフレームワークです。Goのテストは、_test.go
というサフィックスを持つファイルに記述され、TestXxx
という形式の関数がテスト関数として認識されます。
*testing.T
: テスト関数に渡される構造体で、テストのステータス(成功/失敗)を報告したり、ログを出力したりするためのメソッドを提供します。t.Fail()
: 現在のテストを失敗としてマークしますが、テストの実行は継続します。t.FailNow()
: 現在のテストを失敗としてマークし、テストの実行を即座に停止します。t.Error()
/t.Errorf()
:t.Fail()
を呼び出し、エラーメッセージをログに出力します。t.Fatal()
/t.Fatalf()
:t.FailNow()
を呼び出し、エラーメッセージをログに出力します。
tRunner
関数
testing
パッケージの内部には、個々のテスト関数を実行し、その結果を管理するtRunner
という関数が存在します。この関数は、テスト関数の実行をdefer
とrecover
のブロックで囲むことで、テスト関数内で発生したパニックを捕捉し、テストスイート全体がクラッシュするのを防ぐ役割を担っています。
技術的詳細
このコミットの技術的な核心は、testing
パッケージ内のtRunner
関数にt.Fail()
の呼び出しを追加することです。
tRunner
関数は、各テスト関数をゴルーチンとして実行し、その実行中に発生する可能性のあるパニックを捕捉するためにdefer
とrecover
のメカニズムを使用しています。元の実装では、パニックが捕捉された場合、テストの出力(t.report()
)を生成し、その後捕捉したパニックを再スロー(panic(err)
)していました。これにより、テストスイート全体がクラッシュすることは避けられましたが、個々のテストがパニックによって中断された際に、そのテストが明示的に「失敗」としてマークされることはありませんでした。
この変更により、recover()
がパニックを検出した場合、t.Fail()
が呼び出されます。これにより、テストの実行がパニックによって中断されたとしても、*testing.T
オブジェクトの内部状態が更新され、そのテストが失敗したという情報が正確に記録されるようになります。その後のt.report()
呼び出しで、この失敗状態が適切に報告され、最終的にパニックが再スローされることで、テストランナーは次のテストの実行に移る前に、現在のテストが異常終了したことを認識できます。
この修正は、テストの堅牢性と信頼性を高める上で非常に重要です。テストがパニックによって中断された場合でも、そのテストが失敗として明確に報告されることで、開発者はテストスイートの結果から、どのテストが問題を引き起こしたのかを正確に把握できるようになります。
コアとなるコードの変更箇所
変更はsrc/pkg/testing/testing.go
ファイルの一箇所のみです。
--- a/src/pkg/testing/testing.go
+++ b/src/pkg/testing/testing.go
@@ -337,6 +337,7 @@ func tRunner(t *T, test *InternalTest) {
t.duration = time.Now().Sub(t.start)
// If the test panicked, print any test output before dying.
if err := recover(); err != nil {
+ t.Fail()
t.report()
panic(err)
}
コアとなるコードの解説
変更されたコードスニペットは、tRunner
関数内のdefer
ブロックの一部です。
if err := recover(); err != nil {
t.Fail() // 追加された行
t.report()
panic(err)
}
if err := recover(); err != nil { ... }
: このブロックは、tRunner
が実行しているテスト関数内でパニックが発生した場合に実行されます。recover()
はパニックの値を捕捉し、err
変数に代入します。パニックが発生していなければrecover()
はnil
を返します。t.Fail()
: この行が追加された変更点です。recover()
によってパニックが捕捉された場合、このメソッドが呼び出されます。t.Fail()
は、現在のテスト(t
によって表される)を失敗としてマークします。これにより、テストの最終結果が「失敗」として記録されるようになります。t.report()
: テストの実行時間や出力など、テストに関するレポートを生成します。t.Fail()
が呼び出された後なので、このレポートにはテストが失敗したという情報が含まれることになります。panic(err)
: 捕捉したパニックを再スローします。これは、tRunner
がパニックを捕捉してテストの失敗を記録した後も、そのパニックがテストランナーのさらに上位の層に伝播し、最終的にプログラムが終了する(または別のrecover
によって処理される)ことを保証するためです。これにより、テストスイート全体がパニックによって不正な状態になることを防ぎつつ、個々のテストの失敗を正確に報告できます。
このシンプルな一行の追加により、Goのテストフレームワークは、テスト中のパニックに対してより堅牢で正確な振る舞いをするようになりました。
関連リンク
- Go Issue #5149: https://github.com/golang/go/issues/5149
- Go CL 8136043: https://golang.org/cl/8136043 (Gerrit Code Review)
参考にした情報源リンク
- Go言語の公式ドキュメント:
testing
パッケージ https://pkg.go.dev/testing - Go言語の公式ドキュメント:
panic
とrecover
https://go.dev/blog/defer-panic-and-recover - Go言語のソースコード:
src/pkg/testing/testing.go
(コミット時点のバージョン、または現在の最新版)
[インデックス 16033] ファイルの概要
このコミットは、Go言語の標準ライブラリであるtesting
パッケージにおける重要な改善を導入しています。具体的には、テスト実行中にパニック(panic)が発生した場合に、そのテストが失敗として適切に報告されるように修正しています。
コミット
commit 174a17e3c6cee14b575c1871c6ce35c1c0806b45
Author: Ewan Chou <coocood@gmail.com>
Date: Mon Apr 1 22:36:41 2013 +1100
testing: report test as failed if the test panics.
Fixes #5149.
R=golang-dev, dave, minux.ma
CC=golang-dev
https://golang.org/cl/8136043
GitHub上でのコミットページへのリンク
https://github.com/golang/go/commit/174a17e3c6cee14b575c1871c6ce35c1c0806b45
元コミット内容
testing: report test as failed if the test panics.
Fixes #5149.
R=golang-dev, dave, minux.ma
CC=golang-dev
https://golang.org/cl/8136043
変更の背景
Go言語のテストフレームワークでは、テスト関数内で予期せぬエラーが発生した場合、通常はt.Error()
やt.Fail()
などのメソッドを呼び出してテストを失敗としてマークします。しかし、Goのプログラムでは、回復不可能なエラーやプログラミング上のバグを示すために「パニック(panic)」が発生することがあります。
このコミットが修正している問題(Issue #5149)は、テスト関数内でパニックが発生した場合、testing
パッケージがそのパニックを適切に捕捉し、テスト結果として「失敗」を報告しないというものでした。パニックは通常、プログラムの実行を即座に停止させるため、テストがパニックによって中断された場合、テストスイート全体がクラッシュしたり、そのテストが「パス」として誤って報告されたりする可能性がありました。これは、テストの信頼性を著しく損なう問題であり、開発者がテストの失敗原因を正確に特定することを困難にしていました。
この変更の目的は、テスト中に発生したパニックを捕捉し、そのテストを明示的に失敗としてマークすることで、テスト結果の正確性と信頼性を向上させることにあります。これにより、開発者はテストのパニックが原因であることを明確に認識し、デバッグプロセスを効率化できるようになります。
前提知識の解説
Go言語のpanic
とrecover
Go言語には、エラーハンドリングのための2つの主要なメカニズムがあります。
- エラー(error): 予期されるが異常な状況(例: ファイルが見つからない、ネットワーク接続が切れた)を扱うためのもので、関数の戻り値として
error
型を返します。これはGoにおける一般的なエラーハンドリングの慣習です。 - パニック(panic): プログラムの実行を停止させる回復不可能なエラー(例: nilポインタのデリファレンス、配列の範囲外アクセス)を示すために使用されます。パニックが発生すると、現在の関数の実行が中断され、遅延関数(
defer
で登録された関数)が実行された後、呼び出し元の関数へとスタックを遡っていきます。最終的にスタックの最上位に到達すると、プログラムはクラッシュします。
panic
が発生した際に、プログラムがクラッシュするのを防ぎ、パニックから回復するためにrecover
組み込み関数が使用されます。recover
はdefer
関数内で呼び出された場合にのみ有効で、パニックが発生した際にrecover
が呼び出されると、パニックの値が返され、パニックのシーケンスが停止し、プログラムの実行が再開されます。
Go言語のtesting
パッケージ
testing
パッケージは、Go言語の標準テストフレームワークです。Goのテストは、_test.go
というサフィックスを持つファイルに記述され、TestXxx
という形式の関数がテスト関数として認識されます。
*testing.T
: テスト関数に渡される構造体で、テストのステータス(成功/失敗)を報告したり、ログを出力したりするためのメソッドを提供します。t.Fail()
: 現在のテストを失敗としてマークしますが、テストの実行は継続します。t.FailNow()
: 現在のテストを失敗としてマークし、テストの実行を即座に停止します。t.Error()
/t.Errorf()
:t.Fail()
を呼び出し、エラーメッセージをログに出力します。t.Fatal()
/t.Fatalf()
:t.FailNow()
を呼び出し、エラーメッセージをログに出力します。
tRunner
関数
testing
パッケージの内部には、個々のテスト関数を実行し、その結果を管理するtRunner
という関数が存在します。この関数は、テスト関数の実行をゴルーチンとして実行し、その実行中に発生する可能性のあるパニックを捕捉するためにdefer
とrecover
のメカニズムを使用しています。
技術的詳細
このコミットの技術的な核心は、testing
パッケージ内のtRunner
関数にt.Fail()
の呼び出しを追加することです。
tRunner
関数は、各テスト関数をゴルーチンとして実行し、その実行中に発生する可能性のあるパニックを捕捉するためにdefer
とrecover
のメカニズムを使用しています。元の実装では、パニックが捕捉された場合、テストの出力(t.report()
)を生成し、その後捕捉したパニックを再スロー(panic(err)
)していました。これにより、テストスイート全体がクラッシュすることは避けられましたが、個々のテストがパニックによって中断された際に、そのテストが明示的に「失敗」としてマークされることはありませんでした。
この変更により、recover()
がパニックを検出した場合、t.Fail()
が呼び出されます。これにより、テストの実行がパニックによって中断されたとしても、*testing.T
オブジェクトの内部状態が更新され、そのテストが失敗したという情報が正確に記録されるようになります。その後のt.report()
呼び出しで、この失敗状態が適切に報告され、最終的にパニックが再スローされることで、テストランナーは次のテストの実行に移る前に、現在のテストが異常終了したことを認識できます。
この修正は、テストの堅牢性と信頼性を高める上で非常に重要です。テストがパニックによって中断された場合でも、そのテストが失敗として明確に報告されることで、開発者はテストスイートの結果から、どのテストが問題を引き起こしたのかを正確に把握できるようになります。
コアとなるコードの変更箇所
変更はsrc/pkg/testing/testing.go
ファイルの一箇所のみです。
--- a/src/pkg/testing/testing.go
+++ b/src/pkg/testing/testing.go
@@ -337,6 +337,7 @@ func tRunner(t *T, test *InternalTest) {
t.duration = time.Now().Sub(t.start)
// If the test panicked, print any test output before dying.
if err := recover(); err != nil {
+ t.Fail()
t.report()
panic(err)
}
コアとなるコードの解説
変更されたコードスニペットは、tRunner
関数内のdefer
ブロックの一部です。
if err := recover(); err != nil {
t.Fail() // 追加された行
t.report()
panic(err)
}
if err := recover(); err != nil { ... }
: このブロックは、tRunner
が実行しているテスト関数内でパニックが発生した場合に実行されます。recover()
はパニックの値を捕捉し、err
変数に代入します。パニックが発生していなければrecover()
はnil
を返します。t.Fail()
: この行が追加された変更点です。recover()
によってパニックが捕捉された場合、このメソッドが呼び出されます。t.Fail()
は、現在のテスト(t
によって表される)を失敗としてマークします。これにより、テストの最終結果が「失敗」として記録されるようになります。t.report()
: テストの実行時間や出力など、テストに関するレポートを生成します。t.Fail()
が呼び出された後なので、このレポートにはテストが失敗したという情報が含まれることになります。panic(err)
: 捕捉したパニックを再スローします。これは、tRunner
がパニックを捕捉してテストの失敗を記録した後も、そのパニックがテストランナーのさらに上位の層に伝播し、最終的にプログラムが終了する(または別のrecover
によって処理される)ことを保証するためです。これにより、テストスイート全体がパニックによって不正な状態になることを防ぎつつ、個々のテストの失敗を正確に報告できます。
このシンプルな一行の追加により、Goのテストフレームワークは、テスト中のパニックに対してより堅牢で正確な振る舞いをするようになりました。
関連リンク
- Go Issue #5149: https://github.com/golang/go/issues/5149
- Go CL 8136043: https://golang.org/cl/8136043 (Gerrit Code Review)
参考にした情報源リンク
- Go言語の公式ドキュメント:
testing
パッケージ https://pkg.go.dev/testing - Go言語の公式ドキュメント:
panic
とrecover
https://go.dev/blog/defer-panic-and-recover - Go言語のソースコード:
src/pkg/testing/testing.go
(コミット時点のバージョン、または現在の最新版)