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

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

このコミットは、Go言語の標準ライブラリである encoding/xml パッケージ内の EncodeToken メソッドに対するテストを追加するものです。これにより、EncodeToken の様々な入力に対する挙動が検証され、堅牢性が向上します。

コミット

commit 58980821c7f8f5412da418365b717eeef4078846
Author: Shawn Smith <shawn.p.smith@gmail.com>
Date:   Wed Mar 5 14:49:33 2014 -0500

    encoding/xml: add test for EncodeToken
    
    LGTM=rsc
    R=golang-codereviews, josharian, dave, iant, bradfitz, rsc
    CC=golang-codereviews
    https://golang.org/cl/47870043

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

https://github.com/golang/go/commit/58980821c7f8f5412da418365b717eeef4078846

元コミット内容

encoding/xml: add test for EncodeToken
LGTM=rsc
R=golang-codereviews, josharian, dave, iant, bradfitz, rsc
CC=golang-codereviews
https://golang.org/cl/47870043

変更の背景

このコミットの背景には、encoding/xml パッケージの EncodeToken メソッドのテストカバレッジを向上させるという目的があります。EncodeToken は、XMLトークン(要素の開始、終了、文字データ、コメント、処理命令、ディレクティブなど)をXMLエンコーダに書き込むための内部的な、あるいは低レベルなメソッドであると推測されます。このような低レベルなAPIは、XMLドキュメントの正確な生成を保証するために、様々なエッジケースや不正な入力に対する挙動を厳密にテストすることが重要です。

既存のテストスイートでは EncodeToken の特定のシナリオがカバーされていなかった可能性があり、このコミットはそのギャップを埋めることを目的としています。これにより、将来的な変更やリファクタリングが行われた際にも、EncodeToken の既存の動作が意図せず破壊されることを防ぎ、パッケージ全体の安定性と信頼性を高めることができます。

前提知識の解説

XML (Extensible Markup Language)

XMLは、構造化されたデータを表現するためのマークアップ言語です。HTMLがウェブページの表示に特化しているのに対し、XMLはデータの意味を記述することに重点を置いています。XMLドキュメントは、要素、属性、テキスト、コメント、処理命令などの「ノード」で構成されます。

Go言語の encoding/xml パッケージ

Go言語の標準ライブラリには、XMLデータのエンコード(Goのデータ構造からXMLへの変換)とデコード(XMLからGoのデータ構造への変換)を扱う encoding/xml パッケージが含まれています。このパッケージは、XMLパーサーやエンコーダーを構築するための低レベルなAPIと、Goの構造体とXML要素をマッピングするための高レベルなAPI(xml.Marshalxml.Unmarshal など)を提供します。

XMLトークン (XML Token)

