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

[インデックス 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 パッケージの特定のエラー定数に依存していると、以下のような問題が生じる可能性があります。

  1. 不必要な依存関係: text/template のテストが os パッケージに依存する必要がなくなります。これにより、テストのコンパイル時間や実行時のオーバーヘッドがわずかながら削減される可能性があります。
  2. テストの意図の不明瞭さ: テストの目的は、text/template がエラーを適切に処理できることを確認することであり、特定のエラーの種類(os.EPERM)に焦点を当てる必要はありません。カスタムエラーを使用することで、テストの意図がより明確になります。
  3. 将来的な変更への耐性: os.EPERM のセマンティクスが将来的に変更された場合、または異なるプラットフォームでその挙動が異なる場合、テストが意図せず失敗する可能性があります。カスタムエラーを使用することで、このような外部要因からの影響を排除できます。

これらの理由から、テストの堅牢性と独立性を高めるために、os.EPERM への参照を削除し、テスト専用のカスタムエラーに置き換える変更が行われました。

前提知識の解説

このコミットを理解するためには、以下のGo言語の概念と標準ライブラリに関する知識が必要です。

  1. Go言語のエラーハンドリング:

    • Go言語では、エラーは error インターフェースを実装する値として扱われます。
    • 関数は通常、最後の戻り値として error 型の値を返します。エラーがない場合は nil を返します。
    • errors.New("message") 関数は、指定された文字列をエラーメッセージとする新しい error 値を作成します。これは、特定の意味を持つカスタムエラーを定義する一般的な方法です。
    • fmt.Errorf("format string", args...) も同様にエラーを作成しますが、フォーマット文字列を使用してより詳細なエラーメッセージを生成できます。
    • エラーの比較には、errors.Iserrors.As が使用されますが、このコミットの時点(2012年)では、エラーメッセージの文字列比較 (strings.Contains(err.Error(), ...) ) が一般的でした。
  2. os パッケージ:

    • os パッケージは、オペレーティングシステム機能へのプラットフォーム非依存なインターフェースを提供します。これには、ファイルシステム操作、プロセス管理、環境変数へのアクセスなどが含まれます。
    • os パッケージには、os.ErrPermission (Go 1.16以降) や os.EPERM (Go 1.15まで) のような、システムコールによって返される特定のエラーを表す定数が含まれています。これらのエラーは、通常、権限の問題や不正な操作に関連しています。
  3. text/template パッケージ:

    • text/template パッケージは、Go言語でテキストベースのテンプレートを生成するための機能を提供します。これは、HTML、XML、プレーンテキストなどの動的なコンテンツを生成するのに役立ちます。
    • テンプレートは、プレースホルダーや制御構造(条件分岐、ループなど)を含むテキストで構成され、データ構造を適用することで最終的な出力が生成されます。
    • テンプレートの実行中に、データアクセスや関数呼び出しでエラーが発生する可能性があります。このパッケージは、そのようなエラーを適切に捕捉し、報告するメカニズムを提供します。
  4. Go言語のテスト:

    • Go言語のテストは、_test.go で終わるファイルに記述され、testing パッケージを使用します。
    • テスト関数は TestXxx の形式で定義され、*testing.T 型の引数を取ります。
    • t.Fatalf は致命的なエラーを報告し、テストを即座に終了させます。
    • t.Errorf はエラーを報告しますが、テストの実行は継続します。
    • テストの目的は、コードが期待通りに動作することを確認することであり、特定の外部依存性(この場合は os.EPERM)に不必要に結合しないことが望ましいです。

技術的詳細

このコミットの技術的な変更点は以下の通りです。

  1. os パッケージのインポート削除:

    • src/pkg/text/template/exec_test.go ファイルから import "os" の行が削除されました。これは、テストコードが os パッケージの EPERM 定数に依存しなくなったためです。これにより、テストコードの依存関係が簡素化されます。
  2. カスタムエラー変数の導入:

    • var myError = errors.New("my error") という新しいグローバル変数が導入されました。これは、errors.New 関数を使用して、"my error" という文字列をメッセージとする新しい error 型の値を生成しています。この myError が、os.EPERM の代わりに使用されます。
  3. メソッド名の変更とエラーの置き換え:

    • *T 型のレシーバを持つ EPERM メソッドが MyError にリネームされました。
    • このメソッド内で、if error { return true, os.EPERM } の行が if error { return true, myError } に変更されました。これにより、テスト用のエラー生成ロジックが os.EPERM ではなく、新しく定義された myError を返すようになりました。
  4. テストケースの更新:

    • execTests スライス内のテストケースで、{{.EPERM true}}{{.EPERM false}} というテンプレート文字列が、それぞれ {{.MyError true}}{{.MyError false}} に更新されました。これは、メソッド名が変更されたことに伴う修正です。
  5. エラーチェックロジックの更新:

    • 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 ファイル内の以下の部分です。

  1. os パッケージのインポート削除:

    - 	"os"
    
  2. カスタムエラー変数 myError の定義:

    var myError = errors.New("my error")
    
  3. 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
    }
    
  4. 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},
    
  5. 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言語の公式ドキュメント
  • Go言語のエラーハンドリングに関する一般的なプラクティス
  • Go言語のテストに関する情報