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

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

このコミットは、Go言語の標準ライブラリbytesパッケージにおけるContains関数のテストを追加するものです。bytes.Contains関数は、あるバイトスライスが別のバイトスライスを部分列として含むかどうかを判定する機能を提供します。このコミットにより、Contains関数の正確性と堅牢性が、様々な入力パターン(ASCII文字列、日本語文字列、存在しない部分文字列など)に対して保証されるようになります。

コミット

commit 872f5ffa095c483a506a639f1960f6778328d83e
Author: Shawn Smith <shawn.p.smith@gmail.com>
Date:   Sat Dec 28 20:33:05 2013 +1100

    bytes: add test for Contains
    
    R=golang-codereviews, dave
    CC=golang-codereviews
    https://golang.org/cl/46140043

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

https://github.com/golang/go/commit/872f5ffa095c483a506a639f1960f6778328d83e

元コミット内容

このコミットは、src/pkg/bytes/bytes_test.goファイルにbytes.Contains関数のテストケースを追加します。具体的には、containsTestsという構造体のスライスを定義し、様々なバイトスライスとその部分スライス、そして期待される結果(真偽値)を組み合わせたテストデータを提供しています。TestContains関数は、このテストデータをイテレートし、bytes.Contains関数の実際の出力と期待される結果を比較することで、関数の動作を検証します。

変更の背景

ソフトウェア開発において、テストはコードの品質と信頼性を保証するために不可欠です。特に、標準ライブラリのような基盤となるコンポーネントでは、その機能が正確かつ期待通りに動作することが極めて重要です。bytes.Contains関数は、バイトスライス内の部分スライスの存在をチェックするという、多くのアプリケーションで利用される基本的な操作を提供します。

このコミットが行われた2013年12月時点では、bytes.Contains関数は既に存在していましたが、その動作を網羅的に検証するテストケースが不足していた可能性があります。テストの追加は、以下の目的のために行われます。

  1. 機能の正確性の保証: Contains関数が、様々な入力パターン(空のスライス、部分スライスが先頭、中間、末尾にある場合、部分スライスが存在しない場合、異なるエンコーディングの文字列など)に対して正しく動作することを確認します。
  2. リグレッションの防止: 将来のコード変更によってContains関数の既存の動作が意図せず変更される(リグレッション)ことを防ぎます。テストスイートが存在することで、変更が既存の機能に悪影響を与えないことを自動的に検証できます。
  3. コードの理解とドキュメント: テストケースは、関数の意図された動作を示す一種のドキュメントとしても機能します。開発者はテストを見ることで、関数がどのようなシナリオでどのように振る舞うべきかを理解できます。
  4. 堅牢性の向上: エッジケースや予期せぬ入力に対する関数の挙動をテストすることで、より堅牢なコードベースを構築します。

このコミットは、Go言語の標準ライブラリの品質向上と、開発者が安心してbytes.Contains関数を利用できる環境を整備するための一環として行われました。

前提知識の解説

このコミットを理解するためには、以下のGo言語の基本的な概念とテストに関する知識が必要です。

1. Go言語のbytesパッケージ

bytesパッケージは、バイトスライス([]byte型)を操作するためのユーティリティ関数を提供します。文字列操作によく似た機能を提供しますが、string型ではなく[]byte型を対象とします。これは、バイナリデータや、エンコーディングが確定していないテキストデータを扱う際に特に有用です。

  • []byte: Go言語におけるバイトスライスは、可変長のバイトのシーケンスを表します。これは、ファイルの内容、ネットワークからのデータ、またはUTF-8エンコードされた文字列などを表現するためによく使用されます。
  • bytes.Contains(s, subslice []byte) bool: この関数は、バイトスライスsがバイトスライスsubsliceを部分列として含む場合にtrueを返し、そうでない場合にfalseを返します。

2. Go言語のテストフレームワーク