XMLドキュメントは、一連のトークンとして解釈することができます。主要なXMLトークンには以下のようなものがあります。

  • StartElement: 要素の開始タグ(例: <book>)
  • EndElement: 要素の終了タグ(例: </book>)
  • CharData: 要素内の文字データ(例: <title>Go Programming</title> の "Go Programming")
  • Comment: XMLコメント(例: <!-- This is a comment -->
  • ProcInst (Processing Instruction): 処理命令。XMLプロセッサに対する指示(例: <?php echo "Hello"; ?>
  • Directive: ドキュメント型宣言(DTD)などのディレクティブ(例: <!DOCTYPE html>

xml.EncoderEncodeToken

encoding/xml パッケージの xml.Encoder は、Goのデータ構造をXMLストリームに書き込むための型です。EncodeToken メソッドは、この xml.Encoder が提供する低レベルなAPIの一つであり、個々のXMLトークンを直接出力ストリームに書き込むために使用されます。通常、開発者が直接 EncodeToken を呼び出すことは稀で、xml.Marshal のような高レベルな関数が内部的にこれを利用します。しかし、カスタムのXMLエンコーディングロジックを実装する際には、EncodeToken が必要になる場合があります。

EncodeToken は、与えられた xml.Token 型の値をXML形式に変換し、エンコーダの出力先(io.Writer)に書き込みます。この際、XMLの仕様に準拠しているか、不正な文字が含まれていないかなどの検証も行われます。

技術的詳細

このコミットは、src/pkg/encoding/xml/marshal_test.go ファイルに encodeTokenTests という新しいテストケースのスライスと、TestEncodeToken というテスト関数を追加しています。

encodeTokenTests は、Token 型の入力、期待される出力文字列、そしてそのテストケースが成功するべきか(ok)を示すブール値の組み合わせを定義しています。これにより、EncodeToken が様々な種類のXMLトークンを正しくエンコードするか、あるいは不正な入力に対して適切にエラーを返すかを検証します。

具体的にテストされているケースは以下の通りです。

  • StartElement:
    • 名前空間とローカル名を持つ要素の開始タグ(例: <local xmlns="space">
    • 名前空間が空の不正な要素(エラーが期待される)
  • EndElement:
    • 名前空間が空の不正な終了タグ(エラーが期待される)
  • CharData:
    • 通常の文字データ
  • Comment:
    • 通常のコメント(例: <!--foo-->
    • 不正なコメント(--> を含むコメントはXML仕様で禁止されており、エラーが期待される)
  • ProcInst (Processing Instruction):
    • 通常の処理命令(例: <?Target Instruction?>
    • ターゲット名が "xml" の処理命令(XML宣言と混同されるため禁止されており、エラーが期待される)
    • 不正な処理命令(?> を含む処理命令はXML仕様で禁止されており、エラーが期待される)
  • Directive:
    • 通常のディレクティブ(例: <!foo>
    • 不正なディレクティブ(> を含むディレクティブはXML仕様で禁止されており、エラーが期待される)

TestEncodeToken 関数は、これらのテストケースをループで実行します。各テストケースでは、bytes.Buffer を出力先とする新しい xml.Encoder を作成し、enc.EncodeToken(tt.tok) を呼び出します。その後、以下の検証を行います。

  1. エラーの期待と実際:
    • tt.okfalse(エラーが期待される)にもかかわらずエラーが発生しなかった場合、テストは失敗します。
    • tt.oktrue(エラーが期待されない)にもかかわらずエラーが発生した場合、テストは失敗します。
    • tt.okfalse でエラーが発生した場合、それは期待通りの挙動であるため、テストは続行されます。
  2. 出力の検証:
    • enc.Flush() を呼び出してバッファに書き込まれた内容を確定させます。
    • buf.String() で取得した実際の出力と、tt.want で定義された期待される出力が一致するかを比較します。一致しない場合、テストは失敗します。

このテストの追加により、EncodeToken がXMLの仕様に厳密に準拠しているか、特に不正な入力に対するエラーハンドリングが適切に行われているかが確認できるようになります。

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

変更は src/pkg/encoding/xml/marshal_test.go ファイルに集中しています。

--- a/src/pkg/encoding/xml/marshal_test.go
+++ b/src/pkg/encoding/xml/marshal_test.go
@@ -1149,3 +1149,43 @@ func TestStructPointerMarshal(t *testing.T) {
 		t.Fatal(err)
 	}
 }
+
+var encodeTokenTests = []struct {
+	tok  Token
+	want string
+	ok   bool
+}{
+	{StartElement{Name{"space", "local"}, nil}, "<local xmlns=\"space\">", true},
+	{StartElement{Name{"space", ""}, nil}, "", false},
+	{EndElement{Name{"space", ""}}, "", false},
+	{CharData("foo"), "foo", true},
+	{Comment("foo"), "<!--foo-->", true},
+	{Comment("foo-->"), "", false},
+	{ProcInst{"Target", []byte("Instruction")}, "<?Target Instruction?>", true},
+	{ProcInst{"xml", []byte("Instruction")}, "", false},
+	{ProcInst{"Target", []byte("Instruction?>")}, "", false},
+	{Directive("foo"), "<!foo>", true},
+	{Directive("foo>"), "", false},
+}
+
+func TestEncodeToken(t *testing.T) {
+	for _, tt := range encodeTokenTests {
+		var buf bytes.Buffer
+		enc := NewEncoder(&buf)
+		err := enc.EncodeToken(tt.tok)
+		switch {
+		case !tt.ok && err == nil:
+			t.Errorf("enc.EncodeToken(%#v): expected error; got none", tt.tok)
+		case tt.ok && err != nil:
+			t.Fatalf("enc.EncodeToken: %v", err)
+		case !tt.ok && err != nil:
+			// expected error, got one
+		}
+		if err := enc.Flush(); err != nil {
+			t.Fatalf("enc.EncodeToken: %v", err)
+		}
+		if got := buf.String(); got != tt.want {
+			t.Errorf("enc.EncodeToken = %s; want: %s", got, tt.want)
+		}
+	}
+}

コアとなるコードの解説

encodeTokenTests スライス

このスライスは、EncodeToken メソッドのテストケースを構造体リテラルの配列として定義しています。各構造体は以下のフィールドを持ちます。

  • tok Token: EncodeToken に渡されるXMLトークン。xml.StartElement, xml.EndElement, xml.CharData, xml.Comment, xml.ProcInst, xml.Directive などの具体的なトークン型が使用されています。
  • want string: EncodeToken が成功した場合に期待される出力文字列。
  • ok bool: このテストケースがエラーを発生させずに成功するべきかどうかを示すフラグ。false の場合、エラーが発生することが期待されます。

このデータ駆動型テストのアプローチにより、多数の異なる入力パターンと期待される出力を簡潔に記述し、テストの可読性と保守性を高めています。

TestEncodeToken 関数

この関数は、Goの標準的なテストフレームワーク testing を使用して EncodeToken メソッドをテストします。

  1. for _, tt := range encodeTokenTests: encodeTokenTests スライス内の各テストケースを反復処理します。
  2. var buf bytes.Buffer: 各テストケースの実行ごとに、XMLエンコーダの出力先となる新しい bytes.Buffer を作成します。これにより、各テストケースが独立して実行され、前のテストケースの出力が影響しないようにします。
  3. enc := NewEncoder(&buf): 新しい xml.Encoder インスタンスを作成し、その出力先を buf に設定します。
  4. err := enc.EncodeToken(tt.tok): テスト対象の EncodeToken メソッドを呼び出し、結果のエラーを err 変数に格納します。
  5. switch ステートメントによるエラー検証:
    • !tt.ok && err == nil: エラーが期待される(tt.okfalse)にもかかわらずエラーが返されなかった場合、t.Errorf でテスト失敗を報告します。
    • tt.ok && err != nil: エラーが期待されない(tt.oktrue)にもかかわらずエラーが返された場合、t.Fatalf でテスト失敗を報告し、そのテストケースの実行を停止します。
    • !tt.ok && err != nil: エラーが期待され、実際にエラーが返された場合、これは期待通りの挙動であるため、何もしません。
  6. if err := enc.Flush(); err != nil: EncodeToken の呼び出し後、エンコーダのバッファをフラッシュします。これにより、すべてのデータが buf に書き込まれたことを保証します。ここでもエラーチェックが行われます。
  7. if got := buf.String(); got != tt.want: buf に書き込まれた実際の文字列(got)と、テストケースで定義された期待される文字列(tt.want)を比較します。一致しない場合、t.Errorf でテスト失敗を報告し、実際の出力と期待される出力を表示します。

このテスト関数は、EncodeToken がXML仕様に準拠した出力を生成するか、あるいは不正な入力に対して適切にエラーを返すかを網羅的に検証するための堅牢なフレームワークを提供しています。

関連リンク

参考にした情報源リンク

  • Go言語の公式ドキュメント
  • XMLのW3C勧告
  • Go言語のテストに関する一般的な知識