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

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

このコミットは、Go言語の標準ライブラリであるsrc/pkg/testing/testing.goファイルに対する変更です。testingパッケージは、Goプログラムの自動テスト、ベンチマーク、およびExample(使用例)の記述をサポートするための機能を提供します。このファイルは、go testコマンドがテストやExampleを実行する際のコアロジックを含んでいます。

コミット

このコミットは、Goのtestingパッケージにおいて、Exampleが存在する場合に「no tests to run」という警告が表示される問題を修正します。具体的には、テストが一つも存在しないがExampleが存在する場合に、不必要な警告が出力されないようにロジックが変更されました。

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

https://github.com/golang/go/commit/291636b99bcb581301faa14de61cde4c9093c335

元コミット内容

commit 291636b99bcb581301faa14de61cde4c9093c335
Author: Russ Cox <rsc@golang.org>
Date:   Wed Mar 7 14:54:31 2012 -0500

    testing: do not print 'no tests' when there are examples
    
    I am not sure why RunTests and RunExamples are
    exported, but I assume that because they are we
    should not change the signature, so I added an
    unexported global shared by Main and RunTests.
    
    Fixes #3237.
    
    R=golang-dev, gri
    CC=golang-dev
    https://golang.org/cl/5779043

変更の背景

Goのtestingパッケージでは、go testコマンドを実行した際に、テスト関数(TestXxx)が一つも定義されていない場合に「testing: warning: no tests to run」という警告メッセージが標準エラー出力に表示されます。しかし、テスト関数は存在しないものの、Example関数(ExampleXxx)が定義されている場合でも、この警告が表示されてしまうという問題がありました。

Example関数は、パッケージの使用例を示すための特別な関数で、go testコマンドによって実行され、その出力が期待される出力と一致するかどうかが検証されます。Exampleはテストとは異なる目的を持つものの、go testの実行対象であり、Exampleが存在するにも関わらず「テストがない」と警告されるのはユーザーにとって混乱を招く可能性がありました。

このコミットは、この不整合を解消し、Exampleが存在する場合には「no tests to run」警告が表示されないようにするために行われました。これはGo issue #3237として報告された問題の修正です。

前提知識の解説

Go言語のtestingパッケージ

Go言語には、テストを記述するための組み込みのtestingパッケージがあります。このパッケージは、ユニットテスト、ベンチマークテスト、およびExampleの作成をサポートします。

  • テスト関数: func TestXxx(t *testing.T)というシグネチャを持つ関数で、Xxxは英大文字で始まる任意の名前です。これらの関数は、コードの特定の単位が正しく動作するかどうかを検証するために使用されます。
  • ベンチマーク関数: func BenchmarkXxx(b *testing.B)というシグネチャを持つ関数で、コードのパフォーマンスを測定するために使用されます。
  • Example関数: func ExampleXxx()またはfunc ExampleXxx_Yyy()というシグネチャを持つ関数で、パッケージや関数の使用方法を示すコード例を提供します。Example関数内のコードは実行され、その標準出力がコメント行のOutput:セクションに記述された内容と一致するかどうかが検証されます。これにより、ドキュメントとコードの整合性が保たれます。

go testコマンドの動作

go testコマンドは、Goのソースコードディレクトリ内で実行され、そのディレクトリ内のテストファイル(ファイル名が_test.goで終わるもの)をコンパイルし、テスト、ベンチマーク、Exampleを実行します。

go testは内部的に、testingパッケージのMain関数を呼び出してテスト実行を管理します。Main関数は、検出されたテスト、ベンチマーク、Exampleのリストを受け取り、それらを適切な順序で実行します。

testingパッケージ内の主要な関数と変数(変更前)

  • Main(matchString func(pat, str string) (bool, error), tests []InternalTest, examples []InternalExample): go testコマンドのエントリポイントとなる関数です。テスト、ベンチマーク、Exampleの実行をオーケストレートします。
  • RunTests(matchString func(pat, str string) (bool, error), tests []InternalTest) (ok bool): 実際のテスト関数を実行するロジックを含みます。テストが一つも存在しない場合に「no tests to run」警告を出力していました。
  • RunExamples(matchString func(pat, str string) (bool, error), examples []InternalExample) (ok bool): Example関数を実行するロジックを含みます。
  • InternalTest: 内部的にテスト関数を表す構造体です。
  • InternalExample: 内部的にExample関数を表す構造体です。

技術的詳細

