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

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

このコミットは、Go言語のテストユーティリティであるgotestの動作を改善するものです。具体的には、テスト関数を識別するための正規表現パターンをより柔軟なものに変更し、テスト関数が見つからないファイルに対して警告を発する機能を追加しています。また、テスト関連の警告メッセージの出所をより明確にするための修正も含まれています。これにより、開発者はテストの命名規則に対する柔軟性が向上し、意図せずテストが実行されていないファイルがある場合に早期に気づくことができるようになります。

コミット

commit 92cff8557ed411e9f9ec05a9ad92ac40cdbef0b1
Author: Russ Cox <rsc@golang.org>
Date:   Tue Nov 25 12:49:17 2008 -0800

    gotest: change pattern to Test([^a-z].*)?
            warn about files with no tests
    be clear about where testing warnings come from
    
    R=r
    DELTA=18  (12 added, 3 deleted, 3 changed)
    OCL=19988
    CL=19993

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

https://github.com/golang/go/commit/92cff8557ed411e9f9ec05a9ad92ac40cdbef0b1

元コミット内容

gotest: change pattern to Test([^a-z].*)?
        warn about files with no tests
be clear about where testing warnings come from

変更の背景

このコミットが行われた2008年11月は、Go言語がまだ一般に公開される前の初期開発段階でした。この時期は、言語仕様、標準ライブラリ、およびツールチェーンが活発に設計・実装されており、ユーザーからのフィードバックや内部での利用を通じて継続的に改善が加えられていました。

このコミットの背景には、以下の点が考えられます。

  1. テスト関数の命名規則の柔軟性向上: 当時のgotestツールは、テスト関数を識別するためのパターンが厳密すぎた可能性があります。例えば、TestFooのような形式は認識できても、Test_FooTest123のような、より多様な命名パターンに対応できていなかったかもしれません。開発者がより自由にテスト関数名を付けられるように、パターンの柔軟性を高める必要がありました。
  2. テストカバレッジの可視化とデバッグの支援: テストファイルを作成したにもかかわらず、何らかの理由でテスト関数がgotestによって認識されず、テストが全く実行されないという状況は、開発者にとって混乱の元となります。このような場合に明示的な警告を出すことで、開発者は問題に早期に気づき、デバッグや修正を行うことができるようになります。これは、テストの信頼性を高め、開発体験を向上させる上で重要です。
  3. 警告メッセージの出所の明確化: システムが複雑になるにつれて、どのコンポーネントが警告を発しているのかを明確にすることは、問題の特定と解決に役立ちます。この場合、gotestツール自体が発する警告と、testingパッケージのランタイムが発する警告を区別することで、メッセージの意図と原因をより正確に理解できるようになります。これは、Goの設計哲学である「明確さ」と「シンプルさ」にも合致する改善です。

これらの改善は、Go言語のテストフレームワークがより堅牢で使いやすいものになるための、初期段階における重要なステップであったと言えます。

前提知識の解説

