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

[インデックス 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.Dialnet.Listen といったネットワーク操作関数は、Goアプリケーションにおいて非常に頻繁に使用されます。これらの関数は、ネットワークタイプ(例: "tcp", "udp", "unix")とアドレス(例: "localhost:8080", "/tmp/sock")を引数として受け取りますが、これらの引数が無効な場合、どのようなエラーが返されるべきか、そしてそのエラーが開発者にとって理解しやすいものであるかは非常に重要です。

このコミットが作成された背景には、おそらく以下のような課題があったと考えられます。

  1. エラーハンドリングの不確実性: 無効な引数が渡された場合に、DialListen が一貫した、予測可能なエラーを返すことを保証する必要がありました。これにより、アプリケーション開発者はエラーを適切に処理し、デバッグを容易にすることができます。
  2. 堅牢性の向上: ネットワーク関連の関数は、外部からの入力や環境に依存するため、予期せぬ入力に対する堅牢性が求められます。無効な引数に対する明確なエラーは、システムの安定性を高めます。
  3. テストカバレッジの拡充: 既存のテストでは、これらの特定の無効な引数ケースが十分にカバーされていなかった可能性があります。テストを追加することで、将来的なリファクタリングや変更があった際にも、既存の振る舞いが維持されることを保証できます。

この変更は、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):
    • 指定された networkaddress を使用して、着信接続をリッスンするためのネットワークリスナーを確立します。
    • 成功すると net.Listener インターフェースを実装するリスナーオブジェクトと nil エラーを返します。
    • 失敗すると nil リスナーとエラーを返します。
  • net.OpError:
    • Goの net パッケージで発生する一般的なネットワーク操作エラーを表す構造体です。
    • Op (操作の種類、例: "dial", "listen"), Net (ネットワークタイプ), Addr (アドレス), Err (元のエラー) などのフィールドを持ち、エラーの詳細なコンテキストを提供します。
    • Error() メソッドを実装しており、error インターフェースを満たします。
  • net.UnknownNetworkError:
    • net パッケージで定義されているエラータイプの一つで、指定されたネットワークタイプが認識できない場合に返されます。例えば、Dial("foo", "bar") のように存在しないネットワークタイプ "foo" を指定した場合に発生します。
  • errMissingAddress:
    • net パッケージ内部で定義されているエラーで、ネットワーク操作に必要なアドレスが提供されていない場合に発生します。例えば、Dial("tcp", "") のようにアドレスが空文字列の場合に発生します。
  • reflect.DeepEqual(x, y interface{}) bool:
    • Goの reflect パッケージに含まれる関数で、2つの値が「深く」等しいかどうかを再帰的に比較します。
    • テストにおいて、構造体や配列、マップなどの複雑なデータ構造が期待される値と完全に一致するかどうかを検証する際によく使用されます。単なる == 演算子では比較できない場合に役立ちます。
  • Goのテストフレームワーク (testing パッケージ):
    • Goには標準でテストフレームワークが組み込まれており、_test.go で終わるファイルにテストコードを記述します。
    • func TestXxx(*testing.T) という形式の関数がテスト関数として認識されます。
    • t.Fatalf(...) は、テストが失敗した場合にメッセージを出力し、テストを即座に終了させるために使用されます。

技術的詳細

このコミットは、src/pkg/net/dial_test.go ファイルに TestInvalidDialAndListenArgs という新しいテスト関数を追加することで、net.Dial および net.Listen 関数が無効な引数を受け取った際の挙動を検証しています。

テストは、invalidDialAndListenArgTests という構造体のスライスを定義することから始まります。このスライスは、各テストケースの入力 (net, addr) と、期待されるエラー (err) を保持します。期待されるエラーは *OpError 型で、その Op (操作), Net (ネットワーク), Addr (アドレス), Err (内部エラー) フィールドが具体的に指定されています。

TestInvalidDialAndListenArgs 関数は、この invalidDialAndListenArgTests スライスをイテレートし、各テストケースに対して以下の処理を行います。

  1. 操作の特定: tt.err.(*OpError).Op を見て、現在のテストケースが Dial 操作をテストしているのか、Listen 操作をテストしているのかを判断します。
  2. 関数呼び出し: 特定された操作に応じて、Dial(tt.net, tt.addr) または Listen(tt.net, tt.addr) を呼び出し、返されたエラーを err 変数に格納します。
  3. エラーの比較: reflect.DeepEqual(tt.err, err) を使用して、実際に返されたエラー err が、テストケースで定義された期待されるエラー tt.err と完全に一致するかどうかを比較します。
  4. テストの失敗: もしエラーが一致しない場合、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つの部分から構成されます。

  1. 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 が返されることを期待します。
  2. 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 文は、テストケースが DialListen のどちらを対象としているかを判断します。これは、期待されるエラーの 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言語の 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は割愛)