Keyboard shortcuts

Press or to navigate between chapters

Press S or / to search in the book

Press ? to show this help

Press Esc to hide this help

[インデックス 11411] ファイルの概要

このコミットは、Go言語の標準ライブラリ path/filepath パッケージのテストコード (path_test.go) における修正です。テスト実行中にエラーが発生した場合に、テストが適切に停止するように変更されています。

コミット

このコミットは、path/filepath パッケージのテストにおいて、エラー発生時のテストの振る舞いを改善することを目的としています。具体的には、t.Errorft.Fatalf に変更したり、エラー発生時に早期リターン (returncontinue) を追加することで、テストがエラーを検出した際にそれ以上処理を続行せず、即座に失敗としてマークされるようにしています。これにより、テストの信頼性が向上し、エラーの根本原因を特定しやすくなります。

GitHub上でのコミットページへのリンク

https://github.com/golang/go/commit/f8a28ecc9f9ab0ca6a65ca4af4f5a7f3256f6a96

元コミット内容

commit f8a28ecc9f9ab0ca6a65ca4af4f5a7f3256f6a96
Author: Rob Pike <r@golang.org>
Date:   Wed Jan 25 20:19:55 2012 -0800

    path/filepath: fix test
    If there's an error, sometimes you need to stop.
    Part of issue 2787.
    
    R=golang-dev, adg
    CC=golang-dev
    https://golang.org/cl/5570068

変更の背景

この変更は、コミットメッセージに「Part of issue 2787」と記載されているように、Go言語のIssueトラッカーで報告された問題2787の一部として行われました。具体的なIssue 2787の内容は、一般的なWeb検索では直接特定できませんでしたが、コミットメッセージの「If there's an error, sometimes you need to stop.」という記述から、テストコードがエラーを検出してもすぐに終了せず、その後の処理が誤った状態で行われてしまう問題があったと推測されます。

従来のテストでは、t.Errorf を使用してエラーを報告しても、テスト関数自体の実行は継続されていました。これにより、最初のエラーが後続のテストロジックに影響を与え、さらなる誤ったエラー報告や、本来検出されるべきではないバグの見逃しにつながる可能性がありました。このコミットは、このような状況を改善し、テストの信頼性とエラー検出の即時性を高めることを目的としています。

前提知識の解説

Go言語のテスト (testing パッケージ)

Go言語には、標準ライブラリとして testing パッケージが提供されており、ユニットテストやベンチマークテストを簡単に記述できます。テストファイルは通常、テスト対象のファイルと同じディレクトリに _test.go というサフィックスを付けて配置されます。

テスト関数は func TestXxx(*testing.T) というシグネチャを持ちます。*testing.T 型の引数 t は、テストの実行中にエラーを報告したり、テストのフローを制御したりするためのメソッドを提供します。

t.Errorft.Fatalf の違い

  • t.Errorf(format string, args ...interface{}): このメソッドは、テスト中にエラーが発生したことを報告しますが、テスト関数の実行は継続されます。複数のエラーを一度のテスト実行で報告したい場合や、エラーが発生しても後続のクリーンアップ処理などを実行したい場合に利用されます。テストは最終的に失敗としてマークされますが、t.Errorf が呼び出された後もコードの実行は続きます。

  • t.Fatalf(format string, args ...interface{}): このメソッドは、t.Errorf と同様にエラーを報告しますが、エラーを報告した直後に現在のテスト関数の実行を停止します。これは、致命的なエラーが発生し、それ以上テストを続行しても意味がない場合や、後続のテストロジックがエラーによって不正な状態になることを防ぎたい場合に利用されます。t.Fatalf が呼び出されると、テスト関数は即座に終了し、その後のコードは実行されません。

path/filepath パッケージと filepath.Walk

path/filepath パッケージは、ファイルパスを操作するためのユーティリティ関数を提供します。OSに依存しないパス操作(結合、分割、クリーンアップなど)を行うことができます。

  • filepath.Walk(root string, walkFn WalkFunc) error: この関数は、指定された root ディレクトリ以下のファイルツリーを再帰的に走査します。走査中に見つかった各ファイルやディレクトリに対して、引数として渡された WalkFunc 型の関数 (walkFn) を呼び出します。WalkFuncfunc(path string, info os.FileInfo, err error) error というシグネチャを持ち、走査中のパス、そのファイル情報、およびエラーを受け取ります。walkFnnil 以外のエラーを返すと、filepath.Walk はそのエラーを返して走査を停止します。

