[インデックス 15227] ファイルの概要
このコミットは、Go言語の標準ライブラリ net
パッケージにおいて、Dial
および Listen
関数の引数に関するエラーハンドリングのテストを追加するものです。具体的には、無効なネットワークタイプやアドレスが指定された場合に、期待されるエラーが正しく返されることを検証するためのテストケースが導入されています。これにより、ネットワーク接続やリスニング処理の堅牢性が向上し、開発者がこれらの関数を誤用した際により明確なエラーメッセージを受け取れるようになります。
コミット
- コミットハッシュ:
ee9d148ce159581dfd0005dede6c56d1d4afeafe
- Author: Mikio Hara mikioh.mikioh@gmail.com
- Date: Thu Feb 14 07:02:32 2013 +0900
GitHub上でのコミットページへのリンク
https://github.com/golang/go/commit/ee9d148ce159581dfd0005dede6c56d1d4afeafe
元コミット内容
net: add test for Dial and Listen arguments
R=dave, bradfitz
CC=golang-dev
https://golang.org/cl/7305081
変更の背景
net.Dial
や net.Listen
といったネットワーク操作関数は、Goアプリケーションにおいて非常に頻繁に使用されます。これらの関数は、ネットワークタイプ(例: "tcp", "udp", "unix")とアドレス(例: "localhost:8080", "/tmp/sock")を引数として受け取りますが、これらの引数が無効な場合、どのようなエラーが返されるべきか、そしてそのエラーが開発者にとって理解しやすいものであるかは非常に重要です。
このコミットが作成された背景には、おそらく以下のような課題があったと考えられます。
- エラーハンドリングの不確実性: 無効な引数が渡された場合に、
Dial
やListen
が一貫した、予測可能なエラーを返すことを保証する必要がありました。これにより、アプリケーション開発者はエラーを適切に処理し、デバッグを容易にすることができます。 - 堅牢性の向上: ネットワーク関連の関数は、外部からの入力や環境に依存するため、予期せぬ入力に対する堅牢性が求められます。無効な引数に対する明確なエラーは、システムの安定性を高めます。
- テストカバレッジの拡充: 既存のテストでは、これらの特定の無効な引数ケースが十分にカバーされていなかった可能性があります。テストを追加することで、将来的なリファクタリングや変更があった際にも、既存の振る舞いが維持されることを保証できます。
この変更は、Goの net
パッケージの品質と信頼性を向上させるための、標準的なソフトウェア開発プラクティスの一環として行われました。
前提知識の解説
このコミットを理解するためには、以下のGo言語のネットワークプログラミングおよびテストに関する基本的な概念を理解しておく必要があります。
net.Dial(network, address string) (Conn, error)
:- 指定された
network
(例: "tcp", "udp", "unix") とaddress
(例: "localhost:8080", "google.com:80") を使用して、ネットワーク接続を確立します。 - 成功すると
net.Conn
インターフェースを実装する接続オブジェクトとnil
エラーを返します。 - 失敗すると
nil
接続とエラーを返します。
- 指定された
net.Listen(network, address string) (Listener, error)
:- 指定された
network
とaddress
を使用して、着信接続をリッスンするためのネットワークリスナーを確立します。 - 成功すると
net.Listener
インターフェースを実装するリスナーオブジェクトとnil
エラーを返します。 - 失敗すると
nil
リスナーとエラーを返します。
- 指定された
net.OpError
:- Goの
net
パッケージで発生する一般的なネットワーク操作エラーを表す構造体です。 Op
(操作の種類、例: "dial", "listen"),Net
(ネットワークタイプ),Addr
(アドレス),Err
(元のエラー) などのフィールドを持ち、エラーの詳細なコンテキストを提供します。Error()
メソッドを実装しており、error
インターフェースを満たします。
- Goの
net.UnknownNetworkError
:net
パッケージで定義されているエラータイプの一つで、指定されたネットワークタイプが認識できない場合に返されます。例えば、Dial("foo", "bar")
のように存在しないネットワークタイプ "foo" を指定した場合に発生します。
errMissingAddress
:net
パッケージ内部で定義されているエラーで、ネットワーク操作に必要なアドレスが提供されていない場合に発生します。例えば、Dial("tcp", "")
のようにアドレスが空文字列の場合に発生します。
reflect.DeepEqual(x, y interface{}) bool
:- Goの
reflect
パッケージに含まれる関数で、2つの値が「深く」等しいかどうかを再帰的に比較します。 - テストにおいて、構造体や配列、マップなどの複雑なデータ構造が期待される値と完全に一致するかどうかを検証する際によく使用されます。単なる
==
演算子では比較できない場合に役立ちます。
- Goの
- Goのテストフレームワーク (
testing
パッケージ):- Goには標準でテストフレームワークが組み込まれており、
_test.go
で終わるファイルにテストコードを記述します。 func TestXxx(*testing.T)
という形式の関数がテスト関数として認識されます。t.Fatalf(...)
は、テストが失敗した場合にメッセージを出力し、テストを即座に終了させるために使用されます。
- Goには標準でテストフレームワークが組み込まれており、
技術的詳細
このコミットは、src/pkg/net/dial_test.go
ファイルに TestInvalidDialAndListenArgs
という新しいテスト関数を追加することで、net.Dial
および net.Listen
関数が無効な引数を受け取った際の挙動を検証しています。
テストは、invalidDialAndListenArgTests
という構造体のスライスを定義することから始まります。このスライスは、各テストケースの入力 (net
, addr
) と、期待されるエラー (err
) を保持します。期待されるエラーは *OpError
型で、その Op
(操作), Net
(ネットワーク), Addr
(アドレス), Err
(内部エラー) フィールドが具体的に指定されています。
TestInvalidDialAndListenArgs
関数は、この invalidDialAndListenArgTests
スライスをイテレートし、各テストケースに対して以下の処理を行います。
- 操作の特定:
tt.err.(*OpError).Op
を見て、現在のテストケースがDial
操作をテストしているのか、Listen
操作をテストしているのかを判断します。 - 関数呼び出し: 特定された操作に応じて、
Dial(tt.net, tt.addr)
またはListen(tt.net, tt.addr)
を呼び出し、返されたエラーをerr
変数に格納します。 - エラーの比較:
reflect.DeepEqual(tt.err, err)
を使用して、実際に返されたエラーerr
が、テストケースで定義された期待されるエラーtt.err
と完全に一致するかどうかを比較します。 - テストの失敗: もしエラーが一致しない場合、
t.Fatalf
を呼び出してテストを失敗させ、実際の値と期待される値を詳細に出力します。
このテストの重要な点は、reflect.DeepEqual
を使用していることです。これは、OpError
のような複雑な構造体のフィールドがすべて期待通りに設定されていることを厳密に検証するために必要です。単にエラーメッセージの文字列を比較するだけでは、内部のエラータイプや他のコンテキスト情報が正しく設定されているかを保証できません。
追加されたテストケースは以下の3つです。
{"foo", "bar", &OpError{Op: "dial", Net: "foo", Addr: nil, Err: UnknownNetworkError("foo")}}
Dial
関数に存在しないネットワークタイプ "foo" を渡した場合のテスト。UnknownNetworkError
が返されることを期待します。
{"baz", "", &OpError{Op: "listen", Net: "baz", Addr: nil, Err: UnknownNetworkError("baz")}}
Listen
関数に存在しないネットワークタイプ "baz" を渡した場合のテスト。こちらもUnknownNetworkError
が返されることを期待します。
{"tcp", "", &OpError{Op: "dial", Net: "tcp", Addr: nil, Err: errMissingAddress}}
Dial
関数に有効なネットワークタイプ "tcp" を渡すものの、アドレスが空文字列の場合のテスト。errMissingAddress
が返されることを期待します。
これらのテストケースは、net
パッケージのネットワーク操作関数が、無効な入力に対して予測可能で、かつ情報量の多いエラーを返すことを保証します。
コアとなるコードの変更箇所
変更は src/pkg/net/dial_test.go
ファイルに集中しています。
--- a/src/pkg/net/dial_test.go
+++ b/src/pkg/net/dial_test.go
@@ -9,6 +9,7 @@ import (
"fmt"
"io"
"os"
+ "reflect"
"regexp"
"runtime"
"testing"
@@ -223,6 +224,31 @@ func TestDialError(t *testing.T) {
}
}
+var invalidDialAndListenArgTests = []struct {
+ net string
+ addr string
+ err error
+}{
+ {"foo", "bar", &OpError{Op: "dial", Net: "foo", Addr: nil, Err: UnknownNetworkError("foo")}},
+ {"baz", "", &OpError{Op: "listen", Net: "baz", Addr: nil, Err: UnknownNetworkError("baz")}},
+ {"tcp", "", &OpError{Op: "dial", Net: "tcp", Addr: nil, Err: errMissingAddress}},
+}
+
+func TestInvalidDialAndListenArgs(t *testing.T) {
+ for _, tt := range invalidDialAndListenArgTests {
+ var err error
+ switch tt.err.(*OpError).Op {
+ case "dial":
+ _, err = Dial(tt.net, tt.addr)
+ case "listen":
+ _, err = Listen(tt.net, tt.addr)
+ }
+ if !reflect.DeepEqual(tt.err, err) {
+ t.Fatalf("got %#v; expected %#v", err, tt.err)
+ }
+ }
+}
+
func TestDialTimeoutFDLeak(t *testing.T) {
if runtime.GOOS != "linux" {
// TODO(bradfitz): test on other platforms
コアとなるコードの解説
追加されたコードは主に以下の2つの部分から構成されます。
-
invalidDialAndListenArgTests
スライス:var invalidDialAndListenArgTests = []struct { net string addr string err error }{ {"foo", "bar", &OpError{Op: "dial", Net: "foo", Addr: nil, Err: UnknownNetworkError("foo")}}, {"baz", "", &OpError{Op: "listen", Net: "baz", Addr: nil, Err: UnknownNetworkError("baz")}}, {"tcp", "", &OpError{Op: "dial", Net: "tcp", Addr: nil, Err: errMissingAddress}}, }
これは、テストケースを定義するための匿名構造体のスライスです。各要素は、テスト対象のネットワークタイプ (
net
)、アドレス (addr
)、そして期待されるエラー (err
) を保持します。- 最初のケース:
Dial("foo", "bar")
をテストし、UnknownNetworkError("foo")
を含むOpError
が返されることを期待します。 - 2番目のケース:
Listen("baz", "")
をテストし、UnknownNetworkError("baz")
を含むOpError
が返されることを期待します。 - 3番目のケース:
Dial("tcp", "")
をテストし、errMissingAddress
を含むOpError
が返されることを期待します。
- 最初のケース:
-
TestInvalidDialAndListenArgs
関数:func TestInvalidDialAndListenArgs(t *testing.T) { for _, tt := range invalidDialAndListenArgTests { var err error switch tt.err.(*OpError).Op { case "dial": _, err = Dial(tt.net, tt.addr) case "listen": _, err = Listen(tt.net, tt.addr) } if !reflect.DeepEqual(tt.err, err) { t.Fatalf("got %#v; expected %#v", err, tt.err) } } }
この関数は、
invalidDialAndListenArgTests
スライス内の各テストケースをループ処理します。switch tt.err.(*OpError).Op
文は、テストケースがDial
とListen
のどちらを対象としているかを判断します。これは、期待されるエラーのOp
フィールドから情報を取得しています。- それぞれのケースで、対応する
Dial
またはListen
関数が呼び出され、返されたエラーがerr
変数に格納されます。 if !reflect.DeepEqual(tt.err, err)
は、実際に返されたエラーerr
が、テストケースで定義された期待されるエラーtt.err
と完全に一致するかどうかを検証します。reflect.DeepEqual
を使用することで、OpError
構造体のすべてのフィールド(Op
,Net
,Addr
,Err
)が正しく設定されていることを確認できます。- もし一致しない場合、
t.Fatalf
が呼び出され、テストが失敗したことを報告し、デバッグに役立つ詳細なエラー情報(実際の値と期待される値)を出力します。
このコードは、Goのテストにおけるテーブル駆動テスト(table-driven tests)の典型的な例であり、複数の類似したテストケースを簡潔かつ効率的に記述するための一般的なパターンです。
関連リンク
- Go CL 7305081: https://golang.org/cl/7305081
参考にした情報源リンク
- Go言語の
net
パッケージ公式ドキュメント: https://pkg.go.dev/net - Go言語の
testing
パッケージ公式ドキュメント: https://pkg.go.dev/testing - Go言語の
reflect
パッケージ公式ドキュメント: https://pkg.go.dev/reflect - Goのテーブル駆動テストに関する一般的な情報 (例: Go by Example - Table Driven Tests): https://gobyexample.com/table-driven-tests (一般的な概念の理解のため)
- Goのネットワークプログラミングに関する一般的な情報 (例: Go言語によるWebアプリケーション開発 - ネットワーク): (一般的な概念の理解のため、特定のURLは割愛)