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

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

このコミットは、Go言語の標準ライブラリ encoding/hex パッケージにおけるエラー型の正規化と、関連するテストの簡素化を目的としています。具体的には、既存のカスタムエラー型を、よりGo言語の慣習に沿った形式に統一し、エラーハンドリングの一貫性を向上させています。また、テストコードのリファクタリングにより、可読性と保守性を高めています。

コミット

commit 92f55949f9e747477937e66df3cb486b1912e97f
Author: Rob Pike <r@golang.org>
Date:   Wed Feb 8 11:53:32 2012 +1100

    encoding/hex: canonicalize error type names
    Also simplify the tests.
    
    Fixes #2849.
    
    R=golang-dev, bradfitz, r
    CC=golang-dev
    https://golang.org/cl/5643045

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

https://github.com/golang/go/commit/92f55949f9e747477937e66df3cb486b1912e97f

元コミット内容

encoding/hex: canonicalize error type names
Also simplify the tests.

Fixes #2849.

R=golang-dev, bradfitz, r
CC=golang-dev
https://golang.org/cl/5643045

変更の背景

このコミットの主な背景には、Go言語におけるエラーハンドリングのベストプラクティスへの準拠と、コードベース全体の一貫性の向上が挙げられます。初期のGo言語では、エラーの表現方法に様々なパターンが存在しましたが、時間とともに errors.New やカスタムエラー型を定義する際の慣習が確立されていきました。

以前の encoding/hex パッケージでは、OddLengthInputErrorInvalidHexCharError といった構造体ベースのカスタムエラー型が定義されていました。これらは Error() メソッドを実装することで error インターフェースを満たしていましたが、Go言語の標準ライブラリやコミュニティで推奨されるエラー表現とは若干異なっていました。特に、特定の固定エラーを表す場合には、errors.New で作成された変数を使用する方が一般的です。また、エラーの詳細情報(例:無効なバイト値)を伝える場合には、fmt.Errorf や、エラーインターフェースを実装するシンプルな型が好まれます。

このコミットは、これらのエラー型をGo言語の慣習に沿った形に「正規化 (canonicalize)」することで、パッケージのAPIをより直感的で使いやすくし、他のGoコードとの連携をスムーズにすることを目指しています。同時に、エラー型の変更に伴い、テストコードも簡素化され、より効率的で読みやすい形にリファクタリングされています。

前提知識の解説

Go言語のエラーハンドリング

Go言語では、エラーは組み込みの error インターフェースによって表現されます。このインターフェースは、Error() string という単一のメソッドを持ち、エラーメッセージを文字列として返します。

type error interface {
    Error() string
}

エラーを返す関数は、通常、最後の戻り値として error 型を返します。慣習として、エラーがない場合は nil を返します。

func SomeFunction() (resultType, error) {
    // ... 処理 ...
    if someErrorCondition {
        return zeroValue, errors.New("something went wrong")
    }
    return actualResult, nil
}

errors.New と固定エラー

errors.New 関数は、単純な文字列から新しいエラーを作成するために使用されます。これは、特定の固定されたエラー条件を表す場合によく使われます。

var ErrSomethingBad = errors.New("something bad happened")

func MyFunction() error {
    if condition {
        return ErrSomethingBad // 事前に定義されたエラー変数を返す
    }
    return nil
}

カスタムエラー型とエラーの詳細

エラーに付加的な情報を含めたい場合(例:エラーが発生した具体的な値や行番号など)は、カスタム構造体を定義し、それに Error() メソッドを実装することで error インターフェースを満たします。

type MyCustomError struct {
    Code int
    Message string
}

func (e *MyCustomError) Error() string {
    return fmt.Sprintf("Error %d: %s", e.Code, e.Message)
}

func AnotherFunction() error {
    return &MyCustomError{Code: 100, Message: "Invalid input"}
}

Go 1.13以降では、errors.Iserrors.As を用いてエラーの比較やアンラップが可能になり、より柔軟なエラーハンドリングが可能になっています。

16進数エンコーディング (Hex Encoding)

16進数エンコーディング(Hexadecimal Encoding)は、バイナリデータを可読な文字列形式に変換する一般的な方法です。各バイト(8ビット)を2つの16進数文字(0-9, a-f または A-F)で表現します。例えば、1バイトの 0xFF は16進数で FF となります。