技術的詳細

このコミットの主要な変更点は、テストコードにおけるエラーハンドリングの強化です。

  1. makeTree 関数における早期リターン: makeTree 関数はテスト用のファイルツリーを作成するユーティリティ関数です。ファイル作成時にエラーが発生した場合、以前は t.Errorf でエラーを報告するだけで、関数は続行していました。しかし、ファイル作成に失敗した場合、その後のテストは不正なファイルツリーを前提として実行されることになり、テスト結果が信頼できないものになります。 このコミットでは、os.Create(path) でエラーが発生した場合に t.Errorf でエラーを報告した後、return ステートメントを追加しています。これにより、ファイル作成に失敗した時点で makeTree 関数の実行が停止し、不正な状態でのテスト実行を防ぎます。

  2. TestWalk 関数における t.Fatalf の導入: TestWalk 関数は filepath.Walk の動作をテストします。このテストでは、filepath.Walk がエラーを返すべきではないシナリオや、特定の数のエラーを返すことを期待するシナリオがあります。 以前は、これらのシナリオで期待と異なる結果(エラーが返された、またはエラー数が期待と異なる)になった場合、t.Errorf を使用していました。しかし、これらのエラーはテストの前提条件が満たされていないことを意味するため、その後のテストロジックを続行しても意味がありません。 このコミットでは、これらの箇所で t.Errorft.Fatalf に変更しています。これにより、致命的なエラーが検出された場合、そのテスト関数は即座に終了し、後続の検証ロジックが実行されることなく、テストが失敗としてマークされます。これは、テストの失敗がより明確になり、デバッグが容易になるという利点があります。

  3. TestAbs 関数における continue の導入: TestAbs 関数は filepath.Abs の動作をテストします。このテストでは、様々なパスに対して os.Statfilepath.Abs を呼び出し、その結果を検証しています。 os.Stat(path)filepath.Abs(path) の呼び出しでエラーが発生した場合、以前は t.Errorf でエラーを報告するだけでした。しかし、これらのエラーが発生した場合、その後の absinfo.(*os.FileStat).SameFile(info.(*os.FileStat)) のような検証は、無効な infoabspath を使用して行われることになり、パニックを引き起こしたり、誤ったテスト結果を招く可能性がありました。 このコミットでは、これらのエラー発生時に continue ステートメントを追加しています。これにより、エラーが発生した特定のテストケースはスキップされ、次のテストケースの処理に移ります。これは t.Fatalf のようにテスト関数全体を終了させるのではなく、現在のループのイテレーションのみをスキップするため、他の独立したテストケースの実行を妨げません。

これらの変更は、Go言語のテストにおけるベストプラクティスに沿ったものであり、テストの堅牢性と信頼性を向上させます。

コアとなるコードの変更箇所