Go言語には、標準でテストをサポートするtestingパッケージが組み込まれています。これにより、外部のテストフレームワークを導入することなく、ユニットテストやベンチマークテストを記述できます。

  • テストファイルの命名規則: テストファイルは、テスト対象のソースファイルと同じディレクトリに配置され、ファイル名の末尾が_test.goである必要があります(例: bytes.goに対するbytes_test.go)。
  • テスト関数の命名規則: テスト関数はTestで始まり、その後に続く名前の最初の文字が大文字である必要があります(例: func TestContains(t *testing.T))。
  • *testing.T: テスト関数は*testing.T型の引数を受け取ります。このオブジェクトは、テストの失敗を報告したり、ログを出力したりするためのメソッドを提供します。
    • t.Errorf(...): テストが失敗したことを報告し、指定されたフォーマット文字列と引数でエラーメッセージを出力します。テストは続行されます。
    • t.Fatalf(...): テストが失敗したことを報告し、エラーメッセージを出力した後、テストの実行を停止します。
  • テストの実行: Goのテストは、プロジェクトのルートディレクトリまたはテストファイルが存在するディレクトリでgo testコマンドを実行することで実行されます。

3. テーブルドリブンテスト (Table-Driven Tests)

Go言語のテストでよく用いられるパターンの一つに「テーブルドリブンテスト」があります。これは、複数のテストケースを構造体のスライスとして定義し、ループで各テストケースを実行する手法です。

  • 利点:
    • 簡潔性: 多数のテストケースをコンパクトに記述できます。
    • 可読性: テストデータと期待される結果が一目でわかります。
    • 保守性: 新しいテストケースの追加や既存のテストケースの変更が容易です。
    • 網羅性: 様々なエッジケースや入力パターンを効率的にテストできます。

このコミットで追加されたcontainsTests構造体のスライスと、それをループで処理するTestContains関数は、まさにテーブルドリブンテストの典型的な例です。

技術的詳細

このコミットの技術的詳細は、bytes.Contains関数のテスト実装に集約されます。

containsTests構造体スライスの定義

var containsTests = []struct {
	b, subslice []byte
	want        bool
}{
	{[]byte("hello"), []byte("hel"), true},
	{[]byte("日本語"), []byte("日本"), true},
	{[]byte("hello"), []byte("Hello, world"), false},
	{[]byte("東京"), []byte("京東"), false},
}

このコードブロックでは、containsTestsという名前のグローバル変数として、匿名構造体のスライスが定義されています。各構造体は以下のフィールドを持ちます。

  • b []byte: 検索対象となるバイトスライス。
  • subslice []byte: b内に存在するかどうかをチェックする部分バイトスライス。
  • want bool: bytes.Contains(b, subslice)が返すことが期待される真偽値。

定義されているテストケースは以下の通りです。

  1. {[]byte("hello"), []byte("hel"), true}: "hello"の中に"hel"が含まれるのでtrueが期待されます。
  2. {[]byte("日本語"), []byte("日本"), true}: 日本語の文字列でも正しく動作することを確認します。"日本語"の中に"日本"が含まれるのでtrueが期待されます。
  3. {[]byte("hello"), []byte("Hello, world"), false}: "hello"の中に"Hello, world"は含まれないのでfalseが期待されます。これは、部分スライスが検索対象スライスよりも長い場合や、大文字・小文字が異なる場合のテストです。bytes.Containsはバイト列の完全一致をチェックするため、大文字・小文字は区別されます。
  4. {[]byte("東京"), []byte("京東"), false}: "東京"の中に"京東"は含まれないのでfalseが期待されます。これは、部分スライスが検索対象スライス内のバイトを異なる順序で含む場合のテストです。

これらのテストケースは、bytes.Contains関数が様々なシナリオで正しく動作するかどうかを検証するための基本的な網羅性を提供します。特に、ASCII以外のUTF-8エンコードされた文字列(日本語)のテストが含まれている点は重要です。

TestContains関数の実装

func TestContains(t *testing.T) {
	for _, tt := range containsTests {
		if got := Contains(tt.b, tt.subslice); got != tt.want {
			t.Errorf("Contains(%q, %q) = %v, want %v", tt.b, tt.subslice, got, tt.want)
		}
	}
}

この関数は、bytes.Contains関数のテストを実行するメインのロジックです。

  1. for _, tt := range containsTests: containsTestsスライスをイテレートし、各テストケースをtt変数に代入します。
  2. if got := Contains(tt.b, tt.subslice); got != tt.want:
    • Contains(tt.b, tt.subslice): 実際のbytes.Contains関数を呼び出し、tt.btt.subsliceを引数として渡します。その結果はgot変数に代入されます。
    • got != tt.want: 実際の戻り値gotが、期待される戻り値tt.wantと異なるかどうかをチェックします。
  3. t.Errorf("Contains(%q, %q) = %v, want %v", tt.b, tt.subslice, got, tt.want): もしgottt.wantが一致しない場合(テストが失敗した場合)、t.Errorfを呼び出してエラーメッセージを出力します。
    • %q: バイトスライスをクォートされた文字列として表示するためのフォーマット動詞です。これにより、非表示文字や特殊文字も明確に表示されます。
    • エラーメッセージは、どの入力でテストが失敗し、どのような結果が得られ、どのような結果が期待されたのかを明確に示します。