encoding/hex パッケージは、この16進数エンコーディングとデコーディングの機能を提供します。

  • エンコード (Encode): バイナリデータ([]byte)を16進数文字列([]byte または string)に変換します。
  • デコード (Decode): 16進数文字列([]byte または string)をバイナリデータ([]byte)に変換します。

デコード時には、入力文字列が偶数長であること(各バイトが2つの16進数文字で表現されるため)と、含まれる文字が有効な16進数文字(0-9, a-f, A-F)であることが求められます。これらの条件が満たされない場合、エラーが発生します。

技術的詳細

このコミットは、encoding/hex パッケージ内のエラー処理ロジックとテスト構造に焦点を当てています。

エラー型の正規化

以前のバージョンでは、encoding/hex パッケージは以下の2つのカスタムエラー型を使用していました。

  1. OddLengthInputError: デコード対象の16進数文字列が奇数長である場合に発生。
    type OddLengthInputError struct{}
    func (OddLengthInputError) Error() string { return "odd length hex string" }
    
  2. InvalidHexCharError: 16進数文字列内に無効な文字が含まれている場合に発生。
    type InvalidHexCharError byte
    func (e InvalidHexCharError) Error() string { return "invalid hex char: " + strconv.Itoa(int(e)) }
    

このコミットでは、これらのエラー型が以下のように変更されました。

  1. OddLengthInputError は、errors.New を使用した固定エラー変数 ErrLength に置き換えられました。

    var ErrLength = errors.New("encoding/hex: odd length hex string")
    

    これにより、奇数長エラーは、他のGo標準ライブラリでよく見られる固定エラー変数として表現されるようになり、エラーの比較が errors.Is(err, hex.ErrLength) のように直接行えるようになりました。

  2. InvalidHexCharError は、InvalidByteError という新しい型に置き換えられました。この型は byte を基底型とし、Error() メソッド内で fmt.Sprintf を使用して、無効なバイト値をより詳細に、かつ標準的なフォーマットで出力するように変更されました。

    type InvalidByteError byte
    func (e InvalidByteError) Error() string {
        return fmt.Sprintf("encoding/hex: invalid byte: %#U", rune(e))
    }
    

    %#U フォーマット動詞は、Unicodeコードポイントとその文字名を U+XXXX 'C' の形式で出力します。これにより、エラーメッセージがより情報豊富になり、デバッグが容易になります。

これらの変更により、エラーメッセージのプレフィックスに encoding/hex: が追加され、どのパッケージからのエラーであるかが明確になりました。これはGo標準ライブラリのエラーメッセージの一般的な慣習です。

テストの簡素化

エラー型の変更に伴い、hex_test.go のテストコードも大幅に簡素化されました。

  • テストケース構造の統合: 以前は encodeTestdecodeTest という別々の構造体でエンコードとデコードのテストケースを管理していましたが、これらが encDecTest という単一の構造体に統合されました。

    type encDecTest struct {
        enc string // 16進数エンコードされた文字列
        dec []byte // デコードされたバイトスライス
    }
    

    これにより、エンコードとデコードの両方を同じデータセットでテストできるようになり、テストコードの重複が削減され、保守性が向上しました。

  • エラーテストの分離と改善: 以前の decodeTest には ok フィールドがあり、エラーが発生するかどうかをフラグで管理していました。このコミットでは、エラーテストが errTest という専用の構造体と TestInvalidErr, TestInvalidStringErr という関数に分離されました。

    type errTest struct {
        in  string // 入力文字列
        err string // 期待されるエラーメッセージ
    }
    

    これにより、エラーケースのテストがより明確になり、期待されるエラーメッセージとの厳密な比較が可能になりました。

  • bytes.Compare から bytes.Equal への変更: テスト内のバイトスライスの比較に bytes.Compare が使用されていましたが、これは bytes.Equal に変更されました。bytes.Equal は2つのバイトスライスが等しいかどうかをブール値で返すため、より直接的で意図が明確な比較方法です。

これらのテストの変更は、コードの重複を減らし、テストケースの定義をより簡潔にし、エラーテストの網羅性と正確性を向上させています。

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

src/pkg/encoding/hex/hex.go

--- a/src/pkg/encoding/hex/hex.go
+++ b/src/pkg/encoding/hex/hex.go
@@ -7,8 +7,9 @@ package hex
 
 import (
 	"bytes"
+	"errors"
+	"fmt"
 	"io"
-	"strconv"
 )
 
 const hextable = "0123456789abcdef"
@@ -29,16 +30,14 @@ func Encode(dst, src []byte) int {
 	return len(src) * 2
 }
 