--- a/src/pkg/path/filepath/path_test.go
+++ b/src/pkg/path/filepath/path_test.go
@@ -296,6 +296,7 @@ func makeTree(t *testing.T) {\n 			fd, err := os.Create(path)\n 			if err != nil {\n 				t.Errorf("makeTree: %v", err)\n+				return\n 			}\n 			fd.Close()\n 		} else {\
@@ -345,10 +346,10 @@ func TestWalk(t *testing.T) {\
 	// Expect no errors.\n 	err := filepath.Walk(tree.name, markFn)\n 	if err != nil {\n-\t\tt.Errorf("no error expected, found: %s", err)\n+\t\tt.Fatalf("no error expected, found: %s", err)\n 	}\n 	if len(errors) != 0 {\n-\t\tt.Errorf("unexpected errors: %s", errors)\n+\t\tt.Fatalf("unexpected errors: %s", errors)\n 	}\n 	checkMarks(t, true)\n 	errors = errors[0:0]\
@@ -370,7 +371,7 @@ func TestWalk(t *testing.T) {\
 		tree.entries[3].mark--\n 		err := filepath.Walk(tree.name, markFn)\n 		if err != nil {\n-\t\t\tt.Errorf("expected no error return from Walk, %s", err)\n+\t\t\tt.Fatalf("expected no error return from Walk, got %s", err)\n 		}\n 		if len(errors) != 2 {\n 			t.Errorf("expected 2 errors, got %d: %s", len(errors), errors)\
@@ -389,7 +390,7 @@ func TestWalk(t *testing.T) {\
 		clear = false // error will stop processing\n 		err = filepath.Walk(tree.name, markFn)\n 		if err == nil {\n-\t\t\tt.Errorf("expected error return from Walk")\n+\t\t\tt.Fatalf("expected error return from Walk")\n 		}\n 		if len(errors) != 1 {\n 			t.Errorf("expected 1 error, got %d: %s", len(errors), errors)\
@@ -657,11 +658,13 @@ func TestAbs(t *testing.T) {\
 		info, err := os.Stat(path)\n 		if err != nil {\n 			t.Errorf("%s: %s", path, err)\n+			continue\n 		}\n \n 		abspath, err := filepath.Abs(path)\n 		if err != nil {\n 			t.Errorf("Abs(%q) error: %v", path, err)\n+			continue\n 		}\n 		absinfo, err := os.Stat(abspath)\n 		if err != nil || !absinfo.(*os.FileStat).SameFile(info.(*os.FileStat)) {\

コアとなるコードの解説

src/pkg/path/filepath/path_test.go

func makeTree(t *testing.T) 内の変更

 			fd, err := os.Create(path)
 			if err != nil {
 				t.Errorf("makeTree: %v", err)
 				return // 追加
 			}
 			fd.Close()
  • 変更前: os.Create でファイル作成に失敗した場合、t.Errorf でエラーを報告するものの、関数はそのまま続行していました。
  • 変更後: t.Errorf の後に return が追加されました。これにより、ファイル作成が失敗した時点で makeTree 関数の実行が即座に終了します。これは、テスト用のファイルツリーが正しく構築できない場合、その後のテストが意味をなさなくなるため、早期にテストを終了させることで、誤ったテスト結果や後続のパニックを防ぎます。

func TestWalk(t *testing.T) 内の変更 (複数箇所)

 	// Expect no errors.
 	err := filepath.Walk(tree.name, markFn)
 	if err != nil {
-		t.Errorf("no error expected, found: %s", err)
+		t.Fatalf("no error expected, found: %s", err) // 変更
 	}
 	if len(errors) != 0 {
-		t.Errorf("unexpected errors: %s", errors)
+		t.Fatalf("unexpected errors: %s", errors) // 変更
 	}
 		tree.entries[3].mark--
 		err := filepath.Walk(tree.name, markFn)
 		if err != nil {
-			t.Errorf("expected no error return from Walk, %s", err)
+			t.Fatalf("expected no error return from Walk, got %s", err) // 変更
 		}
 		clear = false // error will stop processing
 		err = filepath.Walk(tree.name, markFn)
 		if err == nil {
-			t.Errorf("expected error return from Walk")
+			t.Fatalf("expected error return from Walk") // 変更
 		}
  • 変更前: filepath.Walk の結果が期待と異なる場合(エラーが返されるべきでないのに返された、またはエラー数が期待と異なるなど)、t.Errorf を使用していました。
  • 変更後: これらの箇所で t.Errorft.Fatalf に変更されました。t.Fatalf はエラーを報告した直後に現在のテスト関数を終了させるため、これらの致命的なテスト失敗条件が満たされた場合、それ以上テストを続行することなく、即座にテストが失敗としてマークされます。これにより、テストの失敗がより明確になり、デバッグの効率が向上します。

func TestAbs(t *testing.T) 内の変更

 		info, err := os.Stat(path)
 		if err != nil {
 			t.Errorf("%s: %s", path, err)
 			continue // 追加
 		}
 
 		abspath, err := filepath.Abs(path)
 		if err != nil {
 			t.Errorf("Abs(%q) error: %v", path, err)
 			continue // 追加
 		}
  • 変更前: os.Statfilepath.Abs の呼び出しでエラーが発生した場合、t.Errorf でエラーを報告するものの、ループは続行していました。これにより、エラーが発生したにもかかわらず、その後の absinfo.(*os.FileStat).SameFile のような検証が不正な値で行われる可能性がありました。
  • 変更後: エラー発生時に continue が追加されました。これにより、os.Stat または filepath.Abs でエラーが発生した特定のテストケースはスキップされ、ループの次のイテレーションに進みます。これは t.Fatalf のようにテスト関数全体を終了させるのではなく、現在のテストケースのみをスキップするため、他の独立したテストケースの実行を妨げずに、不正な状態での検証を防ぎます。

関連リンク

参考にした情報源リンク