このシンプルなループ構造により、定義されたすべてのテストケースが自動的に実行され、bytes.Contains関数の動作が検証されます。

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

このコミットによる変更は、src/pkg/bytes/bytes_test.goファイルに集中しています。

--- a/src/pkg/bytes/bytes_test.go
+++ b/src/pkg/bytes/bytes_test.go
@@ -1162,6 +1162,24 @@ func TestBufferTruncateOutOfRange(t *testing.T) {
 	b.Truncate(20)
 }
 
+var containsTests = []struct {
+	b, subslice []byte
+	want        bool
+}{
+	{[]byte("hello"), []byte("hel"), true},
+	{[]byte("日本語"), []byte("日本"), true},
+	{[]byte("hello"), []byte("Hello, world"), false},
+	{[]byte("東京"), []byte("京東"), false},
+}
+
+func TestContains(t *testing.T) {
+	for _, tt := range containsTests {
+		if got := Contains(tt.b, tt.subslice); got != tt.want {
+			t.Errorf("Contains(%q, %q) = %v, want %v", tt.b, tt.subslice, got, tt.want)
+		}
+	}
+}
+
 var makeFieldsInput = func() []byte {
 	x := make([]byte, 1<<20)
 	// Input is ~10% space, ~10% 2-byte UTF-8, rest ASCII non-space.

具体的には、ファイルの末尾近くに、既存のTestBufferTruncateOutOfRange関数の後に、新しいcontainsTests変数とTestContains関数が追加されています。

コアとなるコードの解説

追加されたコードは、Go言語の標準的なテストパターンであるテーブルドリブンテストを実装しています。

  1. containsTests変数:

    • これは、bytes.Contains関数のテストケースを定義する構造体のスライスです。
    • 各要素は、テスト対象のバイトスライスb、検索する部分スライスsubslice、そして期待される結果want(真偽値)を含んでいます。
    • この構造により、テストケースの追加や変更が非常に容易になります。新しいシナリオをテストしたい場合は、このスライスに新しい要素を追加するだけで済みます。
  2. TestContains関数:

    • Goのテストフレームワークによって自動的に発見され、実行されるテスト関数です。
    • for _, tt := range containsTestsループを使って、containsTestsスライス内の各テストケースを順番に処理します。
    • Contains(tt.b, tt.subslice)を呼び出して実際の関数実行結果gotを取得します。
    • if got != tt.wantで、実際の実行結果が期待される結果と異なる場合にテスト失敗と判断します。
    • t.Errorf(...)を使って、テストが失敗した際に詳細なエラーメッセージを出力します。このメッセージには、どの入力で失敗したか、何が得られ、何が期待されたかが含まれるため、デバッグが容易になります。

この追加により、bytes.Contains関数の機能が、定義されたテストケースに対して正しく動作することが保証されるようになりました。特に、日本語のようなマルチバイト文字を含む文字列のテストケースが含まれていることは、国際化対応の観点からも重要です。

関連リンク

  • Go言語のbytesパッケージのドキュメント: https://pkg.go.dev/bytes
  • Go言語のtestingパッケージのドキュメント: https://pkg.go.dev/testing
  • Go言語におけるテーブルドリブンテストに関する記事(例: Goの公式ブログやGoDocの例など)

参考にした情報源リンク

  • Go言語の公式ドキュメント
  • Go言語のテストに関する一般的なプラクティスとパターン
  • Go言語のソースコード(特にbytesパッケージとtestingパッケージ)
  • コミットメッセージに記載されているGoのコードレビューリンク: https://golang.org/cl/46140043 (現在はhttps://go.dev/cl/46140043にリダイレクトされる可能性があります)
  • bytes.Contains関数の具体的な動作に関する情報(Goのソースコードやドキュメントから)
  • Go言語における%qフォーマット動詞の利用方法に関する情報