このコミットの変更内容を理解するためには、以下の前提知識が必要です。

  1. Go言語のテストの基本:

    • go testコマンド: Go言語の標準的なテスト実行コマンドです。プロジェクト内のテストファイルを自動的に検出し、テストを実行します。
    • testingパッケージ: Go言語に組み込まれているテストフレームワークです。テスト関数やベンチマーク関数を記述するために使用されます。
    • テスト関数の命名規則: Go言語では、テスト関数はTestで始まり、その後に続く最初の文字が大文字である必要があります(例: func TestMyFunction(t *testing.T))。このコミットでは、この命名規則の解釈がより柔軟になります。
    • *testing.T: テスト関数に渡される引数で、テストの失敗を報告したり、ログを出力したりするためのメソッドを提供します。
  2. gotestコマンド(初期のGoツール):

    • このコミットで変更されているsrc/cmd/gotest/gotestは、当時のGo言語のテスト実行ツールです。現在のgo testコマンドの内部実装の一部、またはその前身にあたる可能性があります。これはシェルスクリプトで書かれており、Goのコンパイル済みバイナリやシンボル情報ツール(6nmなど)を組み合わせてテストを実行するロジックを含んでいます。
  3. 正規表現 (Regular Expressions):

    • テキストパターンを記述するための強力な言語です。このコミットでは、テスト関数名を識別するために正規表現が使用されています。
    • Test([^a-z].*)?: この正規表現は、Testという文字列で始まり、その後に小文字(a-z)以外の任意の文字が続くパターンにマッチします。?は直前の要素が0回または1回出現することを示し、.*は任意の文字が0回以上繰り返されることを意味します。これにより、TestFooだけでなく、Test_BarTest123のような関数名もテスト関数として認識できるようになります。
  4. シェルスクリプトの基本:

    • src/cmd/gotest/gotestがシェルスクリプトであるため、forループ、if条件、変数代入、標準出力/エラー出力のリダイレクト(1>&2)などの基本的な構文を理解していると、コードの変更がより明確になります。
    • 6nmコマンド: Go言語のツールチェーンの一部で、コンパイルされたGoのオブジェクトファイル(.6拡張子を持つファイル)からシンボル情報を抽出するコマンドです。C言語のnmコマンドに相当します。6nm -s $ofilesは、指定されたオブジェクトファイルからシンボルテーブルを抽出し、そのシンボルの種類(Tはテキストセクションのシンボル、つまり関数)と名前を表示します。
    • grep / egrep: テキストから特定のパターンにマッチする行を検索するコマンドです。egrepは拡張正規表現をサポートします。
    • sed: ストリームエディタで、テキストの変換に使用されます。ここでは、6nmの出力から関数名だけを抽出するために使われています。

これらの知識があれば、コミットの意図と具体的なコード変更がどのように機能するのかを深く理解することができます。

技術的詳細

このコミットは、Go言語のテスト実行ツールgotestと、テストランタイムを提供するtestingパッケージの2つの主要なコンポーネントにわたる変更を含んでいます。

1. src/cmd/gotest/gotestにおけるテスト関数検出ロジックの変更

gotestシェルスクリプトは、Goのソースファイルをコンパイルして生成されたオブジェクトファイル(.6ファイル)から、テスト関数として認識すべきシンボルを抽出する役割を担っています。

  • テスト関数パターンの変更: 以前のバージョンでは、テスト関数を識別するためにgrep ' T .*·Test[A-Z]'のようなパターンが使用されていました。これは、Testの後に大文字が続く関数名のみをテスト関数として認識していました。 このコミットでは、このパターンがpattern='Test([^a-z].*)?'という正規表現に更新されました。そして、egrep ' T .*·'$pattern'$'を使用してシンボルをフィルタリングしています。

    • Test([^a-z].*)?: この新しいパターンは、Testで始まり、その直後に小文字(a-z)以外の任意の文字が続く関数名にマッチします。例えば、TestFooTest_BarTest123Test日本語のような関数名もテスト関数として認識されるようになります。これにより、テスト関数の命名規則に対する柔軟性が大幅に向上しました。
    • · (中点): Goのコンパイル済みバイナリでは、パッケージ名と関数名の区切り文字として中点(Unicode U+00B7)が使用されることがあります。egrepコマンドのパターンに含まれる·は、このシンボル区切り文字にマッチするために使用されています。
  • テストがないファイルへの警告機能の追加: 変更前は、6nmの出力からテスト関数を抽出し、それらを_testmain.goに書き出すという単純なロジックでした。テスト関数が見つからなかった場合でも、特に警告は発せられませんでした。 このコミットでは、for ofile in $ofilesというループが導入され、各オブジェクトファイル(ofile)ごとにテスト関数を個別に検索するようになりました。

    • tests=$(6nm -s $ofile | egrep ' T .*·'$pattern'$' | grep -v '·.*[.·]' | sed 's/.* //; s/·/./'): 各ofileから、新しいパターンにマッチするテスト関数を抽出し、tests変数に格納します。
    • if [ "x$tests" = x ]; then: tests変数が空であるかどうか(つまり、そのファイルにテスト関数が見つからなかったかどうか)をチェックします。xを前置するのは、変数が空文字列の場合に構文エラーを避けるためのシェルスクリプトの一般的なテクニックです。
    • echo 'gotest: warning: no tests matching '$pattern' in '$ofile 1>&2: もしテスト関数が見つからなかった場合、標準エラー出力(1>&2)に警告メッセージを出力します。このメッセージは、どのファイル($ofile)で、どのパターン($pattern)にマッチするテストが見つからなかったのかを明確に示します。

