[インデックス 11854] ファイルの概要
このコミットは、Go言語の標準ライブラリである text/template
パッケージのテストファイル src/pkg/text/template/exec_test.go
における変更です。具体的には、テストコード内で os.EPERM
という特定のエラー定数への参照を削除し、代わりにカスタムエラー myError
を導入しています。これにより、テストの独立性と堅牢性が向上しています。
コミット
commit 47424d90ec11d9e72088b661a52e769b8074be70
Author: Rob Pike <r@golang.org>
Date: Tue Feb 14 07:11:39 2012 +1100
text/template: drop reference to os.EPERM in the test
R=golang-dev, gri
CC=golang-dev
https://golang.org/cl/5654077
GitHub上でのコミットページへのリンク
https://github.com/golang/go/commit/47424d90ec11d9e72088b661a52e769b8074be70
元コミット内容
--- a/src/pkg/text/template/exec_test.go
+++ b/src/pkg/text/template/exec_test.go
@@ -9,7 +9,6 @@ import (
"errors"
"flag"
"fmt"
- "os"
"reflect"
"strings"
"testing"
@@ -168,10 +167,12 @@ func (t *T) MAdd(a int, b []int) []int {
return v
}
-// EPERM returns a value and an error according to its argument.
-func (t *T) EPERM(error bool) (bool, error) {
+var myError = errors.New("my error")
+
+// MyError returns a value and an error according to its argument.
+func (t *T) MyError(error bool) (bool, error) {
if error {
-\t\treturn true, os.EPERM
+\t\treturn true, myError
}
return false, nil
}
@@ -417,8 +418,8 @@ var execTests = []execTest{
{"or as if false", `{{or .SIEmpty "slice is empty"}}`, "slice is empty", tVal, true},
// Error handling.
-\t{"error method, error", "{{.EPERM true}}", "", tVal, false},
-\t{"error method, no error", "{{.EPERM false}}", "false", tVal, true},
+\t{"error method, error", "{{.MyError true}}", "", tVal, false},
+\t{"error method, no error", "{{.MyError false}}", "false", tVal, true},
// Fixed bugs.
// Must separate dot and receiver; otherwise args are evaluated with dot set to variable.
@@ -565,18 +566,18 @@ func TestDelims(t *testing.T) {
func TestExecuteError(t *testing.T) {
b := new(bytes.Buffer)
tmpl := New("error")
-\t_, err := tmpl.Parse("{{.EPERM true}}")
+\t_, err := tmpl.Parse("{{.MyError true}}")
if err != nil {
t.Fatalf("parse error: %s", err)
}
err = tmpl.Execute(b, tVal)
if err == nil {
t.Errorf("expected error; got none")
-\t} else if !strings.Contains(err.Error(), os.EPERM.Error()) {
+\t} else if !strings.Contains(err.Error(), myError.Error()) {
if *debug {
fmt.Printf("test execute error: %s\\n", err)
}
-\t\tt.Errorf("expected os.EPERM; got %s", err)
+\t\tt.Errorf("expected myError; got %s", err)
}
}
変更の背景
このコミットの背景には、テストコードの独立性と保守性の向上が挙げられます。元のコードでは、text/template
パッケージのテストにおいて、エラーハンドリングのテストケースで os.EPERM
という os
パッケージが提供する特定のエラー定数を使用していました。
os.EPERM
は "Operation not permitted" を意味するシステムコールエラーであり、ファイルシステム操作など、オペレーティングシステムレベルの権限に関連するエラーを示すために使用されます。しかし、text/template
パッケージ自体は、直接的にオペレーティングシステムレベルの権限エラーを扱うものではありません。テストコードが os
パッケージの特定のエラー定数に依存していると、以下のような問題が生じる可能性があります。
- 不必要な依存関係:
text/template
のテストがos
パッケージに依存する必要がなくなります。これにより、テストのコンパイル時間や実行時のオーバーヘッドがわずかながら削減される可能性があります。 - テストの意図の不明瞭さ: テストの目的は、
text/template
がエラーを適切に処理できることを確認することであり、特定のエラーの種類(os.EPERM
)に焦点を当てる必要はありません。カスタムエラーを使用することで、テストの意図がより明確になります。 - 将来的な変更への耐性:
os.EPERM
のセマンティクスが将来的に変更された場合、または異なるプラットフォームでその挙動が異なる場合、テストが意図せず失敗する可能性があります。カスタムエラーを使用することで、このような外部要因からの影響を排除できます。
これらの理由から、テストの堅牢性と独立性を高めるために、os.EPERM
への参照を削除し、テスト専用のカスタムエラーに置き換える変更が行われました。
前提知識の解説
このコミットを理解するためには、以下のGo言語の概念と標準ライブラリに関する知識が必要です。
-
Go言語のエラーハンドリング:
- Go言語では、エラーは
error
インターフェースを実装する値として扱われます。 - 関数は通常、最後の戻り値として
error
型の値を返します。エラーがない場合はnil
を返します。 errors.New("message")
関数は、指定された文字列をエラーメッセージとする新しいerror
値を作成します。これは、特定の意味を持つカスタムエラーを定義する一般的な方法です。fmt.Errorf("format string", args...)
も同様にエラーを作成しますが、フォーマット文字列を使用してより詳細なエラーメッセージを生成できます。- エラーの比較には、
errors.Is
やerrors.As
が使用されますが、このコミットの時点(2012年)では、エラーメッセージの文字列比較 (strings.Contains(err.Error(), ...)
) が一般的でした。
- Go言語では、エラーは
-
os
パッケージ:os
パッケージは、オペレーティングシステム機能へのプラットフォーム非依存なインターフェースを提供します。これには、ファイルシステム操作、プロセス管理、環境変数へのアクセスなどが含まれます。os
パッケージには、os.ErrPermission
(Go 1.16以降) やos.EPERM
(Go 1.15まで) のような、システムコールによって返される特定のエラーを表す定数が含まれています。これらのエラーは、通常、権限の問題や不正な操作に関連しています。
-
text/template
パッケージ:text/template
パッケージは、Go言語でテキストベースのテンプレートを生成するための機能を提供します。これは、HTML、XML、プレーンテキストなどの動的なコンテンツを生成するのに役立ちます。- テンプレートは、プレースホルダーや制御構造(条件分岐、ループなど)を含むテキストで構成され、データ構造を適用することで最終的な出力が生成されます。
- テンプレートの実行中に、データアクセスや関数呼び出しでエラーが発生する可能性があります。このパッケージは、そのようなエラーを適切に捕捉し、報告するメカニズムを提供します。
-
Go言語のテスト:
- Go言語のテストは、
_test.go
で終わるファイルに記述され、testing
パッケージを使用します。 - テスト関数は
TestXxx
の形式で定義され、*testing.T
型の引数を取ります。 t.Fatalf
は致命的なエラーを報告し、テストを即座に終了させます。t.Errorf
はエラーを報告しますが、テストの実行は継続します。- テストの目的は、コードが期待通りに動作することを確認することであり、特定の外部依存性(この場合は
os.EPERM
)に不必要に結合しないことが望ましいです。
- Go言語のテストは、
技術的詳細
このコミットの技術的な変更点は以下の通りです。
-
os
パッケージのインポート削除:src/pkg/text/template/exec_test.go
ファイルからimport "os"
の行が削除されました。これは、テストコードがos
パッケージのEPERM
定数に依存しなくなったためです。これにより、テストコードの依存関係が簡素化されます。
-
カスタムエラー変数の導入:
var myError = errors.New("my error")
という新しいグローバル変数が導入されました。これは、errors.New
関数を使用して、"my error" という文字列をメッセージとする新しいerror
型の値を生成しています。このmyError
が、os.EPERM
の代わりに使用されます。
-
メソッド名の変更とエラーの置き換え:
*T
型のレシーバを持つEPERM
メソッドがMyError
にリネームされました。- このメソッド内で、
if error { return true, os.EPERM }
の行がif error { return true, myError }
に変更されました。これにより、テスト用のエラー生成ロジックがos.EPERM
ではなく、新しく定義されたmyError
を返すようになりました。
-
テストケースの更新:
execTests
スライス内のテストケースで、{{.EPERM true}}
と{{.EPERM false}}
というテンプレート文字列が、それぞれ{{.MyError true}}
と{{.MyError false}}
に更新されました。これは、メソッド名が変更されたことに伴う修正です。
-
エラーチェックロジックの更新:
TestExecuteError
関数内で、テンプレートの実行結果として期待されるエラーのチェックロジックが変更されました。- 元の
!strings.Contains(err.Error(), os.EPERM.Error())
という条件が、!strings.Contains(err.Error(), myError.Error())
に変更されました。これは、os.EPERM
のエラーメッセージではなく、myError
のエラーメッセージ("my error")が含まれていることを確認するようにテストが更新されたことを意味します。 - 同様に、エラーメッセージの出力も
expected os.EPERM; got %s
からexpected myError; got %s
に変更されました。
これらの変更は、text/template
パッケージのテストが、特定のシステムエラー定数に依存することなく、一般的なエラーハンドリングのシナリオをテストできるようにすることを目的としています。これにより、テストの独立性が高まり、より純粋に text/template
パッケージ自体の動作を検証できるようになります。
コアとなるコードの変更箇所
このコミットにおけるコアとなるコードの変更箇所は、src/pkg/text/template/exec_test.go
ファイル内の以下の部分です。
-
os
パッケージのインポート削除:- "os"
-
カスタムエラー変数
myError
の定義:var myError = errors.New("my error")
-
EPERM
メソッドからMyError
メソッドへの変更と、os.EPERM
からmyError
への置き換え:-// EPERM returns a value and an error according to its argument. -func (t *T) EPERM(error bool) (bool, error) { + +// MyError returns a value and an error according to its argument. +func (t *T) MyError(error bool) (bool, error) { if error { -\t\treturn true, os.EPERM +\t\treturn true, myError } return false, nil }
-
execTests
内のテンプレート文字列の更新:-\t{"error method, error", "{{.EPERM true}}", "", tVal, false}, -\t{"error method, no error", "{{.EPERM false}}", "false", tVal, true}, +\t{"error method, error", "{{.MyError true}}", "", tVal, false}, +\t{"error method, no error", "{{.MyError false}}", "false", tVal, true},
-
TestExecuteError
関数内のエラーチェックロジックの更新:-\t_, err := tmpl.Parse("{{.EPERM true}}") +\t_, err := tmpl.Parse("{{.MyError true}}") if err != nil { t.Fatalf("parse error: %s", err) } err = tmpl.Execute(b, tVal) if err == nil { t.Errorf("expected error; got none") -\t} else if !strings.Contains(err.Error(), os.EPERM.Error()) { +\t} else if !strings.Contains(err.Error(), myError.Error()) { if *debug { fmt.Printf("test execute error: %s\\n", err) } -\t\tt.Errorf("expected os.EPERM; got %s", err) +\t\tt.Errorf("expected myError; got %s", err) }
コアとなるコードの解説
このコミットの核心は、テストコードがGo言語の標準ライブラリの特定のエラー定数(os.EPERM
)に依存するのをやめ、テストの目的により適したカスタムエラー(myError
)を使用するように変更した点にあります。
-
os
パッケージのインポート削除: これは、テストコードがos
パッケージの機能に直接依存する必要がなくなったことを明確に示しています。テストのスコープをtext/template
パッケージの機能に限定し、外部の依存関係を減らすことで、テストの独立性と保守性が向上します。 -
var myError = errors.New("my error")
: この行は、テスト専用の新しいエラーインスタンスを定義しています。errors.New
は、シンプルな文字列メッセージを持つエラーを作成するGoの標準的な方法です。このmyError
は、text/template
がエラーを適切に伝播し、処理できることを検証するためのプレースホルダーとして機能します。os.EPERM
のようなシステム固有のエラーではなく、汎用的なエラーを使用することで、テストがより抽象的で、text/template
のエラーハンドリングメカニズム自体に焦点を当てられるようになります。 -
EPERM
からMyError
へのリネームとos.EPERM
からmyError
への置き換え:EPERM
メソッドは、テンプレート内で呼び出されることでエラーを発生させるためのモック(模擬)メソッドでした。その名前がos.EPERM
を連想させるため、カスタムエラーを使用する変更に合わせてMyError
に変更されました。- このメソッドが返すエラーが
os.EPERM
からmyError
に変更されたことで、テストはもはやオペレーティングシステムのエラーコードに結合されなくなりました。これにより、テストはより自己完結的になり、os
パッケージの内部実装やプラットフォーム間の差異に影響されることなく、text/template
のエラー処理ロジックを純粋にテストできます。
-
テストケースとエラーチェックの更新:
- テンプレート文字列内のメソッド名が
EPERM
からMyError
に変更されたのは、メソッドのリネームに合わせた単純な修正です。 TestExecuteError
関数内のエラーチェックロジックがstrings.Contains(err.Error(), os.EPERM.Error())
からstrings.Contains(err.Error(), myError.Error())
に変更されたことは非常に重要です。これは、テストが実際にmyError
が伝播されていることを確認していることを意味します。err.Error()
はエラーの文字列表現を返し、myError.Error()
は "my error" という文字列を返します。この変更により、テストはtext/template
がカスタムエラーを正しく処理し、そのメッセージをエラーチェーンに含めることができることを検証します。
- テンプレート文字列内のメソッド名が
全体として、このコミットは、テストコードの品質と独立性を向上させるための典型的なリファクタリングの例です。外部の具体的なエラー定数への依存を排除し、テストの目的により合致したカスタムエラーを使用することで、テストはより堅牢で、理解しやすく、将来の変更に対して耐性を持つようになります。
関連リンク
- Go言語の
text/template
パッケージのドキュメント: https://pkg.go.dev/text/template - Go言語の
errors
パッケージのドキュメント: https://pkg.go.dev/errors - Go言語の
os
パッケージのドキュメント: https://pkg.go.dev/os - Go言語の
testing
パッケージのドキュメント: https://pkg.go.dev/testing
参考にした情報源リンク
- Go言語の公式ドキュメント
- Go言語のエラーハンドリングに関する一般的なプラクティス
- Go言語のテストに関する情報