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

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

このコミットは、src/pkg/bufio/scan_test.go ファイルに対する変更です。具体的には、テストコード内で使用されているエラー報告関数 t.Fatal の呼び出しが t.Fatalf に修正されています。これにより、エラーメッセージのフォーマットが正しく行われるようになります。

コミット

bufio: Fixed call to Fatal, should be Fatalf.

LGTM=iant
R=golang-codereviews, iant, bradfitz
CC=golang-codereviews
https://golang.org/cl/107390044

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

https://github.com/golang/go/commit/22c3f67cd637d6d99bafc04867e7e4f8833f7f16

元コミット内容

bufio: Fixed call to Fatal, should be Fatalf.

LGTM=iant
R=golang-codereviews, iant, bradfitz
CC=golang-codereviews
https://golang.org/cl/107390044

変更の背景

このコミットの背景には、Go言語の標準ライブラリである bufio パッケージのテストコードにおける、エラー報告の誤った使用がありました。Goのテストフレームワークである testing パッケージには、テストの失敗を報告するためのいくつかの関数が提供されています。その中でも t.Fatalt.Fatalf は、テストを失敗としてマークし、現在のテスト関数を即座に終了させる点で共通していますが、引数の扱いが異なります。

元のコードでは、t.Fatal("scan failed: %v", scanner.Err()) のように、フォーマット文字列とそれに続く引数を t.Fatal に渡していました。しかし、t.Fatalfmt.Print と同様に引数をそのまま出力するだけで、fmt.Printf のようにフォーマット文字列を解釈して引数を埋め込む機能は持っていません。このため、期待されるエラーメッセージ(例: "scan failed: some error")ではなく、"scan failed: %v some error" のように、フォーマット指定子 %v がそのまま表示されてしまうという問題がありました。

この問題を修正し、意図した通りにフォーマットされたエラーメッセージを出力するために、t.Fatalf への変更が必要とされました。t.Fatalffmt.Printf と同様にフォーマット文字列と可変長引数を受け取り、それらを適切にフォーマットして出力します。

前提知識の解説

Go言語のtestingパッケージ

Go言語には、ユニットテストを記述するための標準パッケージ testing が用意されています。テスト関数は func TestXxx(t *testing.T) の形式で定義され、*testing.T 型の引数 t を通じてテストの制御や結果の報告を行います。

*testing.T オブジェクトには、テストの失敗を報告するためのいくつかのメソッドがあります。

  • t.Error(args ...interface{}): テストを失敗としてマークしますが、テスト関数の実行は継続します。引数は fmt.Print と同様に扱われます。
  • t.Errorf(format string, args ...interface{}): t.Error と同様にテストを失敗としてマークし、テスト関数の実行は継続します。引数は fmt.Printf と同様にフォーマット文字列と可変長引数として扱われます。
  • t.Fatal(args ...interface{}): テストを失敗としてマークし、現在のテスト関数の実行を即座に終了させます。引数は fmt.Print と同様に扱われます。
  • t.Fatalf(format string, args ...interface{}): t.Fatal と同様にテストを失敗としてマークし、現在のテスト関数の実行を即座に終了させます。引数は fmt.Printf と同様にフォーマット文字列と可変長引数として扱われます。

このコミットでは、t.Fatalt.Fatalf の違いが重要になります。

bufioパッケージのScanner

bufio パッケージは、バッファリングされたI/O操作を提供します。その中でも bufio.Scanner は、入力ストリーム(io.Reader)からデータを読み込み、定義された区切り文字(例えば、行、単語など)に基づいてトークンに分割するための便利なインターフェースを提供します。

Scanner の主なメソッドには以下のようなものがあります。

  • scanner.Scan() bool: 次のトークンを読み込みます。読み込みが成功した場合は true を、エラーが発生したか入力の終わりに達した場合は false を返します。
  • scanner.Text() string: Scan メソッドによって読み込まれた最新のトークンを文字列として返します。
  • scanner.Err() error: Scan メソッドが false を返した場合に、発生したエラーを返します。エラーがない場合は nil を返します。
  • scanner.MaxTokenSize(int): スキャンされるトークンの最大サイズを設定します。
  • scanner.Split(bufio.SplitFunc): トークンを分割するための関数(SplitFunc)を設定します。bufio.ScanWordsbufio.ScanLines など、いくつかの組み込みの SplitFunc があります。

