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

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

このコミットは、Go言語のテストファイル命名規則を、従来の test*.go から *test.go へと変更するものです。これはGo言語の初期段階における重要な変更であり、現在のGoの標準的なテストファイル命名規則の基礎を築きました。この変更は、テストファイルの識別方法を統一し、ビルドシステムやテストツールとの連携をより効率的かつ直感的にすることを目的としています。

コミット

commit 12254b6c0bc9d3f6689f12d64f7bd4cb4d20d53f
Author: Rob Pike <r@golang.org>
Date:   Wed Nov 19 19:11:01 2008 -0800

    change naming convention for tests from
            test*.go
    to
            *test.go
    
    R=rsc
    DELTA=1747  (864 added, 855 deleted, 28 changed)
    OCL=19666
    CL=19666

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

https://github.com/golang/go/commit/12254b6c0bc9d3f6689f12d64f7bd4cb4d20d53f

元コミット内容

このコミットの目的は、Go言語のテストファイルの命名規則を test*.go から *test.go へと変更することです。これに伴い、テストファイルを検出するビルドツールやスクリプト、Makefile内のパターンも更新されています。また、src/lib/reflect/test.go のような一部のテストファイルでは、パッケージ名の変更や testing パッケージの導入といった、よりGoらしいテストフレームワークへの移行を示す変更も含まれています。

変更の背景

Go言語は、その設計思想の一つとして「シンプルさ」と「一貫性」を重視しています。初期のGo開発において、テストファイルの命名規則はまだ確立されていませんでした。test*.go という命名は、他の言語やフレームワークでよく見られるパターンですが、Goの設計者たちは、よりGoらしい、あるいはより明確な命名規則を模索していました。

*test.go という命名規則は、以下の点で優れています。

  1. 明確性: ファイル名が _test.go で終わることで、そのファイルがテストコードであることを一目で識別できます。これは、パッケージ内の通常のコードとテストコードを明確に区別するのに役立ちます。
  2. ツールとの連携: go test コマンドやその他のビルドツールが、この命名規則に基づいてテストファイルを自動的に発見し、実行することを容易にします。これにより、開発者はテストの実行方法について特別な設定をすることなく、標準的な方法でテストを管理できるようになります。
  3. 一貫性: Goのエコシステム全体でこの命名規則が採用されることで、異なるプロジェクト間でのコードの可読性と保守性が向上します。

このコミットは、Go言語がまだ初期段階にあった2008年に行われたものであり、Goの標準ライブラリやツールチェーンの基盤を形成する上で、このような基本的な規約の確立が非常に重要であったことを示しています。

前提知識の解説

このコミットを理解するためには、以下のGo言語および関連ツールの基本的な知識が必要です。

  • Go言語のパッケージシステム: Goのコードはパッケージにまとめられ、ディレクトリ構造がパッケージパスに対応します。テストファイルも特定のパッケージに属します。
  • Goのビルドシステム: go buildgo install といったコマンドがどのようにソースファイルをコンパイルし、実行可能ファイルを生成するか。
  • Goのテストフレームワーク (testing パッケージ): Goには標準で testing パッケージが提供されており、これを用いてテストを記述します。テスト関数は TestXxx という形式で命名され、*testing.T 型の引数を取ります。
  • go test コマンド: Goのテストを実行するための標準コマンドです。このコマンドは、デフォルトで *test.go という命名規則に従うファイルを検索し、その中のテスト関数を実行します。
  • Makefile: 当時のGoプロジェクトでは、ビルドやテストの自動化にMakefileが広く使用されていました。Makefileは、コマンドの依存関係を定義し、特定のターゲット(例: test, coverage)を実行するためのルールを提供します。
  • 正規表現 (Regular Expressions): ファイル名のパターンマッチングや、ログ出力のフィルタリングに正規表現が使用されています。特に grep コマンドでの利用が顕著です。
  • シェルスクリプト: src/cmd/gotest/gotestsrc/run.bash のようなファイルはシェルスクリプトであり、ファイル操作やコマンド実行のロジックを記述しています。
  • 6cov: 当時のGoのコードカバレッジツールの一つです。Goの初期には、6g (Goコンパイラ), 6l (Goリンカ) といったツールが存在し、6cov もその一部でした。

