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

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

このコミットは、Go言語の標準ライブラリ encoding/json パッケージにおいて、Unmarshal 関数が不正な引数を受け取った場合に発生する InvalidUnmarshalError のテストを追加するものです。具体的には、Unmarshal 関数に nil やポインタではない構造体、nil ポインタが渡された場合の挙動を検証するテストケースが追加されています。

コミット

encoding/json: add tests for InvalidUnmarshalError

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

https://github.com/golang/go/commit/66730120fa2ff7d58d22a7e1cf9a1a299572a907

元コミット内容

commit 66730120fa2ff7d58d22a7e1cf9a1a299572a907
Author: Dave Cheney <dave@cheney.net>
Date:   Thu Jan 2 09:49:55 2014 +1100

    encoding/json: add tests for InvalidUnmarshalError
    
    R=golang-codereviews, shawn.p.smith
    CC=golang-codereviews
    https://golang.org/cl/41960047
---
 src/pkg/encoding/json/decode_test.go | 23 +++++++++++++++++++++++
 1 file changed, 23 insertions(+)

diff --git a/src/pkg/encoding/json/decode_test.go b/src/pkg/encoding/json/decode_test.go
index 22c5f89f79..c5a84ab832 100644
--- a/src/pkg/encoding/json/decode_test.go
+++ b/src/pkg/encoding/json/decode_test.go
@@ -1316,3 +1316,26 @@ func TestPrefilled(t *testing.T) {
 		}
 	}
 }\n+\n+var invalidUnmarshalTests = []struct {\n+\tv    interface{}\n+\twant string\n+}{\n+\t{nil, \"json: Unmarshal(nil)\"},\n+\t{struct{}{}, \"json: Unmarshal(non-pointer struct {})\"},\n+\t{(*int)(nil), \"json: Unmarshal(nil *int)\"},\n+}\n+\n+func TestInvalidUnmarshal(t *testing.T) {\n+\tbuf := []byte(`{\"a\":\"1\"}`)\n+\tfor _, tt := range invalidUnmarshalTests {\n+\t\terr := Unmarshal(buf, tt.v)\n+\t\tif err == nil {\n+\t\t\tt.Errorf(\"Unmarshal expecting error, got nil\")\n+\t\t\tcontinue\n+\t\t}\n+\t\tif got := err.Error(); got != tt.want {\n+\t\t\tt.Errorf(\"Unmarshal = %q; want %q\", got, tt.want)\n+\t\t}\n+\t}\n+}\n```

## 変更の背景

このコミットの背景には、`encoding/json` パッケージの `Unmarshal` 関数が、予期しない、または不正な入力に対してどのように振る舞うかを明確にし、その挙動を保証する必要があったことが挙げられます。`Unmarshal` 関数は、JSONデータをGoのデータ構造にデコードする際に非常に重要な役割を果たしますが、その引数 `v` はポインタである必要があります。ポインタでない値や `nil` が渡された場合、Goの型システムやメモリ管理の観点から、予期せぬパニックや不正な動作を引き起こす可能性があります。

このような不正な入力に対するエラーハンドリングは、ライブラリの堅牢性を高め、開発者が安全に利用できるようにするために不可欠です。特に、`InvalidUnmarshalError` は、`Unmarshal` 関数がデコード先の型として不適切な値を受け取った場合に発生するエラーであり、その発生条件とエラーメッセージが明確に定義されていることが重要です。このコミットは、これらのエラーケースに対するテストカバレッジを向上させ、将来的なリファクタリングや変更があった際にも、既存の堅牢なエラーハンドリングが維持されることを保証することを目的としています。

## 前提知識の解説

このコミットを理解するためには、以下のGo言語および`encoding/json`パッケージに関する知識が必要です。

*   **Go言語のポインタ**: Go言語では、変数のメモリアドレスを指し示す「ポインタ」という概念があります。`Unmarshal` 関数は、デコードしたJSONデータを指定されたGoのデータ構造に書き込むため、そのデータ構造へのポインタを引数として受け取る必要があります。ポインタを渡すことで、関数内でそのデータ構造の値を変更することができます。ポインタではない値を渡すと、関数はその値のコピーを受け取るだけであり、元の値を変更することはできません。
*   **`encoding/json` パッケージ**: Go言語の標準ライブラリの一つで、JSONデータとGoのデータ構造(構造体、スライス、マップなど)の間で変換を行う機能を提供します。
    *   **`json.Unmarshal` 関数**: このコミットの中心となる関数です。`Unmarshal(data []byte, v interface{}) error` というシグネチャを持ち、`data` に含まれるJSONバイト列をGoの値 `v` にデコードします。`v` はデコード先のGoのデータ構造へのポインタである必要があります。
    *   **`interface{}` 型**: Go言語における空のインターフェース型です。あらゆる型の値を保持できるため、`Unmarshal` 関数の `v` 引数のように、任意の型の値を受け取る必要がある場合に利用されます。しかし、`interface{}` 型自体はポインタではないため、`Unmarshal` に直接渡すだけではデコード先の値を変更できません。そのため、`interface{}` 型の変数にポインタを代入して渡す必要があります。
    *   **`InvalidUnmarshalError`**: `encoding/json` パッケージで定義されているエラー型の一つです。`Unmarshal` 関数が、デコード先の値として不適切な型(ポインタではない値、`nil`など)を受け取った場合に返されます。このエラーは、プログラマが `Unmarshal` 関数の使い方を誤っていることを明確に伝えるためのものです。

*   **Go言語のテスト**: Go言語には、標準でテストフレームワークが組み込まれています。
    *   **`testing` パッケージ**: Goのテスト機能を提供するパッケージです。
    *   **`func TestXxx(t *testing.T)`**: テスト関数は `Test` で始まり、`*testing.T` 型の引数を取ります。
    *   **`t.Errorf`**: テストが失敗したことを報告し、エラーメッセージを出力するために使用されます。
    *   **構造体リテラルと匿名構造体**: テストケースをまとめるために、構造体のスライスがよく用いられます。このコミットでも `invalidUnmarshalTests` という匿名構造体のスライスが定義されています。

これらの知識を前提として、このコミットが `Unmarshal` 関数のエラーハンドリングの堅牢性をどのように向上させているかを理解することができます。

## 技術的詳細

このコミットは、`encoding/json` パッケージの `decode_test.go` ファイルに `TestInvalidUnmarshal` という新しいテスト関数を追加しています。このテストの目的は、`json.Unmarshal` 関数が不正な引数 `v` を受け取った場合に、期待される `InvalidUnmarshalError` を正しく返すことを検証することです。

テストは `invalidUnmarshalTests` という匿名構造体のスライスを定義しています。このスライスは、以下の3つのテストケースを含んでいます。

1.  `{nil, "json: Unmarshal(nil)"}`:
    *   `v` に `nil` を渡した場合のテストケースです。
    *   `Unmarshal` 関数に `nil` を渡すことは、デコード先のメモリ領域が指定されていないことを意味するため、`json: Unmarshal(nil)` というエラーメッセージが期待されます。
2.  `{struct{}{}, "json: Unmarshal(non-pointer struct {})"}`:
    *   `v` にポインタではない空の構造体 `struct{}` を渡した場合のテストケースです。
    *   `Unmarshal` 関数はデコード先の値を変更するためにポインタを必要としますが、ここでは値渡しされているため、`json: Unmarshal(non-pointer struct {})` というエラーメッセージが期待されます。
3.  `{(*int)(nil), "json: Unmarshal(nil *int)"}`:
    *   `v` に `nil` の `*int` 型ポインタを渡した場合のテストケースです。
    *   ポインタ自体は `nil` であるため、有効なメモリ領域を指していません。この場合、`json: Unmarshal(nil *int)` というエラーメッセージが期待されます。

`TestInvalidUnmarshal` 関数内では、`buf := []byte(`{"a":"1"}`)` というダミーのJSONデータが用意されています。これは、`Unmarshal` 関数が実際にデコード処理を開始する前に、引数 `v` の妥当性をチェックする段階でエラーが発生することを検証するためです。

テストループでは、`invalidUnmarshalTests` の各テストケースに対して以下の検証が行われます。

1.  `err := Unmarshal(buf, tt.v)`: `Unmarshal` 関数を呼び出し、結果のエラーを取得します。
2.  `if err == nil`: エラーが返されなかった場合、`t.Errorf("Unmarshal expecting error, got nil")` を呼び出してテストを失敗させます。これは、これらのケースでは必ずエラーが返されるべきであるためです。
3.  `if got := err.Error(); got != tt.want`: 返されたエラーメッセージ `err.Error()` が、期待されるエラーメッセージ `tt.want` と一致するかを検証します。一致しない場合、`t.Errorf("Unmarshal = %q; want %q", got, tt.want)` を呼び出してテストを失敗させます。

このテストの追加により、`Unmarshal` 関数が不正な引数に対して一貫したエラーメッセージを返すことが保証され、開発者が `Unmarshal` 関数の誤用を特定しやすくなります。また、将来的に `Unmarshal` 関数の内部実装が変更された場合でも、これらのエラーケースの挙動が維持されることを保証する回帰テストとしての役割も果たします。

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

変更は `src/pkg/encoding/json/decode_test.go` ファイルに集中しており、具体的には以下の部分が追加されています。

```go
var invalidUnmarshalTests = []struct {
	v    interface{}
	want string
}{
	{nil, "json: Unmarshal(nil)"},
	{struct{}{}, "json: Unmarshal(non-pointer struct {})"},
	{(*int)(nil), "json: Unmarshal(nil *int)"},
}

func TestInvalidUnmarshal(t *testing.T) {
	buf := []byte(`{"a":"1"}`)
	for _, tt := range invalidUnmarshalTests {
		err := Unmarshal(buf, tt.v)
		if err == nil {
			t.Errorf("Unmarshal expecting error, got nil")
			continue
		}
		if got := err.Error(); got != tt.want {
			t.Errorf("Unmarshal = %q; want %q", got, tt.want)
		}
	}
}

コアとなるコードの解説

  • invalidUnmarshalTests 変数:

    • これは、TestInvalidUnmarshal 関数で使用されるテストケースを定義する匿名構造体のスライスです。
    • 各要素は、v (テスト対象の Unmarshal 関数の第2引数となる値) と want (期待されるエラーメッセージ) の2つのフィールドを持ちます。
    • v interface{}: Unmarshal 関数の引数 v の型に合わせて interface{} と宣言されています。これにより、nil、構造体、nil ポインタなど、様々な型の値をテストできます。
    • want string: Unmarshal 関数が返す errorError() メソッドが返す文字列と一致することを期待するエラーメッセージです。
    • 定義されている3つのテストケースは、Unmarshal 関数が InvalidUnmarshalError を返す典型的なシナリオをカバーしています。
  • TestInvalidUnmarshal 関数:

    • Goのテスト関数として標準的なシグネチャ func TestXxx(t *testing.T) を持ちます。
    • buf := []byte({"a":"1"}): これは、Unmarshal 関数に渡されるダミーのJSONバイト列です。このテストの目的は Unmarshal の引数 v の検証であり、JSONデータのパース自体ではないため、どのような有効なJSONデータでも構いません。
    • for _, tt := range invalidUnmarshalTests: invalidUnmarshalTests スライスをイテレートし、各テストケースを実行します。
    • err := Unmarshal(buf, tt.v): encoding/json パッケージの Unmarshal 関数を呼び出します。ここで、tt.v には nilstruct{}(*int)(nil) のいずれかが渡されます。
    • if err == nil: Unmarshal 関数がエラーを返さなかった場合、t.Errorf を使ってテストを失敗させます。これらのテストケースでは、Unmarshal は必ずエラーを返すことが期待されるためです。
    • if got := err.Error(); got != tt.want: Unmarshal が返したエラーの文字列表現 (err.Error()) が、テストケースで定義された期待値 (tt.want) と一致するかを検証します。一致しない場合、t.Errorf を使ってテストを失敗させ、実際のエラーメッセージと期待されるエラーメッセージを出力します。

このコードは、Unmarshal 関数のエラーハンドリングの正確性と一貫性を保証するための、シンプルかつ効果的な回帰テストとして機能します。

関連リンク

参考にした情報源リンク

  • Go言語の公式ドキュメント
  • Go言語のソースコード (特に encoding/json パッケージ)
  • Go言語のテストに関する一般的なプラクティス
  • InvalidUnmarshalError に関するGoコミュニティでの議論 (必要に応じてWeb検索で調査)

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

このコミットは、Go言語の標準ライブラリ encoding/json パッケージにおいて、Unmarshal 関数が不正な引数を受け取った場合に発生する InvalidUnmarshalError のテストを追加するものです。具体的には、Unmarshal 関数に nil やポインタではない構造体、nil ポインタが渡された場合の挙動を検証するテストケースが追加されています。

コミット

encoding/json: add tests for InvalidUnmarshalError

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

https://github.com/golang/go/commit/66730120fa2ff7d58d22a7e1cf9a1a299572a907

元コミット内容

commit 66730120fa2ff7d58d22a7e1cf9a1a299572a907
Author: Dave Cheney <dave@cheney.net>
Date:   Thu Jan 2 09:49:55 2014 +1100

    encoding/json: add tests for InvalidUnmarshalError
    
    R=golang-codereviews, shawn.p.smith
    CC=golang-codereviews
    https://golang.org/cl/41960047
---
 src/pkg/encoding/json/decode_test.go | 23 +++++++++++++++++++++++
 1 file changed, 23 insertions(+)

diff --git a/src/pkg/encoding/json/decode_test.go b/src/pkg/encoding/json/decode_test.go
index 22c5f89f79..c5a84ab832 100644
--- a/src/pkg/encoding/json/decode_test.go
+++ b/src/pkg/encoding/json/decode_test.go
@@ -1316,3 +1316,26 @@ func TestPrefilled(t *testing.T) {
 		}
 	}
 }\n+\n+var invalidUnmarshalTests = []struct {\n+\tv    interface{}\n+\twant string\n+}{\n+\t{nil, "json: Unmarshal(nil)"},\n+\t{struct{}{}, "json: Unmarshal(non-pointer struct {})"},\n+\t{(*int)(nil), "json: Unmarshal(nil *int)"},\n+}\n+\n+func TestInvalidUnmarshal(t *testing.T) {\n+\tbuf := []byte(`{"a":"1"}`)\n+\tfor _, tt := range invalidUnmarshalTests {\n+\t\terr := Unmarshal(buf, tt.v)\n+\t\tif err == nil {\n+\t\t\tt.Errorf("Unmarshal expecting error, got nil")\n+\t\t\tcontinue\n+\t\t}\n+\t\tif got := err.Error(); got != tt.want {\n+\t\t\tt.Errorf("Unmarshal = %q; want %q", got, tt.want)\n+\t\t}\n+\t}\n+}\n```

## 変更の背景

このコミットの背景には、`encoding/json` パッケージの `Unmarshal` 関数が、予期しない、または不正な入力に対してどのように振る舞うかを明確にし、その挙動を保証する必要があったことが挙げられます。`Unmarshal` 関数は、JSONデータをGoのデータ構造にデコードする際に非常に重要な役割を果たしますが、その引数 `v` はポインタである必要があります。ポインタでない値や `nil` が渡された場合、Goの型システムやメモリ管理の観点から、予期せぬパニックや不正な動作を引き起こす可能性があります。

このような不正な入力に対するエラーハンドリングは、ライブラリの堅牢性を高め、開発者が安全に利用できるようにするために不可欠です。特に、`InvalidUnmarshalError` は、`Unmarshal` 関数がデコード先の型として不適切な値を受け取った場合に発生するエラーであり、その発生条件とエラーメッセージが明確に定義されていることが重要です。このコミットは、これらのエラーケースに対するテストカバレッジを向上させ、将来的なリファクタリングや変更があった際にも、既存の堅牢なエラーハンドリングが維持されることを保証することを目的としています。

## 前提知識の解説

このコミットを理解するためには、以下のGo言語および`encoding/json`パッケージに関する知識が必要です。

*   **Go言語のポインタ**: Go言語では、変数のメモリアドレスを指し示す「ポインタ」という概念があります。`Unmarshal` 関数は、デコードしたJSONデータを指定されたGoのデータ構造に書き込むため、そのデータ構造へのポインタを引数として受け取る必要があります。ポインタを渡すことで、関数内でそのデータ構造の値を変更することができます。ポインタではない値を渡すと、関数はその値のコピーを受け取るだけであり、元の値を変更することはできません。
*   **`encoding/json` パッケージ**: Go言語の標準ライブラリの一つで、JSONデータとGoのデータ構造(構造体、スライス、マップなど)の間で変換を行う機能を提供します。
    *   **`json.Unmarshal` 関数**: このコミットの中心となる関数です。`Unmarshal(data []byte, v interface{}) error` というシグネチャを持ち、`data` に含まれるJSONバイト列をGoの値 `v` にデコードします。`v` はデコード先のGoのデータ構造へのポインタである必要があります。
    *   **`interface{}` 型**: Go言語における空のインターフェース型です。あらゆる型の値を保持できるため、`Unmarshal` 関数の `v` 引数のように、任意の型の値を受け取る必要がある場合に利用されます。しかし、`interface{}` 型自体はポインタではないため、`Unmarshal` に直接渡すだけではデコード先の値を変更できません。そのため、`interface{}` 型の変数にポインタを代入して渡す必要があります。
    *   **`InvalidUnmarshalError`**: `encoding/json` パッケージで定義されているエラー型の一つです。`Unmarshal` 関数が、デコード先の値として不適切な型(ポインタではない値、`nil`など)を受け取った場合に返されます。このエラーは、プログラマが `Unmarshal` 関数の使い方を誤っていることを明確に伝えるためのものです。

*   **Go言語のテスト**: Go言語には、標準でテストフレームワークが組み込まれています。
    *   **`testing` パッケージ**: Goのテスト機能を提供するパッケージです。
    *   **`func TestXxx(t *testing.T)`**: テスト関数は `Test` で始まり、`*testing.T` 型の引数を取ります。
    *   **`t.Errorf`**: テストが失敗したことを報告し、エラーメッセージを出力するために使用されます。
    *   **構造体リテラルと匿名構造体**: テストケースをまとめるために、構造体のスライスがよく用いられます。このコミットでも `invalidUnmarshalTests` という匿名構造体のスライスが定義されています。

これらの知識を前提として、このコミットが `Unmarshal` 関数のエラーハンドリングの堅牢性をどのように向上させているかを理解することができます。

## 技術的詳細

このコミットは、`encoding/json` パッケージの `decode_test.go` ファイルに `TestInvalidUnmarshal` という新しいテスト関数を追加しています。このテストの目的は、`json.Unmarshal` 関数が不正な引数 `v` を受け取った場合に、期待される `InvalidUnmarshalError` を正しく返すことを検証することです。

テストは `invalidUnmarshalTests` という匿名構造体のスライスを定義しています。このスライスは、以下の3つのテストケースを含んでいます。

1.  `{nil, "json: Unmarshal(nil)"}`:
    *   `v` に `nil` を渡した場合のテストケースです。
    *   `Unmarshal` 関数に `nil` を渡すことは、デコード先のメモリ領域が指定されていないことを意味するため、`json: Unmarshal(nil)` というエラーメッセージが期待されます。
2.  `{struct{}{}, "json: Unmarshal(non-pointer struct {})"}`:
    *   `v` にポインタではない空の構造体 `struct{}` を渡した場合のテストケースです。
    *   `Unmarshal` 関数はデコード先の値を変更するためにポインタを必要としますが、ここでは値渡しされているため、`json: Unmarshal(non-pointer struct {})` というエラーメッセージが期待されます。
3.  `{(*int)(nil), "json: Unmarshal(nil *int)"}`:
    *   `v` に `nil` の `*int` 型ポインタを渡した場合のテストケースです。
    *   ポインタ自体は `nil` であるため、有効なメモリ領域を指していません。この場合、`json: Unmarshal(nil *int)` というエラーメッセージが期待されます。

`TestInvalidUnmarshal` 関数内では、`buf := []byte(`{"a":"1"}`)` というダミーのJSONデータが用意されています。これは、`Unmarshal` 関数が実際にデコード処理を開始する前に、引数 `v` の妥当性をチェックする段階でエラーが発生することを検証するためです。

テストループでは、`invalidUnmarshalTests` の各テストケースに対して以下の検証が行われます。

1.  `err := Unmarshal(buf, tt.v)`: `Unmarshal` 関数を呼び出し、結果のエラーを取得します。
2.  `if err == nil`: エラーが返されなかった場合、`t.Errorf("Unmarshal expecting error, got nil")` を呼び出してテストを失敗させます。これは、これらのケースでは必ずエラーが返されるべきであるためです。
3.  `if got := err.Error(); got != tt.want`: 返されたエラーメッセージ `err.Error()` が、期待されるエラーメッセージ `tt.want` と一致するかを検証します。一致しない場合、`t.Errorf("Unmarshal = %q; want %q", got, tt.want)` を呼び出してテストを失敗させます。

このテストの追加により、`Unmarshal` 関数が不正な引数に対して一貫したエラーメッセージを返すことが保証され、開発者が `Unmarshal` 関数の誤用を特定しやすくなります。また、将来的に `Unmarshal` 関数の内部実装が変更された場合でも、これらのエラーケースの挙動が維持されることを保証する回帰テストとしての役割も果たします。

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

変更は `src/pkg/encoding/json/decode_test.go` ファイルに集中しており、具体的には以下の部分が追加されています。

```go
var invalidUnmarshalTests = []struct {
	v    interface{}
	want string
}{
	{nil, "json: Unmarshal(nil)"},
	{struct{}{}, "json: Unmarshal(non-pointer struct {})"},
	{(*int)(nil), "json: Unmarshal(nil *int)"},
}

func TestInvalidUnmarshal(t *testing.T) {
	buf := []byte(`{"a":"1"}`)
	for _, tt := range invalidUnmarshalTests {
		err := Unmarshal(buf, tt.v)
		if err == nil {
			t.Errorf("Unmarshal expecting error, got nil")
			continue
		}
		if got := err.Error(); got != tt.want {
			t.Errorf("Unmarshal = %q; want %q", got, tt.want)
		}
	}
}

コアとなるコードの解説

  • invalidUnmarshalTests 変数:

    • これは、TestInvalidUnmarshal 関数で使用されるテストケースを定義する匿名構造体のスライスです。
    • 各要素は、v (テスト対象の Unmarshal 関数の第2引数となる値) と want (期待されるエラーメッセージ) の2つのフィールドを持ちます。
    • v interface{}: Unmarshal 関数の引数 v の型に合わせて interface{} と宣言されています。これにより、nil、構造体、nil ポインタなど、様々な型の値をテストできます。
    • want string: Unmarshal 関数が返す errorError() メソッドが返す文字列と一致することを期待するエラーメッセージです。
    • 定義されている3つのテストケースは、Unmarshal 関数が InvalidUnmarshalError を返す典型的なシナリオをカバーしています。
  • TestInvalidUnmarshal 関数:

    • Goのテスト関数として標準的なシグネチャ func TestXxx(t *testing.T) を持ちます。
    • buf := []byte({"a":"1"}): これは、Unmarshal 関数に渡されるダミーのJSONバイト列です。このテストの目的は Unmarshal の引数 v の検証であり、JSONデータのパース自体ではないため、どのような有効なJSONデータでも構いません。
    • for _, tt := range invalidUnmarshalTests: invalidUnmarshalTests スライスをイテレートし、各テストケースを実行します。
    • err := Unmarshal(buf, tt.v): encoding/json パッケージの Unmarshal 関数を呼び出します。ここで、tt.v には nilstruct{}(*int)(nil) のいずれかが渡されます。
    • if err == nil: Unmarshal 関数がエラーを返さなかった場合、t.Errorf を使ってテストを失敗させます。これらのテストケースでは、Unmarshal は必ずエラーを返すことが期待されるためです。
    • if got := err.Error(); got != tt.want: Unmarshal が返したエラーの文字列表現 (err.Error()) が、テストケースで定義された期待値 (tt.want) と一致するかを検証します。一致しない場合、t.Errorf を使ってテストを失敗させ、実際のエラーメッセージと期待されるエラーメッセージを出力します。

このコードは、Unmarshal 関数のエラーハンドリングの正確性と一貫性を保証するための、シンプルかつ効果的な回帰テストとして機能します。

関連リンク

参考にした情報源リンク

  • Go言語の公式ドキュメント
  • Go言語のソースコード (特に encoding/json パッケージ)
  • Go言語のテストに関する一般的なプラクティス
  • InvalidUnmarshalError に関するGoコミュニティでの議論 (必要に応じてWeb検索で調査)