2. src/lib/testing.goにおける警告メッセージの出所の明確化

src/lib/testing.goは、Goのtestingパッケージのランタイムコードの一部です。このファイルには、テスト実行時に「実行すべきテストがない」場合に表示される警告メッセージが含まれていました。

  • 警告メッセージのプレフィックス変更: 以前はprintln("gotest: warning: no tests to run");というメッセージが出力されていました。 このコミットでは、これがprintln("testing: warning: no tests to run");に変更されました。
    • この変更の意図は、この警告がgotestというコマンドラインツールから直接発せられているのではなく、Goの標準ライブラリであるtestingパッケージの内部ロジック(testing.Main関数など)から発せられていることを明確にすることです。これにより、ユーザーは警告の発生源を正確に理解し、デバッグや問題解決の際に適切なコンポーネントに注意を向けることができます。これは、Goのモジュール性と明確なエラー報告の原則に沿った改善です。

これらの技術的な変更は、Go言語のテストインフラストラクチャの初期段階における、堅牢性、柔軟性、およびユーザーフィードバックの品質向上に貢献しています。

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

src/cmd/gotest/gotest

--- a/src/cmd/gotest/gotest
+++ b/src/cmd/gotest/gotest
@@ -55,12 +55,21 @@ trap "rm -f _testmain.go _testmain.6" 0 1 2 3 14 15
  	# test array
  	echo
  	echo 'var tests = &[]testing.Test {'
-	# test functions are named TestFoo
-	# the grep -v eliminates methods and other special names
-	# that have multiple dots.
-	for i in $(6nm -s $ofiles | grep ' T .*·Test[A-Z]' | grep -v '·.*[.·]' | sed 's/.* //; s/·/./')
+	for ofile in $ofiles
  	do
-	\techo '\ttesting.Test{ "'$i'", &'$i' },'
+		# test functions are named TestFoo
+		# the grep -v eliminates methods and other special names
+		# that have multiple dots.
+		pattern='Test([^a-z].*)?'
+		tests=$(6nm -s $ofile | egrep ' T .*·'$pattern'$' | grep -v '·.*[.·]' | sed 's/.* //; s/·/./')
+		if [ "x$tests" = x ]; then
+			echo 'gotest: warning: no tests matching '$pattern' in '$ofile 1>&2
+		else
+			for i in $tests
+			do
+				echo '\ttesting.Test{ "'$i'", &'$i' },'
+			done
+		fi
  	done
  	echo '}'
  	# body

src/lib/testing.go

