[インデックス 12904] ファイルの概要
このコミットは、Go言語の実験的なHTMLパーサーパッケージである exp/html
のテストメカニズムを改善するものです。具体的には、src/pkg/exp/html/parse_test.go
ファイルが変更され、より包括的なテスト実行と結果のロギング機能が導入されました。これにより、HTMLパーサーの開発者が、個々のバグ修正に限定されず、パーサー全体の健全性をより容易に評価できるようになります。
コミット
このコミットは、Go言語の exp/html
パッケージにおけるテストの網羅性を大幅に向上させることを目的としています。これまでのテストは限定的であり、特定の失敗するテストケースの修正に開発が集中しがちでした。この変更により、すべてのテストケースを実行し、その結果を詳細なログとして出力する機能が追加されました。また、ログが更新されていない場合でも、既存のログに基づいて回帰テストを実行できるようになり、開発の柔軟性を保ちつつ、品質を維持することが可能になりました。
GitHub上でのコミットページへのリンク
https://github.com/golang/go/commit/9a6cef8bbf1d62cbeb786f7a3eb9c009ab190ec9
元コミット内容
commit 9a6cef8bbf1d62cbeb786f7a3eb9c009ab190ec9
Author: Andrew Balholm <andybalholm@gmail.com>
Date: Tue Apr 17 17:17:22 2012 +1000
exp/html: more comprehensive tests
Currently, the html package only runs a limited subset of the tests
in the testdata directory. This tends to limit development of the
parser to fixing the bug that causes the first failing test.
This CL gives it the ability to run all the tests and produce a
log showing the status of each test. (It does it when tests are run with
'go test --update-logs') The status is listed as PASS, FAIL, or PARSE
(PARSE means that parsing produced the correct tree, but rendering and
re-parsing does not produce the same tree).
When 'go test' is run without --update-logs, it runs the tests marked
'PASS' in the logs (and the parsing portion of the tests marked 'PARSE').
Thus it will fail if there has been a regression since the last
time the logs were updated.
My goal for this CL is to allow develoment of the html package to
be less test-driven, while still having the advantages of regression
tests. In other words, one can work on any portion of the parser
and quickly see whether he is breaking things or improving them.
Current statistics of the tests:
$ grep ^PASS *.log|wc -l
1017
$ grep ^PARSE *.log|wc -l
46
$ grep ^FAIL *.log|wc -l
181
R=nigeltao
CC=golang-dev
https://golang.org/cl/6031049
変更の背景
この変更の背景には、Go言語の exp/html
パッケージのテストプロセスにおける課題がありました。以前は、testdata
ディレクトリ内のテストケースのごく一部しか実行されていませんでした。この限定的なテスト実行は、開発者がパーサーのバグを修正する際に、最初に失敗するテストケースにのみ焦点を当ててしまい、パーサー全体の健全性や他の部分への影響を見落とす可能性がありました。
開発者は、特定のバグを修正するたびに、その修正が他の既存の機能に悪影響を与えていないか(回帰バグが発生していないか)を確認する必要があります。しかし、テストの網羅性が低いと、この回帰テストが不十分になり、開発効率とコード品質の両方に悪影響を及ぼしていました。
このコミットの目的は、HTMLパッケージの開発を「テスト駆動」から解放しつつ、回帰テストの利点を維持することです。つまり、開発者はパーサーのどの部分でも作業でき、その変更が既存の機能を壊していないか、あるいは改善しているかを迅速に確認できるようになることを目指しています。
前提知識の解説
HTMLパーシング
HTMLパーシングとは、HTMLドキュメント(テキスト形式のマークアップ)を読み込み、それをコンピュータが理解しやすい構造(通常はDOMツリーと呼ばれるツリー構造)に変換するプロセスです。このプロセスは、ウェブブラウザがウェブページを表示したり、検索エンジンがウェブコンテンツをインデックス化したりする上で不可欠です。HTMLは非常に柔軟な(そしてしばしば不完全な)マークアップ言語であるため、パーサーはエラー耐性があり、不正なHTMLでも適切に処理できる必要があります。
Go言語の exp/html
パッケージ
exp/html
は、Go言語の標準ライブラリの一部として提供されているHTMLパーサーです。exp
というプレフィックスが示す通り、これは実験的なパッケージであり、将来的に安定版の html
パッケージとして昇格されることを目指していました。このパッケージは、WHATWG (Web Hypertext Application Technology Working Group) のHTML Living Standardに準拠したパーシングルールを実装しており、ウェブブラウザがHTMLを解析するのと同じ方法でHTMLを解析することを目指しています。
回帰テスト (Regression Testing)
回帰テストとは、ソフトウェアの変更(バグ修正、新機能追加など)が、既存の機能に予期せぬ副作用(回帰バグ)を引き起こしていないことを確認するために行われるテストです。このコミットでは、HTMLパーサーの変更が、以前は正しく動作していたパーシング結果を壊していないことを確認するために、包括的な回帰テストが重要視されています。
Go言語の go test
コマンド
go test
は、Go言語のテストを実行するためのコマンドです。Goのテストは、_test.go
で終わるファイルに記述され、Test
で始まる関数として定義されます。go test
コマンドは、これらのテスト関数を自動的に発見し、実行します。
コマンドラインフラグ (flag
パッケージ)
Go言語の flag
パッケージは、コマンドライン引数を解析するための標準ライブラリです。このコミットでは、--update-logs
という新しいフラグが導入されており、go test
コマンドにこのフラグを渡すことで、テストの動作を変更できるようになっています。
技術的詳細
このコミットは、exp/html
パッケージのテストフレームワークに2つの主要な機能を追加します。
-
全テストケースの実行とログ生成 (
--update-logs
フラグ):go test --update-logs
コマンドを実行すると、testdata
ディレクトリ内のすべての.dat
テストファイルが読み込まれます。- 各テストケースについて、HTMLパーシングが実行され、その結果が期待されるDOMツリーと比較されます。
- テスト結果は、
PASS
、FAIL
、PARSE
のいずれかのステータスで記録されます。PASS
: パーシングが成功し、期待されるDOMツリーと完全に一致し、さらにレンダリングと再パーシングを行っても同じツリーが生成される場合。FAIL
: パーシング中にエラーが発生したか、パーシング結果が期待されるDOMツリーと一致しない場合。PARSE
: パーシング自体は成功し、期待されるDOMツリーと一致するが、その結果をレンダリングして再度パーシングすると、元のツリーと異なるツリーが生成される場合。これは、パーサーがHTMLを正しく解釈しているものの、その後の処理(例えば、DOMツリーのシリアライズ)に問題がある可能性を示唆します。
- これらのステータスは、各テストファイルに対応する
.log
ファイル(例:testdata/webkit/adoption01.dat
に対応するtestlogs/adoption01.dat.log
)に記録されます。
-
ログに基づいた回帰テスト (通常実行):
go test
(フラグなし) コマンドを実行すると、テストは.log
ファイルに記録された情報に基づいて実行されます。- 具体的には、ログで
PASS
とマークされたテストケースは完全に実行されます。 PARSE
とマークされたテストケースは、パーシング部分のみが実行されます。これは、レンダリングと再パーシングのフェーズで問題がある可能性のあるテストを、パーシングの回帰テストから除外するためです。FAIL
とマークされたテストケースはスキップされます。- このメカニズムにより、ログが最後に更新されてから回帰が発生した場合(つまり、以前
PASS
またはPARSE
だったテストがFAIL
になった場合)、テストが失敗するようになります。これにより、開発者は既存の機能が壊れていないことを確認できます。
この新しいテストフレームワークは、開発者がパーサーの特定のバグ修正に集中するのではなく、パーサー全体の改善に注力できるようにすることを目的としています。--update-logs
を使用してログを定期的に更新することで、パーサーの現在の状態を正確に反映した回帰テストスイートを維持できます。
コアとなるコードの変更箇所
このコミットにおけるコアとなるコードの変更は、主に src/pkg/exp/html/parse_test.go
ファイルに集中しています。
-
flag
パッケージの導入と--update-logs
フラグの追加:import ( // ... "flag" // ... ) var updateLogs = flag.Bool("update-logs", false, "Update the log files that show the test results")
これにより、
go test
コマンドに--update-logs
というブール型フラグを追加できるようになりました。 -
parseTestResult
型の定義:type parseTestResult int const ( parseTestFailed parseTestResult = iota parseTestParseOnly parseTestPassed ) func (r parseTestResult) String() string { // ... }
テスト結果の状態 (
FAIL
,PARSE
,PASS
) を表現するための列挙型が導入されました。 -
TestParser
関数の大幅な変更:filepath.Glob(testDataDir + "*.dat")
を使用して、testdata/webkit/
ディレクトリ内のすべての.dat
ファイルを動的に読み込むようになりました。これにより、新しいテストファイルが追加されても、自動的にテスト対象に含まれるようになります。- テストログファイル (
.log
拡張子) の作成または読み込みロジックが追加されました。--update-logs
フラグがtrue
の場合はログファイルが作成され、false
の場合は既存のログファイルが読み込まれます。 - テストループが変更され、各テストケースの実行前にログファイルから期待される結果を読み込むか、テスト結果をログファイルに書き込むようになりました。
testParseCase
関数が導入され、個々のテストケースの解析と検証ロジックが分離されました。
-
testParseCase
関数の追加:func testParseCase(text, want, context string) (result parseTestResult, err error) { // ... }
この新しい関数は、単一のHTMLパーシングテストケースを実行し、その結果を
parseTestResult
型で返します。この関数は、以下のステップを実行します。- HTMLテキストを解析し、期待されるDOMツリーと比較します。
- 解析結果が期待と異なる場合、
parseTestFailed
を返します。 - 解析が成功した場合、さらにレンダリングと再パーシングを行い、その結果が元のツリーと一致するかを検証します。
- レンダリングと再パーシングが成功した場合、
parseTestPassed
を返します。 - レンダリングと再パーシングが失敗した場合、
parseTestParseOnly
を返します。
-
多数の新しいテストログファイルの追加:
src/pkg/exp/html/testlogs/
ディレクトリ以下に、既存のtestdata
ファイルに対応する多数の.log
ファイルが新規追加されています。これらは、--update-logs
フラグが導入された際に生成された初期のテスト結果ログです。
コアとなるコードの解説
parse_test.go
の主要な変更点は、テストの実行方法と結果の管理方法を根本的に変えたことです。
以前は、TestParser
関数内にハードコードされたテストファイルのリストがあり、その中から一部のテストケースのみを実行していました。このコミットでは、filepath.Glob(testDataDir + "*.dat")
を使用することで、testdata/webkit/
ディレクトリ内のすべての .dat
ファイルを動的に発見し、テスト対象とします。これにより、新しいテストデータが追加された際に、テストコードを変更することなく自動的にテストに含めることができるようになりました。
最も重要な変更は、--update-logs
フラグの導入と、それに関連するテスト結果のロギングメカニズムです。
-
go test --update-logs
の実行時:TestParser
関数は、各.dat
テストファイルに対応する.log
ファイルをtestlogs/
ディレクトリに作成します。- 各テストケースが
testParseCase
関数によって実行され、その結果 (PASS
,FAIL
,PARSE
) と元のHTMLテキストが.log
ファイルに書き込まれます。 testParseCase
関数は、HTMLの解析だけでなく、解析されたDOMツリーを再度HTMLにレンダリングし、そのHTMLを再解析して元のツリーと一致するかどうかも検証します。これにより、パーサーの出力が安定しているかどうかも確認できます。
-
go test
(フラグなし) の実行時:TestParser
関数は、既存の.log
ファイルを読み込みます。- 各テストケースについて、ログファイルに記録された期待される結果 (
expectedResult
) と元のHTMLテキストを読み取ります。 expectedResult
がFAIL
の場合、そのテストケースはスキップされます。これは、既知の失敗テストを毎回実行しないことで、テスト時間を短縮し、開発者が新しい回帰に集中できるようにするためです。expectedResult
がPASS
またはPARSE
の場合、testParseCase
関数が実行されます。testParseCase
の実際の実行結果 (result
) がexpectedResult
よりも悪い場合(例: ログではPASS
だったが、今回はFAIL
になった場合)、t.Errorf
が呼び出され、テストが失敗します。これにより、回帰バグが検出されます。
このメカニズムにより、開発者は go test --update-logs
を実行してパーサーの現在の状態を「スナップショット」としてログに記録し、その後は通常の go test
を実行することで、そのスナップショットからの回帰を効率的に検出できるようになります。これにより、開発者はパーサーの特定のバグ修正に集中するのではなく、パーサー全体の改善に注力できるようになります。
関連リンク
- Go CL 6031049: https://golang.org/cl/6031049
参考にした情報源リンク
- Go の exp/html パッケージに関するドキュメント (当時の情報に基づく) (現在の
html
パッケージに統合されている可能性がありますが、当時のexp/html
の役割を理解するために参照) - WHATWG HTML Living Standard (HTMLパーシングの標準仕様)
- Go言語のテスト (go test)
- Go言語の flag パッケージ
- 回帰テストとは - IT用語辞典 e-Words