[インデックス 10325] ファイルの概要
コミット
commit f6615f1b5d7e2e3e621c32fa9f99521f1c3a5f2b
Author: Rob Pike <r@golang.org>
Date: Wed Nov 9 13:19:23 2011 -0800
FAQ: rearrange and expand the discussion of testing
R=gri, r, bradfitz, rsc
CC=golang-dev
https://golang.org/cl/5369052
GitHub上でのコミットページへのリンク
https://github.com/golang/go/commit/f6615f1b5d7e2e3e621c32fa9f99521f1c3a5f2b
元コミット内容
FAQ: rearrange and expand the discussion of testing
R=gri, r, bradfitz, rsc
CC=golang-dev
https://golang.org/cl/5369052
変更の背景
このコミットは、Go言語の公式FAQドキュメント(doc/go_faq.html
)におけるテストに関する記述を再構成し、拡張することを目的としています。特に、Go言語のテスト哲学、アサーションの非推奨、そしてテーブル駆動テストの利点と実践方法について、より明確かつ詳細な説明を加えることが変更の背景にあります。
Go言語は、他の多くのプログラミング言語とは異なり、標準ライブラリに強力なアサーションフレームワークを提供していません。これはGoの設計思想に基づいています。このコミットは、なぜGoがアサーションを推奨しないのか、そして代わりにどのようなテスト手法を用いるべきかという、開発者からの一般的な疑問に答えるために行われました。
具体的には、既存のFAQセクションで触れられていたアサーションに関する議論を、テストに関する新しいセクションに移動させ、さらにその内容を拡充することで、Goのテストに関するベストプラクティスをより効果的に伝えることを目指しています。
前提知識の解説
Go言語のテスト哲学
Go言語のテスト哲学は、シンプルさと標準ライブラリの活用に重点を置いています。他の言語でよく見られるような、複雑なテストフレームワークや専用のアサーションライブラリに依存するのではなく、Goでは標準のtesting
パッケージとGo言語の基本的な構文(if
文、比較演算子など)を用いてテストを記述することを推奨しています。
この哲学の根底には、以下の考え方があります。
- テストを通常のGoコードとして扱う: テストコードもアプリケーションコードと同様に、読みやすく、理解しやすく、保守しやすいものであるべきという考えです。
- ミニマリズム: 必要最小限の機能だけを提供し、開発者がGoの言語機能を使ってテストロジックを自由に構築できるようにします。
- 明確なエラー報告: テストが失敗した場合、何が、なぜ失敗したのかを明確に伝えるエラーメッセージを重視します。
testing
パッケージ
Goの標準ライブラリに含まれるtesting
パッケージは、ユニットテスト、ベンチマークテスト、および例(Example)テストをサポートします。
- テストファイルは、テスト対象のパッケージと同じディレクトリに配置され、ファイル名が
_test.go
で終わる必要があります。 - テスト関数は
Test
で始まり、*testing.T
型の引数を取ります(例:func TestMyFunction(t *testing.T)
)。 - テストの失敗を報告するには、
t.Errorf()
(テストを続行)またはt.Fatalf()
(テストを即座に終了)を使用します。
アサーション(Assertion)
アサーションとは、プログラムの特定の時点である条件が真であることを表明(assert)するものです。多くのプログラミング言語のテストフレームワークでは、assert.Equal(expected, actual)
のようなアサーション関数が提供されており、これによりテストコードを簡潔に記述できます。
しかし、Go言語の標準ライブラリには、このような汎用的なアサーション関数は含まれていません。Goでは、if actual != expected { t.Errorf(...) }
のように、通常のif
文とt.Errorf
を組み合わせて条件をチェックし、エラーを報告することを推奨しています。
Goがアサーションを推奨しない主な理由は以下の通りです。
- テストの継続性: アサーションが失敗すると、そのテストケースの実行が中断されることがあります。Goの哲学では、一つのテストが失敗しても、他のテストが実行され続けることで、デバッガが問題の全体像を把握しやすくなると考えられています。
- 言語のシンプルさ: アサーションライブラリは、それ自体が小さなDSL(Domain Specific Language)のようになりがちです。Goは、テストも通常のGoコードとして記述することで、学習コストを減らし、コードの一貫性を保つことを重視します。
- 明確なエラーメッセージ: 汎用的なアサーション関数よりも、開発者が直接
t.Errorf
を使って具体的なエラーメッセージを記述する方が、デバッグ時に役立つ情報を提供できると考えられています。
テーブル駆動テスト(Table-Driven Tests)
テーブル駆動テストは、Go言語で非常に推奨されるテストパターンです。これは、複数の入力と期待される出力を持つテストケースを、構造体のスライス(テーブル)として定義し、そのテーブルをループで回しながらテストを実行する手法です。
テーブル駆動テストの利点は以下の通りです。
- コードの重複排除: 複数のテストケースに対して同じテストロジックを適用できるため、コードの重複を大幅に削減できます。
- 可読性の向上: テストケースの入力、期待される出力、およびテストケースの名前が一覧で分かりやすく定義されるため、テストの意図が明確になります。
- 保守性の向上: 新しいテストケースを追加するのが非常に簡単です。テーブルに新しいエントリを追加するだけで済みます。
- 網羅性の向上: エッジケースや境界条件を含む、多様なシナリオを効率的にテストできます。
- サブテストの活用:
t.Run()
と組み合わせることで、テーブルの各エントリを独立したサブテストとして実行し、個別に結果を報告できます。これにより、どのテストケースが失敗したかを正確に特定できます。
技術的詳細
このコミットは、Go言語の公式FAQドキュメントのdoc/go_faq.html
ファイルを修正し、テストに関するセクションを改善しています。主な変更点は、アサーションに関する既存の議論をより適切な位置に移動させ、Goのテスト哲学、特にアサーションの非推奨とテーブル駆動テストの推奨について、より詳細な説明を追加したことです。
変更前は、アサーションに関する議論が「エラー処理」のセクションの一部として存在していました。これは、アサーションがエラー処理の一形態と見なされるためですが、テストにおけるアサーションの役割とGoのテスト哲学をより明確に説明するためには、独立したセクションとして扱う方が適切であると判断されました。
新しいセクション「Where is my favorite helper function for testing?」では、Goのtesting
パッケージがアサーション関数のような機能を提供しない理由を、Goのテスト哲学と関連付けて説明しています。具体的には、テストが失敗しても他のテストが継続して実行されることの重要性、そしてテストフレームワークが独自のミニ言語になることの弊害を強調しています。
さらに、テストコードが繰り返しになりがちな場合に、テーブル駆動テストが非常に有効な解決策であることを提案しています。Goのデータ構造リテラルが優れている点を挙げ、fmt
パッケージのテスト(fmt/fmt_test.go
)を具体的な例として参照することで、実際のコードベースでの実践例を示しています。これにより、開発者はGoのテスト哲学を理解し、効果的なテストコードを記述するための具体的な指針を得ることができます。
この変更は、Go言語の設計思想とテストに関するベストプラクティスを、公式ドキュメントを通じてより広く、より深く浸透させるための重要なステップと言えます。
コアとなるコードの変更箇所
変更はdoc/go_faq.html
ファイルに対して行われています。
削除された箇所:
--- a/doc/go_faq.html
+++ b/doc/go_faq.html
@@ -350,26 +350,6 @@ errors are particularly important when the programmer seeing the errors is
not familiar with the code.
</p>
-<p>
-The same arguments apply to the use of <code>assert()</code> in test programs. Proper
-error handling means letting other tests run after one has failed, so
-that the person debugging the failure gets a complete picture of what is
-wrong. It is more useful for a test to report that
-<code>isPrime</code> gives the wrong answer for 2, 3, 5, and 7 (or for
-2, 4, 8, and 16) than to report that <code>isPrime</code> gives the wrong
-answer for 2 and therefore no more tests were run. The programmer who
-triggers the test failure may not be familiar with the code that fails.\
-Time invested writing a good error message now pays off later when the
-test breaks.
-</p>
-
-<p>
-In testing, if the amount of extra code required to write
-good errors seems repetitive and overwhelming, it might work better as a
-table-driven test instead.
-Go has excellent support for data structure literals.
-</p>
-
<p>
We understand that this is a point of contention. There are many things in
the Go language and libraries that differ from modern practices, simply
この部分では、アサーションとテーブル駆動テストに関する既存の短い記述が削除されています。これは、これらの内容が新しい、より詳細なセクションに移動されるためです。
追加された箇所:
--- a/doc/go_faq.html
+++ b/doc/go_faq.html
@@ -1196,6 +1176,45 @@ builds a test binary, and runs it.\
\
<p>See the <a href=\"/doc/code.html\">How to Write Go Code</a> document for more details.</p>\
\
+<h3 id=\"testing_framework\">\
+Where is my favorite helper function for testing?</h3>\
+\
+<p>\
+Go\'s standard <code>testing</code> package makes it easy to write unit tests, but it lacks\
+features provided in other language\'s testing frameworks such as assertion functions.\
+An <a href=\"#assertions\">earlier section</a> of this document explained why Go\
+doesn\'t have assertions, and\
+the same arguments apply to the use of <code>assert</code> in tests.\
+Proper error handling means letting other tests run after one has failed, so\
+that the person debugging the failure gets a complete picture of what is\
+wrong. It is more useful for a test to report that\
+<code>isPrime</code> gives the wrong answer for 2, 3, 5, and 7 (or for\
+2, 4, 8, and 16) than to report that <code>isPrime</code> gives the wrong\
+answer for 2 and therefore no more tests were run. The programmer who\
+triggers the test failure may not be familiar with the code that fails.\
+Time invested writing a good error message now pays off later when the\
+test breaks.\
+</p>\
+\
+<p>\
+A related point is that testing frameworks tend to develop into mini-languages\
+of their own, with conditionals and controls and printing mechanisms,\
+but Go already has all those capabilities; why recreate them?\
+We\'d rather write tests in Go; it\'s one fewer language to learn and the\
+approach keeps the tests straightforward and easy to understand.\
+</p>\
+\
+<p>\
+If the amount of extra code required to write\
+good errors seems repetitive and overwhelming, the test might work better if\
+table-driven, iterating over a list of inputs and outputs defined\
+in a data structure (Go has excellent support for data structure literals).\
+The work to write a good test and good error messages will then be amortized over many\
+test cases. The standard Go library is full of illustrative examples, such as in\
+<a href=\"http://golang.org/src/pkg/fmt/fmt_test.go\">the formatting\
+tests for the <code>fmt</code> package</a>.\
+</p>\
+\
\
<h2 id=\"Implementation\">Implementation</h2>\
\
この追加されたセクションは、testing_framework
というIDを持つ新しい<h3>
見出しの下に配置されています。
コアとなるコードの解説
追加されたコードは、Goのテストに関するFAQの新しいセクションを形成しています。
-
<h3>
見出しの追加:<h3 id="testing_framework"> Where is my favorite helper function for testing?</h3>
この見出しは、他の言語のテストフレームワークに慣れている開発者がGoの
testing
パッケージにアサーション関数がないことに疑問を持つことを想定しています。 -
アサーションに関する詳細な説明:
<p> Go's standard <code>testing</code> package makes it easy to write unit tests, but it lacks features provided in other language's testing frameworks such as assertion functions. An <a href="#assertions">earlier section</a> of this document explained why Go doesn't have assertions, and the same arguments apply to the use of <code>assert</code> in tests. Proper error handling means letting other tests run after one has failed, so that the person debugging the failure gets a complete picture of what is wrong. It is more useful for a test to report that <code>isPrime</code> gives the wrong answer for 2, 3, 5, and 7 (or for 2, 4, 8, and 16) than to report that <code>isPrime</code> gives the wrong answer for 2 and therefore no more tests were run. The programmer who triggers the test failure may not be familiar with the code that fails. Time invested writing a good error message now pays off later when the test breaks. </p>
この段落では、Goの
testing
パッケージにアサーション関数がないことを明確にし、その理由を説明しています。特に、テストが失敗しても他のテストが継続して実行されることの重要性を強調し、デバッグ時の全体像の把握に役立つことを述べています。また、良いエラーメッセージを書くことの価値も指摘しています。 -
テストフレームワークの「ミニ言語化」に関する議論:
<p> A related point is that testing frameworks tend to develop into mini-languages of their own, with conditionals and controls and printing mechanisms, but Go already has all those capabilities; why recreate them? We'd rather write tests in Go; it's one fewer language to learn and the approach keeps the tests straightforward and easy to understand. </p>
ここでは、テストフレームワークが独自のDSL(Domain Specific Language)のように進化しがちであるという問題提起をしています。Goはすでに条件分岐、制御構造、出力メカニズムといった必要な機能をすべて持っているため、テストのためだけにこれらを再構築する必要はないというGoの哲学を説明しています。テストをGoで書くことで、学習する言語が一つ減り、テストコードがより直接的で理解しやすくなるという利点を強調しています。
-
テーブル駆動テストの推奨と例:
<p> If the amount of extra code required to write good errors seems repetitive and overwhelming, the test might work better if table-driven, iterating over a list of inputs and outputs defined in a data structure (Go has excellent support for data structure literals). The work to write a good test and good error messages will then be amortized over many test cases. The standard Go library is full of illustrative examples, such as in <a href="http://golang.org/src/pkg/fmt/fmt_test.go">the formatting tests for the <code>fmt</code> package</a>. </p>
この段落では、エラーメッセージの記述が繰り返しになり、煩雑に感じられる場合に、テーブル駆動テストが有効な解決策であることを提案しています。Goがデータ構造リテラルを強力にサポートしていることに触れ、これにより多くのテストケースにわたってテスト記述の労力を償却できると説明しています。最後に、標準ライブラリの
fmt
パッケージのテスト(fmt_test.go
)を具体的な例として挙げ、実践的な参考資料を提供しています。
これらの変更により、Goのテストに関する公式のガイダンスがより包括的で、開発者にとって実践的なものになっています。
関連リンク
- Go CL 5369052: https://golang.org/cl/5369052
- Goにおけるテストの書き方 (
How to Write Go Code
ドキュメント):/doc/code.html
(コミット内の相対パス) fmt
パッケージのテスト例: http://golang.org/src/pkg/fmt/fmt_test.go
参考にした情報源リンク
- Go's testing philosophy emphasizes simplicity and relies heavily on the built-in
testing
package: https://lwn.net/Articles/960000/ - Go's standard library does not include a rich assertion library: https://dev.to/karanpratapsingh/go-testing-without-assertion-libraries-302
- Third-party assertion libraries like
Testify
: https://betterstack.com/guides/testing-in-go/ - Table-driven tests are a widely adopted and idiomatic pattern in Go: https://go.dev/blog/subtests
- Example Structure of a Table-Driven Test: https://cheney.net/blog/go-table-driven-tests
- Benefits of Table-Driven Tests: https://semaphoreci.com/blog/table-driven-tests-go
- Go testing package assert: https://dev.to/karanpratapsingh/go-testing-without-assertion-libraries-302
stretchr/testify/assert
usage: https://grid.gg/blog/go-unit-testing-with-testify- Table-driven tests in Go: https://golang.cafe/blog/golang-table-driven-tests.html
- Go testing philosophy: https://medium.com/@ankur_anand/go-testing-philosophy-and-best-practices-a-comprehensive-guide-222121212121
- Table-driven tests for comprehensive testing: https://hashnode.dev/blog/table-driven-tests-in-go
- JetBrains on table-driven tests: https://www.jetbrains.com/go/guide/tutorials/table-driven-tests/