-// OddLengthInputError results from decoding an odd length slice.
-type OddLengthInputError struct{}
+// ErrLength results from decoding an odd length slice.
+var ErrLength = errors.New("encoding/hex: odd length hex string")
 
-func (OddLengthInputError) Error() string { return "odd length hex string" }
+// InvalidByteError values describe errors resulting from an invalid byte in a hex string.
+type InvalidByteError byte
 
-// InvalidHexCharError results from finding an invalid character in a hex string.
-type InvalidHexCharError byte
-
-func (e InvalidHexCharError) Error() string {\n-\treturn "invalid hex char: " + strconv.Itoa(int(e))\n+\treturn fmt.Sprintf("encoding/hex: invalid byte: %#U", rune(e))\n }
 
 func DecodedLen(x int) int { return x / 2 }
 
@@ -46,21 +45,20 @@ func DecodedLen(x int) int { return x / 2 }
 // Decode decodes src into DecodedLen(len(src)) bytes, returning the actual
 // number of bytes written to dst.
 //
-// If Decode encounters invalid input, it returns an OddLengthInputError or an
-// InvalidHexCharError.
+// If Decode encounters invalid input, it returns an error describing the failure.
 func Decode(dst, src []byte) (int, error) {
 	if len(src)%2 == 1 {
-\t\treturn 0, OddLengthInputError{}\n+\t\treturn 0, ErrLength
 	}
 
 	for i := 0; i < len(src)/2; i++ {
 		a, ok := fromHexChar(src[i*2])
 		if !ok {
-\t\t\treturn 0, InvalidHexCharError(src[i*2])
+\t\t\treturn 0, InvalidByteError(src[i*2])
 		}
 		b, ok := fromHexChar(src[i*2+1])
 		if !ok {
-\t\t\treturn 0, InvalidHexCharError(src[i*2+1])
+\t\t\treturn 0, InvalidByteError(src[i*2+1])
 		}\n \t\tdst[i] = (a << 4) | b
 \t}\n```

### `src/pkg/encoding/hex/hex_test.go`

```diff
--- a/src/pkg/encoding/hex/hex_test.go
+++ b/src/pkg/encoding/hex/hex_test.go
@@ -9,141 +9,98 @@ import (
 	"testing"
 )
 
-type encodeTest struct {
-\tin, out []byte
+type encDecTest struct {
+\tenc string
+\tdec []byte
 }
 
-var encodeTests = []encodeTest{
-\t{[]byte{}, []byte{}},\n-\t{[]byte{0x01}, []byte{'0', '1'}},\n-\t{[]byte{0xff}, []byte{'f', 'f'}},\n-\t{[]byte{0xff, 00}, []byte{'f', 'f', '0', '0'}},\n-\t{[]byte{0}, []byte{'0', '0'}},\n-\t{[]byte{1}, []byte{'0', '1'}},\n-\t{[]byte{2}, []byte{'0', '2'}},\n-\t{[]byte{3}, []byte{'0', '3'}},\n-\t{[]byte{4}, []byte{'0', '4'}},\n-\t{[]byte{5}, []byte{'0', '5'}},\n-\t{[]byte{6}, []byte{'0', '6'}},\n-\t{[]byte{7}, []byte{'0', '7'}},\n-\t{[]byte{8}, []byte{'0', '8'}},\n-\t{[]byte{9}, []byte{'0', '9'}},\n-\t{[]byte{10}, []byte{'0', 'a'}},\n-\t{[]byte{11}, []byte{'0', 'b'}},\n-\t{[]byte{12}, []byte{'0', 'c'}},\n-\t{[]byte{13}, []byte{'0', 'd'}},\n-\t{[]byte{14}, []byte{'0', 'e'}},\n-\t{[]byte{15}, []byte{'0', 'f'}},\n+var encDecTests = []encDecTest{
+\t{"", []byte{}},
+\t{"0001020304050607", []byte{0, 1, 2, 3, 4, 5, 6, 7}},
+\t{"08090a0b0c0d0e0f", []byte{8, 9, 10, 11, 12, 13, 14, 15}},
+\t{"f0f1f2f3f4f5f6f7", []byte{0xf0, 0xf1, 0xf2, 0xf3, 0xf4, 0xf5, 0xf6, 0xf7}},
+\t{"f8f9fafbfcfdfeff", []byte{0xf8, 0xf9, 0xfa, 0xfb, 0xfc, 0xfd, 0xfe, 0xff}},
+\t{"67", []byte{'g'}},
+\t{"e3a1", []byte{0xe3, 0xa1}},
 }
 
 func TestEncode(t *testing.T) {
-\tfor i, test := range encodeTests {
-\t\tdst := make([]byte, EncodedLen(len(test.in)))\n-\t\tn := Encode(dst, test.in)\n+\tfor i, test := range encDecTests {
+\t\tdst := make([]byte, EncodedLen(len(test.dec)))\n+\t\tn := Encode(dst, test.dec)\n \t\tif n != len(dst) {
 \t\t\tt.Errorf("#%d: bad return value: got: %d want: %d", i, n, len(dst))
 \t\t}
-\t\tif bytes.Compare(dst, test.out) != 0 {
-\t\t\tt.Errorf("#%d: got: %#v want: %#v", i, dst, test.out)
+\t\tif string(dst) != test.enc {
+\t\t\tt.Errorf("#%d: got: %#v want: %#v", i, dst, test.enc)
 \t\t}
 \t}
 }
 
-type decodeTest struct {
-\tin, out []byte
-\tok      bool
-}
-
-var decodeTests = []decodeTest{
-\t{[]byte{}, []byte{}, true},\n-\t{[]byte{'0'}, []byte{}, false},\n-\t{[]byte{'0', 'g'}, []byte{}, false},\n-\t{[]byte{'0', '\x01'}, []byte{}, false},\n-\t{[]byte{'0', '0'}, []byte{0}, true},\n-\t{[]byte{'0', '1'}, []byte{1}, true},\n-\t{[]byte{'0', '2'}, []byte{2}, true},\n-\t{[]byte{'0', '3'}, []byte{3}, true},\n-\t{[]byte{'0', '4'}, []byte{4}, true},\n-\t{[]byte{'0', '5'}, []byte{5}, true},\n-\t{[]byte{'0', '6'}, []byte{6}, true},\n-\t{[]byte{'0', '7'}, []byte{7}, true},\n-\t{[]byte{'0', '8'}, []byte{8}, true},\n-\t{[]byte{'0', '9'}, []byte{9}, true},\n-\t{[]byte{'0', 'a'}, []byte{10}, true},\n-\t{[]byte{'0', 'b'}, []byte{11}, true},\n-\t{[]byte{'0', 'c'}, []byte{12}, true},\n-\t{[]byte{'0', 'd'}, []byte{13}, true},\n-\t{[]byte{'0', 'e'}, []byte{14}, true},\n-\t{[]byte{'0', 'f'}, []byte{15}, true},\n-\t{[]byte{'0', 'A'}, []byte{10}, true},\n-\t{[]byte{'0', 'B'}, []byte{11}, true},\n-\t{[]byte{'0', 'C'}, []byte{12}, true},\n-\t{[]byte{'0', 'D'}, []byte{13}, true},\n-\t{[]byte{'0', 'E'}, []byte{14}, true},\n-\t{[]byte{'0', 'F'}, []byte{15}, true},\n-}
-\n func TestDecode(t *testing.T) {
-\tfor i, test := range decodeTests {
-\t\tdst := make([]byte, DecodedLen(len(test.in)))\n-\t\tn, err := Decode(dst, test.in)\n-\t\tif err == nil && n != len(dst) {\n+\tfor i, test := range encDecTests {
+\t\tdst := make([]byte, DecodedLen(len(test.enc)))\n+\t\tn, err := Decode(dst, []byte(test.enc))\n+\t\tif err != nil {
 \t\t\tt.Errorf("#%d: bad return value: got:%d want:%d", i, n, len(dst))
-\t\t}\n-\t\tif test.ok != (err == nil) {
-\t\t\tt.Errorf("#%d: unexpected err value: %s", i, err)
-\t\t}\n-\t\tif err == nil && bytes.Compare(dst, test.out) != 0 {
-\t\t\tt.Errorf("#%d: got: %#v want: %#v", i, dst, test.out)
+\t\t} else if !bytes.Equal(dst, test.dec) {
+\t\t\tt.Errorf("#%d: got: %#v want: %#v", i, dst, test.dec)
 \t\t}
 \t}\n }
 
-type encodeStringTest struct {
-\tin  []byte
-\tout string
-}
-
-var encodeStringTests = []encodeStringTest{
-\t{[]byte{}, ""},\n-\t{[]byte{0}, "00"},\n-\t{[]byte{0, 1}, "0001"},\n-\t{[]byte{0, 1, 255}, "0001ff"},\n+func TestEncodeToString(t *testing.T) {
+\tfor i, test := range encDecTests {
+\t\ts := EncodeToString(test.dec)
+\t\tif s != test.enc {
+\t\t\tt.Errorf("#%d got:%s want:%s", i, s, test.enc)
+\t\t}
+\t}
 }
 
-func TestEncodeToString(t *testing.T) {
-\tfor i, test := range encodeStringTests {
-\t\ts := EncodeToString(test.in)\n-\t\tif s != test.out {
-\t\t\tt.Errorf("#%d got:%s want:%s", i, s, test.out)
+\n+func TestDecodeString(t *testing.T) {
+\tfor i, test := range encDecTests {
+\t\tdst, err := DecodeString(test.enc)
+\t\tif err != nil {
+\t\t\tt.Errorf("#%d: unexpected err value: %s", i, err)
+\t\t\tcontinue
+\t\t}
+\t\tif bytes.Compare(dst, test.dec) != 0 {
+\t\t\tt.Errorf("#%d: got: %#v want: #%v", i, dst, test.dec)
+\t\t}
+\t}
+}
+
+type errTest struct {
+\tin  string
+\terr string
+}
+
+var errTests = []errTest{
+\t{"0", "encoding/hex: odd length hex string"},
+\t{"0g", "encoding/hex: invalid byte: U+0067 'g'"},
+\t{"0\x01", "encoding/hex: invalid byte: U+0001"},
 }
 
-type decodeStringTest struct {
-\tin  string
-\tout []byte
-\tok  bool
-}
-
-var decodeStringTests = []decodeStringTest{
-\t{"", []byte{}, true},\n-\t{"0", []byte{}, false},\n-\t{"00", []byte{0}, true},\n-\t{"0\x01", []byte{}, false},\n-\t{"0g", []byte{}, false},\n-\t{"00ff00", []byte{0, 255, 0}, true},\n-\t{"0000ff", []byte{0, 0, 255}, true},\n+func TestInvalidErr(t *testing.T) {
+\tfor i, test := range errTests {
+\t\tdst := make([]byte, DecodedLen(len(test.in)))
+\t\t_, err := Decode(dst, []byte(test.in))
+\t\tif err == nil {
+\t\t\tt.Errorf("#%d: expected error; got none")
+\t\t} else if err.Error() != test.err {
+\t\t\tt.Errorf("#%d: got: %v want: %v", i, err, test.err)
+\t\t}
+\t}
+}
+
+func TestInvalidStringErr(t *testing.T) {
+\tfor i, test := range errTests {
+\t\t_, err := DecodeString(test.in)
+\t\tif err == nil {
+\t\t\tt.Errorf("#%d: expected error; got none")
+\t\t} else if err.Error() != test.err {
+\t\t\tt.Errorf("#%d: got: %v want: %v", i, err, test.err)
+\t\t}
+\t}
 }
-
-func TestDecodeString(t *testing.T) {
-\tfor i, test := range decodeStringTests {
-\t\tdst, err := DecodeString(test.in)\n-\t\tif test.ok != (err == nil) {
-\t\t\tt.Errorf("#%d: unexpected err value: %s", i, err)
-\t\t}\n-\t\tif err == nil && bytes.Compare(dst, test.out) != 0 {
-\t\t\tt.Errorf("#%d: got: %#v want: #%v", i, dst, test.out)
-\t\t}
-\t}
-}

コアとなるコードの解説

src/pkg/encoding/hex/hex.go の変更点

  1. インポートの追加と削除:

    • "errors""fmt" パッケージが新しくインポートされました。これは、errors.New で固定エラーを定義するためと、InvalidByteErrorError() メソッドでフォーマットされた文字列を生成するために必要です。
    • "strconv" パッケージが削除されました。これは、InvalidHexCharErrorError() メソッドで strconv.Itoa を使用していたためですが、新しい InvalidByteError では fmt.Sprintf を使用するため不要になりました。
  2. OddLengthInputError から ErrLength への変更:

    • OddLengthInputError 構造体の定義と、その Error() メソッドが削除されました。
    • 代わりに、var ErrLength = errors.New("encoding/hex: odd length hex string") という行が追加されました。これにより、奇数長の入力に対するエラーは、Goの慣習に沿った固定エラー変数 ErrLength として提供されます。
    • Decode 関数内で return 0, OddLengthInputError{} だった箇所が return 0, ErrLength に変更されました。
  3. InvalidHexCharError から InvalidByteError への変更:

    • InvalidHexCharError 構造体の定義と、その Error() メソッドが削除されました。
    • 代わりに、type InvalidByteError bytefunc (e InvalidByteError) Error() string { return fmt.Sprintf("encoding/hex: invalid byte: %#U", rune(e)) } が追加されました。
    • Decode 関数内で return 0, InvalidHexCharError(src[i*2])return 0, InvalidHexCharError(src[i*2+1]) だった箇所が return 0, InvalidByteError(src[i*2])return 0, InvalidByteError(src[i*2+1]) に変更されました。これにより、無効なバイト値がエラーメッセージに直接含まれるようになり、デバッグが容易になります。
  4. Decode 関数のドキュメンテーション更新:

    • Decode 関数のコメントが更新され、返されるエラーが OddLengthInputErrorInvalidHexCharError ではなく、「失敗を説明するエラー」を返すようになったことが明記されました。

これらの変更により、encoding/hex パッケージのエラーハンドリングは、Go言語の標準的な慣習により合致し、エラーの識別と処理がより一貫性のあるものになりました。

src/pkg/encoding/hex/hex_test.go の変更点

  1. テストケース構造の統合:

    • encodeTestdecodeTest の定義が削除され、encDecTest という新しい構造体が導入されました。この構造体は、エンコードされた文字列 (enc) とデコードされたバイトスライス (dec) の両方を保持します。
    • encodeTestsdecodeTests の変数定義が削除され、encDecTests という単一のテストデータスライスに統合されました。これにより、エンコードとデコードの両方のテストで同じデータセットを再利用できるようになりました。
  2. TestEncode 関数の変更:

    • encodeTests の代わりに encDecTests をループするように変更されました。
    • Encode 関数の入力として test.in (旧) ではなく test.dec (新) を使用するように変更されました。
    • 結果の比較が bytes.Compare(dst, test.out) から string(dst) != test.enc に変更されました。これは、エンコード結果が文字列として期待されるためです。
  3. TestDecode 関数の変更:

    • decodeTests の代わりに encDecTests をループするように変更されました。
    • Decode 関数の入力として test.in (旧) ではなく []byte(test.enc) (新) を使用するように変更されました。
    • エラーチェックロジックが簡素化されました。以前は test.ok フラグと err == nil の比較を行っていましたが、新しいエラー型とテスト構造では、エラーが発生しないことを期待するケースでは err != nil を直接チェックし、バイトスライスの比較には !bytes.Equal(dst, test.dec) を使用します。
  4. TestEncodeToString 関数の変更:

    • encodeStringTests の代わりに encDecTests をループするように変更されました。
    • EncodeToString 関数の入力として test.in (旧) ではなく test.dec (新) を使用するように変更されました。
    • 結果の比較が s != test.out から s != test.enc に変更されました。
  5. TestDecodeString 関数の変更:

    • decodeStringTests の代わりに encDecTests をループするように変更されました。
    • DecodeString 関数の入力として test.in (旧) ではなく test.enc (新) を使用するように変更されました。
    • エラーチェックロジックが簡素化されました。以前は test.ok フラグと err == nil の比較を行っていましたが、新しいエラー型とテスト構造では、エラーが発生しないことを期待するケースでは err != nil を直接チェックし、バイトスライスの比較には bytes.Compare(dst, test.dec) != 0 を使用します。
  6. エラーテストの分離:

    • errTest という新しい構造体が定義され、エラーが発生する入力文字列 (in) と期待されるエラーメッセージ (err) を保持します。
    • errTests という新しいテストデータスライスが定義され、エラーケースに特化したテストデータが含まれます。
    • TestInvalidErrTestInvalidStringErr という新しいテスト関数が追加されました。これらの関数は errTests をループし、Decode および DecodeString 関数が期待されるエラーを返すことを検証します。エラーメッセージの厳密な比較 (err.Error() != test.err) が行われます。

これらのテストの変更は、テストコードの重複を大幅に削減し、テストケースの管理を簡素化し、エラーテストの網羅性と正確性を向上させることに貢献しています。

関連リンク

参考にした情報源リンク

  • Go言語の公式ドキュメント
  • Go言語のエラーハンドリングに関する一般的なベストプラクティス
  • GitHubのgolang/goリポジトリのコミット履歴
  • Go言語のfmtパッケージのドキュメント (特に%#Uフォーマット動詞について)