[インデックス 17549] ファイルの概要
このコミットでは、Go言語の標準ライブラリbytes
パッケージ内のテストカバレッジを向上させるための変更が行われました。具体的には、src/pkg/bytes/bytes_test.go
とsrc/pkg/bytes/reader_test.go
の2つのテストファイルが修正され、合計85行の追加が行われています。
コミット
commit 3ee0744c06503eece696a615b1f8b37b4a0ed8a8
Author: Dave Cheney <dave@cheney.net>
Date: Wed Sep 11 21:20:15 2013 +1000
bytes: additional test coverage
Add coverage for some uncovered bytes methods. The increase in actual coverage is disapointing small.
R=golang-dev, bradfitz
CC=golang-dev
https://golang.org/cl/13651044
GitHub上でのコミットページへのリンク
https://github.com/golang/go/commit/3ee0744c06503eece696a615b1f8b37b4a0ed8a8
元コミット内容
bytes: additional test coverage
Add coverage for some uncovered bytes methods. The increase in actual coverage is disapointing small.
変更の背景
このコミットの主な目的は、Go言語のbytes
パッケージにおけるテストカバレッジを向上させることです。コミットメッセージにもあるように、既存のテストではカバーされていなかったbytes
パッケージ内のいくつかのメソッドに対して、新たなテストケースが追加されました。これにより、これらのメソッドが意図した通りに動作することを確認し、将来的なリファクタリングや変更に対する安全性を高めることが期待されます。ただし、コミットメッセージには「実際のカバレッジの増加は期待外れに小さい」とあり、これは既存のコードベースが既に高いカバレッジを持っていたか、あるいは追加されたテストがカバーする範囲が限定的であったことを示唆しています。
前提知識の解説
このコミットを理解するためには、以下のGo言語の概念とbytes
パッケージの基本的な知識が必要です。
bytes
パッケージ: Go言語の標準ライブラリの一部で、バイトスライス([]byte
)を操作するためのユーティリティ関数や型を提供します。文字列操作に似た機能(検索、置換、比較など)や、バイトスライスを効率的に扱うためのBuffer
型やReader
型などが含まれます。bytes.Buffer
: 可変長のバイトバッファを実装する型です。バイトスライスを効率的に追加したり、読み出したりする際に使用されます。Write
、Read
、Grow
、Truncate
などのメソッドを持ちます。Grow(n int)
: バッファの容量を少なくともn
バイト増やすことを保証します。負の値を指定するとパニック(実行時エラー)を引き起こす可能性があります。Truncate(n int)
: バッファの長さをn
バイトに切り詰めます。n
がバッファの現在の長さより大きい場合や負の値の場合、パニックを引き起こす可能性があります。
bytes.Reader
: バイトスライスをio.Reader
、io.Seeker
、io.ByteReader
、io.RuneReader
インターフェースとして扱うための型です。バイトスライスからデータを読み出す際に便利です。Len() int
: 読み出し可能な残りのバイト数を返します。ReadRune() (r rune, size int, err error)
: 次のUTF-8エンコードされたUnicodeコードポイント(ルーン)を読み出し、そのルーンとバイトサイズを返します。UnreadByte() error
: 最後に読み出されたバイトを読み出しストリームに戻します。連続して複数回呼び出すとエラーになることがあります。
- テストカバレッジ: ソフトウェアテストにおいて、テストケースが実行された際に、ソースコードのどの部分が実行されたかを示す指標です。高いテストカバレッジは、コードが十分にテストされていることを示唆しますが、それ自体がバグがないことを保証するものではありません。
- パニック (Panic): Go言語における回復不可能な実行時エラーの一種です。通常、プログラムの異常な状態(例: 配列の範囲外アクセス、nilポインタ参照)を示すために使用され、パニックが発生すると、通常のプログラムフローは停止し、defer関数が実行された後、プログラムが終了します。
技術的詳細
このコミットでは、bytes
パッケージのいくつかの機能に対するテストケースが追加されています。
src/pkg/bytes/bytes_test.go
への変更点:
-
indexTests
の追加:{"foo", "baz", -1}
という新しいテストケースがindexTests
スライスに追加されました。これは、bytes.Index
(または類似の検索関数)が、検索対象のバイトスライス("foo"
)内に指定されたサブスライス("baz"
)が見つからない場合に-1
を返すことを確認するためのものです。これにより、存在しないサブスライスの検索に対する関数の挙動が明示的にテストされます。
-
ToTitle
関数のテスト:ToTitleTests
という新しいTitleTest
型のスライスが定義され、bytes.ToTitle
関数のための様々な入力と期待される出力のペアが含まれています。これには、空文字列、単一の文字、スペースを含む文字列、数字を含む文字列、ハイフンで連結された文字列、そしてUTF-8エンコードされた非ASCII文字(ÿøû
)が含まれます。TestToTitle
という新しいテスト関数が追加され、ToTitleTests
の各ケースを反復処理し、bytes.ToTitle
の実際の出力が期待される出力と一致するかどうかを検証します。これにより、bytes.ToTitle
が様々な種類の入力に対して正しく動作することが保証されます。
-
Buffer
のパニックテスト:TestBufferGrowNegative
:bytes.Buffer
のGrow
メソッドに負の値を渡した場合にパニックが発生することを確認するテストです。defer
とrecover
を使用してパニックを捕捉し、パニックが発生しなかった場合にテストを失敗させます。これは、不正な入力に対する関数の堅牢性を保証します。TestBufferTruncateNegative
:bytes.Buffer
のTruncate
メソッドに負の値を渡した場合にパニックが発生することを確認するテストです。GrowNegative
と同様にdefer
とrecover
を使用します。TestBufferTruncateOutOfRange
:bytes.Buffer
のTruncate
メソッドに、バッファの現在の長さよりも大きい値を渡した場合にパニックが発生することを確認するテストです。これもdefer
とrecover
を使用してパニックを捕捉します。これらのテストは、Buffer
のメソッドが不正な引数に対して適切にパニックを引き起こすことを保証し、開発者がこれらのメソッドを誤用した場合に早期に問題を検出できるようにします。
src/pkg/bytes/reader_test.go
への変更点:
-
TestReaderLen
:bytes.Reader
のLen()
メソッドの動作をテストします。初期状態でのLen()
の値、一部を読み出した後のLen()
の値、そして全てを読み出した後のLen()
の値を検証します。これにより、Len()
メソッドが読み出しの進行状況に応じて正しく残りのバイト数を報告することを確認します。
-
TestReaderDoubleUnreadRune
:bytes.Reader
(実際にはbytes.Buffer
をReader
として使用)のUnreadByte()
メソッドの特定の挙動をテストします。ReadRune()
でルーンを読み出した後、UnreadByte()
を一度呼び出し、さらに続けてUnreadByte()
を呼び出した場合にエラーが発生することを確認します。これは、UnreadByte()
が通常、最後に読み出された1バイトのみを戻すことを意図しており、それ以上の操作は不正であることを示します。
これらの追加されたテストは、bytes
パッケージの様々なエッジケースやエラー条件に対するカバレッジを向上させ、ライブラリの信頼性と堅牢性を高めることに貢献しています。
コアとなるコードの変更箇所
--- a/src/pkg/bytes/bytes_test.go
+++ b/src/pkg/bytes/bytes_test.go
@@ -143,6 +143,7 @@ var indexTests = []BinOpTest{\n {\"\", \"a\", -1},\n {\"\", \"foo\", -1},\n {\"fo\", \"foo\", -1},\n+\t{\"foo\", \"baz\", -1},\n \t{\"foo\", \"foo\", 0},\n \t{\"oofofoofooo\", \"f\", 2},\n \t{\"oofofoofooo\", \"foo\", 4},\
@@ -1082,6 +1083,24 @@ func TestTitle(t *testing.T) {\n }\n }\n \n+var ToTitleTests = []TitleTest{\n+\t{\"\", \"\"},\n+\t{\"a\", \"A\"},\n+\t{\" aaa aaa aaa \", \" AAA AAA AAA \"},\n+\t{\" Aaa Aaa Aaa \", \" AAA AAA AAA \"},\n+\t{\"123a456\", \"123A456\"},\n+\t{\"double-blind\", \"DOUBLE-BLIND\"},\n+\t{\"ÿøû\", \"ŸØÛ\"},\n+}\n+\n+func TestToTitle(t *testing.T) {\n+\tfor _, tt := range ToTitleTests {\n+\t\tif s := string(ToTitle([]byte(tt.in))); s != tt.out {\n+\t\t\tt.Errorf(\"ToTitle(%q) = %q, want %q\", tt.in, s, tt.out)\n+\t\t}\n+\t}\n+}\n+\n var EqualFoldTests = []struct {\n \ts, t string\n \tout bool\n@@ -1110,6 +1129,37 @@ func TestEqualFold(t *testing.T) {\n \t}\n }\n \n+func TestBufferGrowNegative(t *testing.T) {\n+\tdefer func() {\n+\t\tif err := recover(); err == nil {\n+\t\t\tt.Fatal(\"Grow(-1) should have paniced\")\n+\t\t}\n+\t}()\n+\tvar b Buffer\n+\tb.Grow(-1)\n+}\n+\n+func TestBufferTruncateNegative(t *testing.T) {\n+\tdefer func() {\n+\t\tif err := recover(); err == nil {\n+\t\t\tt.Fatal(\"Truncate(-1) should have paniced\")\n+\t\t}\n+\t}()\n+\tvar b Buffer\n+\tb.Truncate(-1)\n+}\n+\n+func TestBufferTruncateOutOfRange(t *testing.T) {\n+\tdefer func() {\n+\t\tif err := recover(); err == nil {\n+\t\t\tt.Fatal(\"Truncate(20) should have paniced\")\n+\t\t}\n+\t}()\n+\tvar b Buffer\n+\tb.Write(make([]byte, 10))\n+\tb.Truncate(20)\n+}\n+\n var makeFieldsInput = func() []byte {\n \tx := make([]byte, 1<<20)\n \t// Input is ~10% space, ~10% 2-byte UTF-8, rest ASCII non-space.\
--- a/src/pkg/bytes/reader_test.go
+++ b/src/pkg/bytes/reader_test.go
@@ -113,6 +113,41 @@ func TestReaderWriteTo(t *testing.T) {\n \t}\n }\n \n+func TestReaderLen(t *testing.T) {\n+\tconst data = \"hello world\"\n+\tr := NewReader([]byte(data))\n+\tif got, want := r.Len(), 11; got != want {\n+\t\tt.Errorf(\"r.Len(): got %d, want %d\", got, want)\n+\t}\n+\tif n, err := r.Read(make([]byte, 10)); err != nil || n != 10 {\n+\t\tt.Errorf(\"Read failed: read %d %v\", n, err)\n+\t}\n+\tif got, want := r.Len(), 1; got != want {\n+\t\tt.Errorf(\"r.Len(): got %d, want %d\", got, want)\n+\t}\n+\tif n, err := r.Read(make([]byte, 1)); err != nil || n != 1 {\n+\t\tt.Errorf(\"Read failed: read %d %v\", n, err)\n+\t}\n+\tif got, want := r.Len(), 0; got != want {\n+\t\tt.Errorf(\"r.Len(): got %d, want %d\", got, want)\n+\t}\n+}\n+\n+func TestReaderDoubleUnreadRune(t *testing.T) {\n+\tbuf := NewBuffer([]byte(\"groucho\"))\n+\tif _, _, err := buf.ReadRune(); err != nil {\n+\t\t// should not happen\n+\t\tt.Fatal(err)\n+\t}\n+\tif err := buf.UnreadByte(); err != nil {\n+\t\t// should not happen\n+\t\tt.Fatal(err)\n+\t}\n+\tif err := buf.UnreadByte(); err == nil {\n+\t\t\tt.Fatal(\"UnreadByte: expected error, got nil\")\n+\t}\n+}\n+\n // verify that copying from an empty reader always has the same results,\n // regardless of the presence of a WriteTo method.\n func TestReaderCopyNothing(t *testing.T) {\
コアとなるコードの解説
上記のコード差分は、bytes
パッケージのテストファイルに追加された具体的なテストケースを示しています。
src/pkg/bytes/bytes_test.go
:
-
indexTests
の追加行:+ {"foo", "baz", -1},
これは、
bytes.Index
のような関数が、検索対象の文字列("foo"
)に部分文字列("baz"
)が含まれていない場合に-1
を返すことを確認するテストケースです。これにより、検索失敗時の挙動が正しく処理されることを保証します。 -
ToTitleTests
とTestToTitle
の追加:+var ToTitleTests = []TitleTest{ + {"", ""}, + {"a", "A"}, + {" aaa aaa aaa ", " AAA AAA AAA "}, + {" Aaa Aaa Aaa ", " AAA AAA AAA "}, + {"123a456", "123A456"}, + {"double-blind", "DOUBLE-BLIND"}, + {"ÿøû", "ŸØÛ"}, +} + +func TestToTitle(t *testing.T) { + for _, tt := range ToTitleTests { + if s := string(ToTitle([]byte(tt.in))); s != tt.out { + t.Errorf("ToTitle(%q) = %q, want %q", tt.in, s, tt.out) + } + } +}
bytes.ToTitle
関数は、バイトスライス内の各Unicode文字をタイトルケースに変換します。これらのテストケースは、空文字列、単一文字、スペースを含む文字列、数字を含む文字列、ハイフンで連結された文字列、そして多バイト文字(UTF-8)を含む文字列など、様々な入力に対するToTitle
の正確な変換を検証します。 -
Buffer
のパニックテスト関数群:+func TestBufferGrowNegative(t *testing.T) { + defer func() { + if err := recover(); err == nil { + t.Fatal("Grow(-1) should have paniced") + } + }() + var b Buffer + b.Grow(-1) +} // ... (TestBufferTruncateNegative, TestBufferTruncateOutOfRangeも同様の構造)
これらのテストは、
bytes.Buffer
のGrow
およびTruncate
メソッドに不正な引数(負の値や範囲外の値)を渡した場合に、Goのランタイムがパニックを発生させることを確認します。defer
とrecover
のメカニズムを利用してパニックを捕捉し、期待通りにパニックが発生したかどうかを検証しています。これにより、これらのメソッドの契約違反に対する堅牢性が保証されます。
src/pkg/bytes/reader_test.go
:
-
TestReaderLen
の追加:+func TestReaderLen(t *testing.T) { + const data = "hello world" + r := NewReader([]byte(data)) + if got, want := r.Len(), 11; got != want { + t.Errorf("r.Len(): got %d, want %d", got, want) + } + if n, err := r.Read(make([]byte, 10)); err != nil || n != 10 { + t.Errorf("Read failed: read %d %v", n, err) + } + if got, want := r.Len(), 1; got != want { + t.Errorf("r.Len(): got %d, want %d", got, want) + } + if n, err := r.Read(make([]byte, 1)); err != nil || n != 1 { + t.Errorf("Read failed: read %d %v", n, err) + } + if got, want := r.Len(), 0; got != want { + t.Errorf("r.Len(): got %d, want %d", got, want) + } +}
このテストは、
bytes.Reader
のLen()
メソッドが、読み出し操作の進行に伴って残りのバイト数を正確に返すことを検証します。初期状態、部分的な読み出し後、そして全ての読み出し後のLen()
の値をチェックしています。 -
TestReaderDoubleUnreadRune
の追加:+func TestReaderDoubleUnreadRune(t *testing.T) { + buf := NewBuffer([]byte("groucho")) + if _, _, err := buf.ReadRune(); err != nil { + // should not happen + t.Fatal(err) + } + if err := buf.UnreadByte(); err != nil { + // should not happen + t.Fatal(err) + } + if err := buf.UnreadByte(); err == nil { + t.Fatal("UnreadByte: expected error, got nil") + } +}
このテストは、
bytes.Reader
(ここではbytes.Buffer
をReader
として利用)に対してReadRune
の後にUnreadByte
を2回連続で呼び出した場合の挙動を検証します。通常、UnreadByte
は直前の1バイトのみを戻すため、2回目の呼び出しはエラーとなるべきです。このテストは、そのエラーが正しく発生することを確認し、UnreadByte
のセマンティクスを明確にします。
これらのテストの追加により、bytes
パッケージの堅牢性と信頼性が向上し、開発者がこれらの機能を使用する際により安全な基盤が提供されます。
関連リンク
- Go CL 13651044: https://golang.org/cl/13651044
参考にした情報源リンク
- Go言語の公式ドキュメント:
bytes
パッケージ (https://pkg.go.dev/bytes) - Go言語のテストに関するドキュメント (https://go.dev/blog/testing)
- Go言語の
panic
とrecover
に関するドキュメント (https://go.dev/blog/defer-panic-recover)