技術的詳細

このコミットの技術的な変更は、主に以下の3つの側面から構成されています。

  1. テストファイル名の変更: 既存の test*.go 形式のテストファイルが、*test.go 形式に一括でリネームされています。これは git mv コマンドに相当する操作で、ファイルの内容自体は変更されずに名前だけが変わっています。例えば、src/lib/container/array/testarray.gosrc/lib/container/array/array_test.go に変更されています。

  2. ビルドスクリプトとMakefileの更新:

    • src/cmd/gobuild/gobuild.c: このC言語で書かれたビルドツールは、テストファイルを識別するためのロジックを変更しています。
      • grep -v '^test.*\\.go:'grep -v '^.*test\\.go:' に変更されています。これは、コードカバレッジレポートからテストファイルを除外する際に、新しい命名規則に合致するファイルを無視するように正規表現を更新したものです。
      • strncmp(argv[i], "test", 4)strstr(argv[i], "test.go") != nil に変更されています。これは、コマンドライン引数で渡されたファイルがテストファイルであるかどうかを判断するロジックを、より柔軟な文字列検索 (strstr) に変更し、test.go を含むファイルをテストファイルとして扱うようにしたものです。
    • src/cmd/gotest/gotest: このシェルスクリプトは、テストファイルを検索する際に使用するパターンを変更しています。
      • gofiles=$(echo test*.go)gofiles=$(echo *test.go) に変更されています。これにより、gotest コマンドが新しい命名規則に従ってテストファイルを正しく見つけられるようになります。
    • 各ライブラリのMakefile (src/lib/*/Makefile): coverage ターゲット内で使用されている 6cov コマンドの grep パターンが、^test.*\\.go: から ^.*test\\.go: に変更されています。これにより、コードカバレッジツールが新しい命名規則のテストファイルを正しく除外できるようになります。
  3. テストコード自体の変更と標準テストフレームワークへの移行:

    • src/lib/reflect/test.bash の削除: このファイルは、reflect パッケージのテストを実行するためのカスタムシェルスクリプトでした。この削除は、Goの標準テストフレームワーク (testing パッケージと go test コマンド) への移行を示唆しています。
    • src/lib/reflect/test.go の変更:
      • package main から package reflect への変更: テストファイルがテスト対象のパッケージと同じパッケージに属するというGoの慣習に従っています。
      • import "testing" の追加: Goの標準テストフレームワークである testing パッケージを使用することを示しています。
      • func main() から export func TestAll(tt *testing.T) への変更: main 関数で直接テストを実行するのではなく、testing パッケージの規約に従い、TestAll という名前のテスト関数を定義しています。*testing.T 型の引数は、テストの実行状態やエラー報告に使用されます。
      • 文字列リテラル内のパッケージ名の変更: main.T{...} のような文字列が reflect.T{...} に変更されており、これはパッケージ名の変更に伴うものです。
    • src/lib/testing.go の変更: Main 関数に、テストが一つも存在しない場合に警告メッセージを表示するロジックが追加されています。これは、テスト実行時のユーザーエクスペリエンスを向上させるための小さな改善です。
    • src/run.bash の変更: lib/reflect ディレクトリでのテスト実行方法が bash test.bash から make test に変更されています。これは、カスタムスクリプトからMakefileベースの標準的なテスト実行フローへの移行を反映しています。

これらの変更は、Go言語のテストエコシステムが初期段階でどのように進化し、現在の洗練された形へと向かっていったかを示す貴重な証拠です。特に、カスタムのテスト実行スクリプトから testing パッケージと go test コマンドを中心とした標準的なアプローチへの移行は、Goのテスト文化を形成する上で極めて重要なステップでした。

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

このコミットにおけるコアとなるコードの変更箇所は、主に以下のファイルと行に集約されます。

  1. src/cmd/gobuild/gobuild.c:

    --- a/src/cmd/gobuild/gobuild.c
    +++ b/src/cmd/gobuild/gobuild.c
    @@ -290,7 +290,7 @@ char preamble[] =
     	"coverage: packages\n"
     	"\tgotest\n"
    -\t"\t6cov -g `pwd` | grep -v '^test.*\\\\.go:'\n"
    +\t"\t6cov -g `pwd` | grep -v '^.*test\\\\.go:'\n"
     	"\n"
     	"%%.$O: %%.go\n"
     	"\t$(GC) $*.go\n"
    @@ -487,7 +487,7 @@ main(int argc, char **argv)
     	njob = 0;
     	job = emalloc(argc*sizeof job[0]);
     	for(i=0; i<argc; i++) {
    -\t\tif(strncmp(argv[i], "test", 4) == 0)
    +\t\tif(strstr(argv[i], "test.go") != nil)
     		continue;
     	job[njob].name = argv[i];
     	job[njob].pass = -1;
    
  2. src/cmd/gotest/gotest:

    --- a/src/cmd/gotest/gotest
    +++ b/src/cmd/gotest/gotest
    @@ -27,7 +27,7 @@ done
     
     case "x$gofiles" in
     x)
    -\tgofiles=$(echo test*.go)\n"
    +\tgofiles=$(echo *test.go)\n"
     esac
     
     ofiles=$(echo $gofiles | sed 's/\.go/.6/g')
    
  3. src/lib/reflect/test.go:

    --- a/src/lib/reflect/test.go
    +++ b/src/lib/reflect/test.go
    @@ -2,10 +2,11 @@
     // Use of this source code is governed by a BSD-style
     // license that can be found in the LICENSE file.
     
    -package main
    +package reflect
     
     import (
    -\t"reflect"\n"
    +\t"reflect";
    +\t"testing"\n"
     )
     
     var doprint bool = false
    @@ -87,7 +88,7 @@ export type empty interface {}
     
     export type T struct { a int; b float64; c string; d *int }
     
    -func main() {
    +export func TestAll(tt *testing.T) {	// TODO(r): wrap up better
     	var s string;
     	var t reflect.Type;
     
    @@ -168,30 +169,30 @@ func main() {
     		var i int = 7;
     		var tmp = &T{123, 456.75, "hello", &i};
     		value := reflect.NewValue(tmp);
    -\t\tassert(reflect.ValueToString(value.(reflect.PtrValue).Sub()), "main.T{123, 456.75, hello, *int(@)}");
    +\t\tassert(reflect.ValueToString(value.(reflect.PtrValue).Sub()), "reflect.T{123, 456.75, hello, *int(@)}");
     	}
     	{
     		type C chan *T;	// TODO: should not be necessary
     		var tmp = new(C);
     		value := reflect.NewValue(tmp);
    -\t\tassert(reflect.ValueToString(value), "*main.C·test(@)");
    +\t\tassert(reflect.ValueToString(value), "*reflect.C·test(@)");
     	}
     	{
     		type A [10]int;
     		var tmp A = A{1,2,3,4,5,6,7,8,9,10};
     		value := reflect.NewValue(&tmp);
    -\t\tassert(reflect.ValueToString(value.(reflect.PtrValue).Sub()), "main.A·test{1, 2, 3, 4, 5, 6, 7, 8, 9, 10}");
    +\t\tassert(reflect.ValueToString(value.(reflect.PtrValue).Sub()), "reflect.A·test{1, 2, 3, 4, 5, 6, 7, 8, 9, 10}");
     		value.(reflect.PtrValue).Sub().(reflect.ArrayValue).Elem(4).(reflect.IntValue).Set(123);
    -\t\tassert(reflect.ValueToString(value.(reflect.PtrValue).Sub()), "main.A·test{1, 2, 3, 4, 123, 6, 7, 8, 9, 10}");
    +\t\tassert(reflect.ValueToString(value.(reflect.PtrValue).Sub()), "reflect.A·test{1, 2, 3, 4, 123, 6, 7, 8, 9, 10}");
     	}
     	{
     		type AA []int;
     		tmp1 := [10]int{1,2,3,4,5,6,7,8,9,10};	// TODO: should not be necessary to use tmp1
     		var tmp *AA = &tmp1;
     		value := reflect.NewValue(tmp);
    -\t\tassert(reflect.ValueToString(value.(reflect.PtrValue).Sub()), "main.AA·test{1, 2, 3, 4, 5, 6, 7, 8, 9, 10}");
    +\t\tassert(reflect.ValueToString(value.(reflect.PtrValue).Sub()), "reflect.AA·test{1, 2, 3, 4, 5, 6, 7, 8, 9, 10}");
     		value.(reflect.PtrValue).Sub().(reflect.ArrayValue).Elem(4).(reflect.IntValue).Set(123);
    -\t\tassert(reflect.ValueToString(value.(reflect.PtrValue).Sub()), "main.AA·test{1, 2, 3, 4, 123, 6, 7, 8, 9, 10}");
    +\t\tassert(reflect.ValueToString(value.(reflect.PtrValue).Sub()), "reflect.AA·test{1, 2, 3, 4, 123, 6, 7, 8, 9, 10}");
     	}
     
     	{
    
  4. src/lib/reflect/test.bash の削除

  5. src/run.bash:

    --- a/src/run.bash
    +++ b/src/run.bash
    @@ -20,7 +20,7 @@ make test
     (xcd lib/reflect
     make clean
     time make
    -bash test.bash
    +make test
     ) || exit $?
     
     (xcd lib/regexp
    

コアとなるコードの解説

上記の変更箇所は、Go言語のテストシステムにおける根本的なパラダイムシフトを示しています。

  • gobuild.cgotest の変更: これらの変更は、Goのビルドツールとテスト実行スクリプトが、新しい命名規則 (*test.go) に従ってテストファイルを正確に識別し、処理できるようにするためのものです。正規表現の変更は、ファイルパスのどこに test が出現してもマッチするように柔軟性を高めています。strncmp から strstr への変更も同様に、より広範なマッチングを可能にしています。これにより、システム全体でテストファイルの検出が一貫して行われるようになります。

  • src/lib/reflect/test.go の変更: これは、Goのテストがカスタムスクリプトや main 関数ベースのアプローチから、標準の testing パッケージと go test コマンドを利用する現代的なGoのテストスタイルへと移行する重要なステップです。

    • package main から package reflect への変更は、テストコードがテスト対象のパッケージと同じパッケージに属するというGoの慣習を確立します。これにより、テストコードはテスト対象のパッケージ内の非エクスポートされた識別子にもアクセスできるようになります。
    • import "testing"func TestAll(tt *testing.T) の導入は、Goの標準テストフレームワークの採用を意味します。*testing.T はテストの状態を管理し、エラー報告やヘルパー関数を提供します。
    • 文字列リテラル内のパッケージ名の変更は、reflect パッケージの内部構造を反映したものであり、テストが新しいパッケージコンテキストで正しく動作することを確認しています。
  • src/lib/reflect/test.bash の削除と src/run.bash の変更: これらは、カスタムのテスト実行スクリプトを廃止し、Makefileと go test コマンドを介した標準的なテスト実行フローに統一する動きを示しています。これにより、テストの実行方法が簡素化され、Goプロジェクト全体での一貫性が向上します。

これらの変更は、Go言語がその初期段階で、開発者がテストを記述し、実行し、管理する方法について、明確で一貫性のある標準を確立しようとしていたことを明確に示しています。このコミットは、現在のGoのテスト文化の基盤を築いたと言えるでしょう。

関連リンク

参考にした情報源リンク

  • Go言語の公式ドキュメントやパッケージドキュメントは、Goのテストに関する標準的な情報源です。
  • Go言語のGitHubリポジトリのコミット履歴は、Goの進化を追跡するための主要な情報源です。
  • Go言語の初期の設計に関する議論やメーリングリストのアーカイブは、特定の設計判断の背景を理解するのに役立ちますが、この特定のコミットに関する詳細な議論は公開されていません。