[インデックス 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言語がまだ一般に公開される前の初期開発段階でした。この時期は、言語仕様、標準ライブラリ、およびツールチェーンが活発に設計・実装されており、ユーザーからのフィードバックや内部での利用を通じて継続的に改善が加えられていました。
このコミットの背景には、以下の点が考えられます。
- テスト関数の命名規則の柔軟性向上: 当時の
gotest
ツールは、テスト関数を識別するためのパターンが厳密すぎた可能性があります。例えば、TestFoo
のような形式は認識できても、Test_Foo
やTest123
のような、より多様な命名パターンに対応できていなかったかもしれません。開発者がより自由にテスト関数名を付けられるように、パターンの柔軟性を高める必要がありました。 - テストカバレッジの可視化とデバッグの支援: テストファイルを作成したにもかかわらず、何らかの理由でテスト関数が
gotest
によって認識されず、テストが全く実行されないという状況は、開発者にとって混乱の元となります。このような場合に明示的な警告を出すことで、開発者は問題に早期に気づき、デバッグや修正を行うことができるようになります。これは、テストの信頼性を高め、開発体験を向上させる上で重要です。 - 警告メッセージの出所の明確化: システムが複雑になるにつれて、どのコンポーネントが警告を発しているのかを明確にすることは、問題の特定と解決に役立ちます。この場合、
gotest
ツール自体が発する警告と、testing
パッケージのランタイムが発する警告を区別することで、メッセージの意図と原因をより正確に理解できるようになります。これは、Goの設計哲学である「明確さ」と「シンプルさ」にも合致する改善です。
これらの改善は、Go言語のテストフレームワークがより堅牢で使いやすいものになるための、初期段階における重要なステップであったと言えます。
前提知識の解説
このコミットの変更内容を理解するためには、以下の前提知識が必要です。
-
Go言語のテストの基本:
go test
コマンド: Go言語の標準的なテスト実行コマンドです。プロジェクト内のテストファイルを自動的に検出し、テストを実行します。testing
パッケージ: Go言語に組み込まれているテストフレームワークです。テスト関数やベンチマーク関数を記述するために使用されます。- テスト関数の命名規則: Go言語では、テスト関数は
Test
で始まり、その後に続く最初の文字が大文字である必要があります(例:func TestMyFunction(t *testing.T)
)。このコミットでは、この命名規則の解釈がより柔軟になります。 *testing.T
: テスト関数に渡される引数で、テストの失敗を報告したり、ログを出力したりするためのメソッドを提供します。
-
gotest
コマンド(初期のGoツール):- このコミットで変更されている
src/cmd/gotest/gotest
は、当時のGo言語のテスト実行ツールです。現在のgo test
コマンドの内部実装の一部、またはその前身にあたる可能性があります。これはシェルスクリプトで書かれており、Goのコンパイル済みバイナリやシンボル情報ツール(6nm
など)を組み合わせてテストを実行するロジックを含んでいます。
- このコミットで変更されている
-
正規表現 (Regular Expressions):
- テキストパターンを記述するための強力な言語です。このコミットでは、テスト関数名を識別するために正規表現が使用されています。
Test([^a-z].*)?
: この正規表現は、Test
という文字列で始まり、その後に小文字(a-z
)以外の任意の文字が続くパターンにマッチします。?
は直前の要素が0回または1回出現することを示し、.*
は任意の文字が0回以上繰り返されることを意味します。これにより、TestFoo
だけでなく、Test_Bar
やTest123
のような関数名もテスト関数として認識できるようになります。
-
シェルスクリプトの基本:
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
)以外の任意の文字が続く関数名にマッチします。例えば、TestFoo
、Test_Bar
、Test123
、Test日本語
のような関数名もテスト関数として認識されるようになります。これにより、テスト関数の命名規則に対する柔軟性が大幅に向上しました。·
(中点): 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
の変更点
-
テスト関数検出ロジックの変更とループの導入:
- 変更前は、すべてのオブジェクトファイル(
$ofiles
)に対して一度に6nm
を実行し、その結果をgrep
とsed
でフィルタリングしてテスト関数を抽出していました。この方法では、どのファイルにテスト関数がないのかを特定するのが困難でした。 - 変更後、
for ofile in $ofiles
というループが導入され、各オブジェクトファイル($ofile
)ごとに個別にテスト関数を検索するようになりました。これにより、ファイルごとのテスト関数の有無をチェックできるようになりました。
- 変更前は、すべてのオブジェクトファイル(
-
新しいテスト関数パターンの定義:
pattern='Test([^a-z].*)?'
という行が追加され、テスト関数を識別するための新しい正規表現パターンが定義されました。このパターンは、Test
の後に小文字以外の任意の文字が続く関数名にマッチします。これにより、TestFoo
だけでなく、Test_Bar
やTest123
のようなテスト関数名も適切に検出できるようになりました。
-
テスト関数検出の実行:
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
変数に格納されます。
-
テストがないファイルへの警告:
if [ "x$tests" = x ]; then ... fi
という条件分岐が追加されました。"x$tests" = x
:tests
変数が空文字列であるかどうかをチェックします。これは、現在の$ofile
にテスト関数が見つからなかったことを意味します。echo 'gotest: warning: no tests matching '$pattern' in '$ofile 1>&2
: テスト関数が見つからなかった場合、どのファイルでどのパターンにマッチするテストが見つからなかったのかを明確に示す警告メッセージを標準エラー出力に表示します。
-
テスト関数のリスト出力:
else
ブロック内で、for i in $tests
ループが実行され、見つかった各テスト関数名$i
に対して、_testmain.go
に書き出すためのtesting.Test
構造体の初期化コード(testing.Test{ "'$i'", &'$i' },
)が生成されます。この部分は、テスト関数が見つかった場合にのみ実行されます。
src/lib/testing.go
の変更点
- 警告メッセージの変更:
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言語公式ドキュメント: https://go.dev/
- Go言語の
testing
パッケージに関するドキュメント: https://pkg.go.dev/testing - Go言語の初期のコミット履歴(GitHubリポジトリ): https://github.com/golang/go/commits/master
参考にした情報源リンク
- Go言語の公式ドキュメント
- Go言語のソースコード(特に
src/cmd/gotest
とsrc/lib/testing
ディレクトリ) - 正規表現に関する一般的な知識
- シェルスクリプトの構文とコマンド(
grep
,sed
,nm
など)に関する一般的な知識 - Gitのコミットと差分表示に関する知識
- Go言語の歴史と初期開発に関する一般的な情報