--- a/src/lib/testing.go
+++ b/src/lib/testing.go
@@ -86,7 +86,7 @@ export func Main(tests *[]Test) {
  	flag.Parse();
  	ok := true;
  	if len(tests) == 0 {\n-		println("gotest: warning: no tests to run");
+\t\tprintln("testing: warning: no tests to run");
  	}
  	for i := 0; i < len(tests); i++ {
  	\tif chatty {

コアとなるコードの解説

src/cmd/gotest/gotestの変更点

  1. テスト関数検出ロジックの変更とループの導入:

    • 変更前は、すべてのオブジェクトファイル($ofiles)に対して一度に6nmを実行し、その結果をgrepsedでフィルタリングしてテスト関数を抽出していました。この方法では、どのファイルにテスト関数がないのかを特定するのが困難でした。
    • 変更後、for ofile in $ofilesというループが導入され、各オブジェクトファイル($ofile)ごとに個別にテスト関数を検索するようになりました。これにより、ファイルごとのテスト関数の有無をチェックできるようになりました。
  2. 新しいテスト関数パターンの定義:

    • pattern='Test([^a-z].*)?'という行が追加され、テスト関数を識別するための新しい正規表現パターンが定義されました。このパターンは、Testの後に小文字以外の任意の文字が続く関数名にマッチします。これにより、TestFooだけでなく、Test_BarTest123のようなテスト関数名も適切に検出できるようになりました。
  3. テスト関数検出の実行:

    • tests=$(6nm -s $ofile | egrep ' T .*·'$pattern'$' | grep -v '·.*[.·]' | sed 's/.* //; s/·/./')
      • 6nm -s $ofile: 現在処理中のオブジェクトファイル$ofileからシンボル情報を抽出します。
      • egrep ' T .*·'$pattern'$': 抽出されたシンボルの中から、T(テキストセクション、つまり関数)であり、かつ新しい$patternにマッチする関数名をフィルタリングします。$は行末にマッチし、完全な関数名にマッチすることを保証します。
      • grep -v '·.*[.·]': メソッドや特殊な名前(複数のドットを含むもの)を除外します。
      • sed 's/.* //; s/·/./': シンボル情報から関数名だけを抽出し、Goのパッケージ区切り文字である中点·をドット.に変換します。
      • この結果がtests変数に格納されます。
  4. テストがないファイルへの警告:

    • if [ "x$tests" = x ]; then ... fiという条件分岐が追加されました。
      • "x$tests" = x: tests変数が空文字列であるかどうかをチェックします。これは、現在の$ofileにテスト関数が見つからなかったことを意味します。
      • echo 'gotest: warning: no tests matching '$pattern' in '$ofile 1>&2: テスト関数が見つからなかった場合、どのファイルでどのパターンにマッチするテストが見つからなかったのかを明確に示す警告メッセージを標準エラー出力に表示します。
  5. テスト関数のリスト出力:

    • elseブロック内で、for i in $testsループが実行され、見つかった各テスト関数名$iに対して、_testmain.goに書き出すためのtesting.Test構造体の初期化コード(testing.Test{ "'$i'", &'$i' },)が生成されます。この部分は、テスト関数が見つかった場合にのみ実行されます。

src/lib/testing.goの変更点

  1. 警告メッセージの変更:
    • if len(tests) == 0ブロック内で、実行すべきテストが全くない場合に表示される警告メッセージが変更されました。
    • 変更前: println("gotest: warning: no tests to run");
    • 変更後: println("testing: warning: no tests to run");
    • この変更は、警告の出所をより正確に伝えるためのものです。以前はgotestツールが警告を発しているように見えましたが、実際にはtestingパッケージのMain関数(テストランナーのコア部分)がこの警告を発しています。プレフィックスをtesting:に変更することで、警告がGoのテストフレームワーク自体から来ていることを明確にし、ユーザーが問題の原因をより正確に特定できるようにします。

これらの変更により、Goのテストシステムは、テスト関数の命名に対する柔軟性を高め、テストの欠落を早期に検出し、警告メッセージの明確性を向上させることで、より堅牢で使いやすいものになりました。

関連リンク

参考にした情報源リンク

  • Go言語の公式ドキュメント
  • Go言語のソースコード(特にsrc/cmd/gotestsrc/lib/testingディレクトリ)
  • 正規表現に関する一般的な知識
  • シェルスクリプトの構文とコマンド(grep, sed, nmなど)に関する一般的な知識
  • Gitのコミットと差分表示に関する知識
  • Go言語の歴史と初期開発に関する一般的な情報