この変更の核心は、testingパッケージがExampleの存在を認識し、テストが存在しない場合でもExampleがあれば警告を出さないようにすることです。

  1. 新しいグローバル変数の導入: testingパッケージの内部に、haveExamples boolという新しいグローバル変数が追加されました。この変数は、現在のテスト実行にExampleが含まれているかどうかを示すフラグとして機能します。bool型であり、初期値はfalseです。

  2. Main関数でのhaveExamplesの設定: Main関数は、go testコマンドによって検出されたExampleのリスト(examples []InternalExample)を受け取ります。このコミットでは、Main関数内でhaveExamples = len(examples) > 0という行が追加されました。これにより、Exampleのリストが空でなければ(つまり、一つでもExampleが存在すれば)、haveExamplestrueに設定されます。

  3. RunTests関数でのhaveExamplesの利用: RunTests関数は、テスト関数が一つも存在しない場合に警告を出力するロジックを含んでいました。変更前はif len(tests) == 0という条件で警告を出力していましたが、変更後はif len(tests) == 0 && !haveExamplesという条件に変更されました。 この新しい条件により、テスト関数が一つも存在しないかつExampleも存在しない場合にのみ警告が出力されるようになります。Exampleが存在する場合は、haveExamplestrueになるため、!haveExamplesfalseとなり、警告は表示されません。

  4. RunTestsRunExamplesのエクスポートに関する考察: コミットメッセージには「I am not sure why RunTests and RunExamples are exported, but I assume that because they are we should not change the signature」とあります。これは、これらの関数が外部にエクスポートされている(つまり、パッケージ外からアクセス可能である)ため、APIの互換性を保つためにそのシグネチャ(引数と戻り値の型)を変更すべきではないという開発者の判断を示しています。 もしシグネチャを変更できたのであれば、RunTests関数にExampleの存在を示す引数を追加することも可能だったかもしれません。しかし、APIの安定性を優先し、既存のシグネチャを変更せずに、パッケージ内部で共有されるグローバル変数haveExamplesを導入するというアプローチが取られました。これにより、外部からRunTestsRunExamplesを直接呼び出しているコードがあったとしても、そのコードに影響を与えることなく修正が適用されました。

この変更により、go testの出力がより直感的になり、Exampleが存在する場合には「テストがない」という誤解を招く警告が抑制されるようになりました。

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

--- a/src/pkg/testing/testing.go
+++ b/src/pkg/testing/testing.go
@@ -107,6 +107,8 @@ var (
 	cpuListStr     = flag.String("test.cpu", "", "comma-separated list of number of CPUs to use for each test")
 	parallel       = flag.Int("test.parallel", runtime.GOMAXPROCS(0), "maximum test parallelism")
 
+	haveExamples bool // are there examples?
+
 	cpuList []int
 )
 
@@ -279,6 +281,7 @@ func Main(matchString func(pat, str string) (bool, error), tests []InternalTest,
 
 	before()
 	startAlarm()
+	haveExamples = len(examples) > 0
 	testOk := RunTests(matchString, tests)
 	exampleOk := RunExamples(matchString, examples)
 	if !testOk || !exampleOk {
@@ -303,7 +306,7 @@ func (t *T) report() {
 
 func RunTests(matchString func(pat, str string) (bool, error), tests []InternalTest) (ok bool) {
 	ok = true
-	if len(tests) == 0 {
+	if len(tests) == 0 && !haveExamples {
 		fmt.Fprintln(os.Stderr, "testing: warning: no tests to run")
 		return
 	}

コアとなるコードの解説

  1. src/pkg/testing/testing.go の変更点:
    • haveExamples 変数の追加:

      +	haveExamples bool // are there examples?
      

      testingパッケージのグローバル変数としてhaveExamplesが追加されました。これはbool型で、Exampleが存在するかどうかを追跡するために使用されます。コメント// are there examples?がその目的を明確に示しています。

    • Main関数内での haveExamples の設定:

      @@ -279,6 +281,7 @@ func Main(matchString func(pat, str string) (bool, error), tests []InternalTest,
       
       	before()
       	startAlarm()
      +	haveExamples = len(examples) > 0
       	testOk := RunTests(matchString, tests)
       	exampleOk := RunExamples(matchString, examples)
       	if !testOk || !exampleOk {
      

      Main関数内で、haveExamples変数が初期化されます。len(examples) > 0という条件は、Main関数に渡されたexamplesスライス(検出されたExample関数のリスト)の長さが0より大きい(つまり、Exampleが一つでも存在する)場合にtrueとなり、haveExamplesにその値が代入されます。これにより、RunTestsが呼び出される前にExampleの存在情報が設定されます。

    • RunTests関数内での警告ロジックの変更:

      @@ -303,7 +306,7 @@ func (t *T) report() {
       
       func RunTests(matchString func(pat, str string) (bool, error), tests []InternalTest) (ok bool) {
       	ok = true
      -	if len(tests) == 0 {
      +	if len(tests) == 0 && !haveExamples {
       		fmt.Fprintln(os.Stderr, "testing: warning: no tests to run")
       		return
       	}
      

      RunTests関数内の警告出力の条件が変更されました。

      • 変更前: if len(tests) == 0 テスト関数が一つも存在しない場合に警告を出力していました。
      • 変更後: if len(tests) == 0 && !haveExamples テスト関数が一つも存在しないかつhaveExamplesfalse(つまり、Exampleも存在しない)の場合にのみ警告を出力するようになりました。これにより、Exampleが存在する場合には、テスト関数がなくても警告は表示されなくなります。

これらの変更により、testingパッケージはExampleの存在を考慮し、より適切な警告メッセージの表示を行うようになりました。

関連リンク

  • Go issue #3237: このコミットが修正した問題のトラッキング。コミットメッセージに記載されているが、直接的なGitHubのリンクは提供されていない。しかし、以下のGo CLのリンクから関連するIssueを確認できる。
  • Go Code Review (CL) 5779043: https://golang.org/cl/5779043

参考にした情報源リンク