このコミットのテストコードでは、bufio.Scanner を使用して単語の分割処理をテストしており、scanner.Err()scanner.Text() の結果を検証しています。

技術的詳細

このコミットの技術的な核心は、Go言語の testing パッケージにおける FatalFatalf メソッドのセマンティクスの違いを正しく理解し、適用することにあります。

元のコードでは、以下のように記述されていました。

t.Fatal("scan failed: %v", scanner.Err())
t.Fatal("unexpected token: %v", token)

ここで t.Fatal は、可変長引数を受け取りますが、それらを fmt.Print のように扱います。つまり、引数をスペースで区切って連結し、改行を追加して出力します。したがって、"scan failed: %v" という文字列と scanner.Err() の値がそれぞれ独立した引数として扱われ、出力は例えば scan failed: %v some error のようになります。これは、%v がフォーマット指定子として機能することを期待している開発者の意図とは異なります。

一方、修正後のコードでは t.Fatalf が使用されています。

t.Fatalf("scan failed: %v", scanner.Err())
t.Fatalf("unexpected token: %v", token)

t.Fatalf は、最初の引数をフォーマット文字列として解釈し、それに続く引数をそのフォーマット文字列に埋め込みます。これは fmt.Printf と同じ挙動です。したがって、"scan failed: %v" はフォーマット文字列として機能し、scanner.Err() の値が %v の位置に適切に埋め込まれ、期待通りのエラーメッセージ(例: scan failed: some error)が出力されます。

この変更は、単なる構文上の修正以上の意味を持ちます。テストが失敗した際に、より正確で読みやすいエラーメッセージが出力されるようになります。これにより、テストのデバッグが容易になり、問題の特定にかかる時間が短縮されます。特に、%v のようなフォーマット指定子を使用している場合、Fatalf を使用することは、開発者の意図を明確にし、テストの出力の品質を向上させる上で不可欠です。

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

変更は src/pkg/bufio/scan_test.go ファイルの以下の部分です。

--- a/src/pkg/bufio/scan_test.go
+++ b/src/pkg/bufio/scan_test.go
@@ -413,9 +413,9 @@ func TestScanWordsExcessiveWhiteSpace(t *testing.T) {
 	scanner.MaxTokenSize(smallMaxTokenSize)
 	scanner.Split(ScanWords)
 	if !scanner.Scan() {
-\t\tt.Fatal(\"scan failed: %v\", scanner.Err())\n+\t\tt.Fatalf(\"scan failed: %v\", scanner.Err())\n \t}\n \tif token := scanner.Text(); token != word {\n-\t\tt.Fatal(\"unexpected token: %v\", token)\n+\t\tt.Fatalf(\"unexpected token: %v\", token)\n \t}\n }\

コアとなるコードの解説

このコミットでは、TestScanWordsExcessiveWhiteSpace というテスト関数内の2箇所で、t.Fatal の呼び出しが t.Fatalf に変更されています。

  1. if !scanner.Scan() { ... } ブロック内:

    • 変更前: t.Fatal("scan failed: %v", scanner.Err())
    • 変更後: t.Fatalf("scan failed: %v", scanner.Err())
    • この部分は、scanner.Scan()false を返した場合、つまりスキャンに失敗した場合に実行されます。元のコードでは、scanner.Err() から得られるエラー情報を %v でフォーマットしようとしていましたが、t.Fatal の挙動により、フォーマットが適用されず、%v がそのまま出力されていました。t.Fatalf に変更することで、scanner.Err() の値が適切にフォーマットされ、より具体的なエラーメッセージがテスト出力に表示されるようになります。
  2. if token := scanner.Text(); token != word { ... } ブロック内:

    • 変更前: t.Fatal("unexpected token: %v", token)
    • 変更後: t.Fatalf("unexpected token: %v", token)
    • この部分は、スキャンされたトークン (scanner.Text()) が期待される単語 (word) と一致しなかった場合に実行されます。ここでも同様に、token の値を %v でフォーマットしようとしていましたが、t.Fatal の挙動により意図通りにフォーマットされませんでした。t.Fatalf に変更することで、token の値が適切に埋め込まれ、「予期しないトークン」が何であったかを明確に伝えるエラーメッセージが出力されるようになります。

これらの変更は、テストの失敗時に出力されるメッセージの品質を向上させ、デバッグプロセスをより効率的にするためのものです。

関連リンク

参考にした情報源リンク