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

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

このコミットは、Go言語の標準ライブラリである net/http/httptest パッケージに、その使用方法を示すための具体的なコード例を追加するものです。これにより、開発者がHTTPハンドラやHTTPクライアントのテストをより容易に記述できるよう、パッケージの利用促進と理解の深化を目的としています。

コミット

net/http/httptest: add examples

R=golang-dev, adg, bradfitz
CC=golang-dev
https://golang.org/cl/7314046

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

https://github.com/golang/go/commit/f26fc0c017e4eaaf5ebed043d39ebc03df8420ac

元コミット内容

commit f26fc0c017e4eaaf5ebed043d39ebc03df8420ac
Author: Kamil Kisiel <kamil@kamilkisiel.net>
Date:   Fri Feb 8 09:20:05 2013 -0800

    net/http/httptest: add examples
    
    R=golang-dev, adg, bradfitz
    CC=golang-dev
    https://golang.org/cl/7314046
---
 src/pkg/net/http/httptest/example_test.go | 50 +++++++++++++++++++++++++++++++
 1 file changed, 50 insertions(+)

diff --git a/src/pkg/net/http/httptest/example_test.go b/src/pkg/net/http/httptest/example_test.go
new file mode 100644
index 0000000000..239470d971
--- /dev/null
+++ b/src/pkg/net/http/httptest/example_test.go
@@ -0,0 +1,50 @@
+// Copyright 2013 The Go Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style
+// license that can be found in the LICENSE file.
+
+package httptest_test
+
+import (
+	"fmt"
+	"io/ioutil"
+	"log"
+	"net/http"
+	"net/http/httptest"
+)
+
+func ExampleRecorder() {
+	handler := func(w http.ResponseWriter, r *http.Request) {
+		http.Error(w, "something failed", http.StatusInternalServerError)
+	}
+
+	req, err := http.NewRequest("GET", "http://example.com/foo", nil)
+	if err != nil {
+		log.Fatal(err)
+	}
+
+	w := httptest.NewRecorder()
+	handler(w, req)
+
+	fmt.Printf("%d - %s", w.Code, w.Body.String())
+	// Output: 500 - something failed
+}
+
+func ExampleServer() {
+	ts := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
+		fmt.Fprintln(w, "Hello, client")
+	}))
+	defer ts.Close()
+
+	res, err := http.Get(ts.URL)
+	if err != nil {
+		log.Fatal(err)
+	}
+	greeting, err := ioutil.ReadAll(res.Body)
+	res.Body.Close()
+	if err != nil {
+		log.Fatal(err)
+	}
+
+	fmt.Printf("%s", greeting)
+	// Output: Hello, client
+}

変更の背景

Go言語の標準ライブラリは、その堅牢性と使いやすさで知られています。特に net/http パッケージは、Webアプリケーション開発の基盤として広く利用されています。しかし、どんなに優れたライブラリであっても、その機能を最大限に活用するためには、適切なドキュメントと具体的な使用例が不可欠です。

net/http/httptest パッケージは、HTTPハンドラやHTTPクライアントのテストを容易にするためのユーティリティを提供しますが、当時のGoのドキュメントには、これらの機能の具体的な利用シナリオを示すコード例が不足していました。これにより、開発者がこのパッケージをどのようにテストに組み込むべきか、あるいはどのようなテストケースを記述できるのかを理解する上で障壁が生じていました。

このコミットは、このような背景から、httptest パッケージの主要な機能である httptest.NewRecorder()httptest.NewServer() の使用例を example_test.go ファイルとして追加することで、開発者の学習コストを削減し、より効率的なテストコードの記述を促進することを目的としています。Go言語では、Example 関数は単なるドキュメントとしてだけでなく、テストの一部としても実行され、出力が期待値と一致するかどうかが検証されるため、常に最新かつ正確な使用例が提供されるという利点があります。

前提知識の解説

このコミットの変更内容を理解するためには、以下のGo言語およびHTTPに関する基本的な知識が必要です。

1. Go言語のテストとExample関数

Go言語には、標準でテストフレームワークが組み込まれています。テストファイルは _test.go というサフィックスを持ち、go test コマンドで実行されます。

  • Test 関数: func TestXxx(t *testing.T) の形式で記述され、ユニットテストや結合テストに使用されます。
  • Example 関数: func ExampleXxx() の形式で記述されます。これは、コードの具体的な使用例を示すための特別な関数です。go doc コマンドでドキュメントとして表示されるだけでなく、go test コマンド実行時に自動的にテストされ、関数のコメントに記述された // Output: と実際の出力が一致するかどうかが検証されます。これにより、ドキュメントとコードの乖離を防ぎ、常に動作する最新の例を提供できます。

2. HTTPの基本

  • HTTPリクエスト (Request): クライアントがサーバーに送信する情報(メソッド、URL、ヘッダ、ボディなど)。Goでは net/http.Request 構造体で表現されます。
  • HTTPレスポンス (Response): サーバーがクライアントに返す情報(ステータスコード、ヘッダ、ボディなど)。Goでは net/http.ResponseWriter インターフェースを通じてレスポンスを書き込みます。
  • HTTPハンドラ (Handler): HTTPリクエストを処理し、HTTPレスポンスを生成するロジック。Goでは http.Handler インターフェース(ServeHTTP(w http.ResponseWriter, r *http.Request) メソッドを持つ)または http.HandlerFunc 型(関数を http.Handler インターフェースに適合させるアダプタ)で表現されます。

3. net/http/httptest パッケージ

net/http/httptest パッケージは、HTTPサーバーを起動せずにHTTPハンドラをテストしたり、実際のHTTPサーバーを一時的に起動してHTTPクライアントの動作をテストしたりするためのユーティリティを提供します。

  • httptest.NewRecorder(): http.ResponseWriter インターフェースを実装した構造体 httptest.ResponseRecorder のインスタンスを返します。これは、HTTPハンドラが書き込んだレスポンス(ステータスコード、ヘッダ、ボディ)をメモリ上に記録するために使用されます。これにより、実際のHTTPサーバーを起動することなく、ハンドラの出力を検証できます。
  • httptest.NewServer(handler http.Handler): 指定された http.Handler を処理するテスト用のHTTPサーバーを起動します。このサーバーはランダムなポートでリッスンし、そのURL (ts.URL) を提供します。テストが完了すると、defer ts.Close() を呼び出すことでサーバーをシャットダウンできます。これは、HTTPクライアントの動作や、外部サービスとの連携をシミュレートする際に非常に便利です。

4. その他の関連パッケージ

  • fmt: フォーマットされたI/O(入出力)を実装するパッケージ。fmt.Printffmt.Fprintln などが使われます。
  • io/ioutil: I/O操作に関するユーティリティ関数を提供するパッケージ。ioutil.ReadAllio.Reader からすべてのデータを読み込むために使用されます。Go 1.16以降では io.ReadAll に移行されていますが、このコミット時点では ioutil.ReadAll が一般的でした。
  • log: シンプルなロギング機能を提供するパッケージ。log.Fatal はエラーメッセージを出力し、プログラムを終了させます。

技術的詳細

このコミットで追加された example_test.go ファイルは、net/http/httptest パッケージの二つの主要な機能、すなわち httptest.NewRecorderhttptest.NewServer の具体的な使用方法を、それぞれ ExampleRecorderExampleServer という二つの Example 関数で示しています。

ExampleRecorder() の技術的詳細

ExampleRecorder 関数は、HTTPハンドラ単体のテスト方法を示しています。

  1. ハンドラの定義: 匿名関数として http.HandlerFunc を定義しています。このハンドラは、常にHTTPステータスコード 500 Internal Server Error と "something failed" というメッセージを返すように設定されています。これは、テスト対象となるハンドラの典型的な例です。
  2. リクエストの作成: http.NewRequest("GET", "http://example.com/foo", nil) を使用して、テスト用のHTTP GETリクエストを作成します。このリクエストは、ハンドラに渡される仮想的な入力となります。第三引数の nil はリクエストボディがないことを意味します。
  3. httptest.NewRecorder() の利用: w := httptest.NewRecorder() を呼び出すことで、http.ResponseWriter インターフェースを実装した httptest.ResponseRecorder のインスタンス w を取得します。この w は、ハンドラがレスポンスを書き込む「仮想的な場所」として機能します。
  4. ハンドラの実行: handler(w, req) を呼び出し、作成したリクエスト req とレコーダー w をハンドラに渡して実行します。ハンドラは w にレスポンスを書き込みます。
  5. レスポンスの検証: ハンドラの実行後、w.Code でステータスコード(この場合は 500)を、w.Body.String() でレスポンスボディ(この場合は "something failed")を取得し、fmt.Printf で出力します。// Output: コメントにより、この出力が期待される結果と一致するかどうかが go test 実行時に検証されます。

このアプローチの利点は、実際のネットワーク通信を伴わないため、テストが高速かつ安定している点です。ハンドラのロジックが正しく動作するかどうかを、外部要因に依存せずに検証できます。

ExampleServer() の技術的詳細

ExampleServer 関数は、HTTPクライアントのテストや、実際のHTTPサーバーとの連携をシミュレートする方法を示しています。

  1. テストサーバーの起動: ts := httptest.NewServer(...) を使用して、テスト用のHTTPサーバーを起動します。引数には、このサーバーがリクエストを受け取った際に処理する http.HandlerFunc が渡されます。このハンドラは、常に "Hello, client" というメッセージをクライアントに返します。
  2. サーバーのクリーンアップ: defer ts.Close() は、ExampleServer 関数が終了する際に、起動したテストサーバーを確実にシャットダウンするための重要な処理です。これにより、リソースリークを防ぎます。
  3. HTTPリクエストの送信: http.Get(ts.URL) を使用して、起動したテストサーバーのURL (ts.URL はランダムなポートでリッスンしているサーバーの完全なURLを提供します) に対してHTTP GETリクエストを送信します。これは、実際のHTTPクライアントがサーバーにアクセスするのと同様の動作をシミュレートします。
  4. レスポンスの読み込みと検証:
    • res, err := http.Get(ts.URL) でレスポンスを取得します。
    • ioutil.ReadAll(res.Body) を使用して、レスポンスボディの内容をすべて読み込みます。
    • res.Body.Close() は、レスポンスボディを読み込んだ後に必ず呼び出すべきです。これは、HTTP接続を適切にクローズし、リソースを解放するために重要です。
    • 読み込んだボディの内容 (greeting) を fmt.Printf で出力し、// Output: コメントで期待される結果 ("Hello, client") と比較されます。

このアプローチは、HTTPクライアントが特定のサーバーとどのようにやり取りするかをテストする際に非常に有効です。例えば、APIクライアントが正しくリクエストを構築し、レスポンスをパースできるかを検証するのに役立ちます。実際のネットワーク通信をシミュレートしつつも、テスト環境内で完結するため、外部ネットワークへの依存を排除できます。

これらの例は、net/http/httptest パッケージが提供する強力なテストユーティリティを、いかに簡潔かつ効果的に利用できるかを示しており、Go言語における堅牢なWebアプリケーション開発に不可欠な要素となっています。

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

このコミットによる変更は、src/pkg/net/http/httptest/example_test.go という新しいファイルの追加のみです。

diff --git a/src/pkg/net/http/httptest/example_test.go b/src/pkg/net/http/httptest/example_test.go
new file mode 100644
index 0000000000..239470d971
--- /dev/null
+++ b/src/pkg/net/http/httptest/example_test.go
@@ -0,0 +1,50 @@
+// Copyright 2013 The Go Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style
+// license that can be found in the LICENSE file.
+
+package httptest_test
+
+import (
+	"fmt"
+	"io/ioutil"
+	"log"
+	"net/http"
+	"net/http/httptest"
+)
+
+func ExampleRecorder() {
+	handler := func(w http.ResponseWriter, r *http.Request) {
+		http.Error(w, "something failed", http.StatusInternalServerError)
+	}
+
+	req, err := http.NewRequest("GET", "http://example.com/foo", nil)
+	if err != nil {
+		log.Fatal(err)
+	}
+
+	w := httptest.NewRecorder()
+	handler(w, req)
+
+	fmt.Printf("%d - %s", w.Code, w.Body.String())
+	// Output: 500 - something failed
+}
+
+func ExampleServer() {
+	ts := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
+		fmt.Fprintln(w, "Hello, client")
+	}))
+	defer ts.Close()
+
+	res, err := http.Get(ts.URL)
+	if err != nil {
+		log.Fatal(err)
+	}
+	greeting, err := ioutil.ReadAll(res.Body)
+	res.Body.Close()
+	if err != nil {
+		log.Fatal(err)
+	}
+
+	fmt.Printf("%s", greeting)
+	// Output: Hello, client
+}

この差分は、新しいファイルが作成され、その中に50行のコードが追加されたことを示しています。追加されたコードは、httptest パッケージの ExampleRecorderExampleServer の二つのExample関数です。

コアとなるコードの解説

追加された example_test.go ファイルには、net/http/httptest パッケージの主要な機能を示す二つの Example 関数が含まれています。

func ExampleRecorder()

この関数は、http.Handler を単体でテストする方法を示しています。

func ExampleRecorder() {
	// テスト対象となるHTTPハンドラを定義します。
	// このハンドラは、常にHTTP 500エラーとメッセージを返します。
	handler := func(w http.ResponseWriter, r *http.Request) {
		http.Error(w, "something failed", http.StatusInternalServerError)
	}

	// テスト用のHTTPリクエストを作成します。
	// ここではGETリクエストを例としていますが、POSTなど他のメソッドも可能です。
	req, err := http.NewRequest("GET", "http://example.com/foo", nil)
	if err != nil {
		// リクエスト作成に失敗した場合、致命的なエラーとしてログを出力し、プログラムを終了します。
		log.Fatal(err)
	}

	// httptest.NewRecorder() を使用して、レスポンスを記録するためのレコーダーを作成します。
	// このレコーダーは、http.ResponseWriter インターフェースを実装しています。
	w := httptest.NewRecorder()
	// 定義したハンドラに、作成したレコーダーとリクエストを渡して実行します。
	// ハンドラがwに書き込んだ内容は、メモリ上のレコーダーに記録されます。
	handler(w, req)

	// レコーダーに記録されたステータスコードとボディの内容を出力します。
	// fmt.Printfの出力は、次の行の"// Output:"コメントと比較され、テストされます。
	fmt.Printf("%d - %s", w.Code, w.Body.String())
	// 期待される出力。go test -run ExampleRecorder で検証されます。
	// Output: 500 - something failed
}

この例は、HTTPハンドラが特定の入力(リクエスト)に対してどのような出力(レスポンス)を生成するかを、実際のHTTPサーバーを起動することなく検証するのに非常に役立ちます。

func ExampleServer()

この関数は、HTTPクライアントのテストや、実際のHTTPサーバーとのやり取りをシミュレートする方法を示しています。

func ExampleServer() {
	// httptest.NewServer() を使用して、テスト用のHTTPサーバーを起動します。
	// 引数には、このサーバーがリクエストを受け取った際に処理するハンドラ関数を渡します。
	// このハンドラは、常に"Hello, client"というメッセージを返します。
	ts := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
		fmt.Fprintln(w, "Hello, client")
	}))
	// defer ts.Close() は、ExampleServer関数が終了する際に、起動したテストサーバーを確実にシャットダウンします。
	// これにより、リソースリークを防ぎます。
	defer ts.Close()

	// 起動したテストサーバーのURL (ts.URL) に対してHTTP GETリクエストを送信します。
	// これは、実際のHTTPクライアントがサーバーにアクセスするのと同様の動作をシミュレートします。
	res, err := http.Get(ts.URL)
	if err != nil {
		// リクエスト送信に失敗した場合、致命的なエラーとしてログを出力し、プログラムを終了します。
		log.Fatal(err)
	}
	// レスポンスボディの内容をすべて読み込みます。
	greeting, err := ioutil.ReadAll(res.Body)
	// レスポンスボディを読み込んだ後は、必ずクローズする必要があります。
	res.Body.Close()
	if err != nil {
		// ボディの読み込みに失敗した場合、致命的なエラーとしてログを出力し、プログラムを終了します。
		log.Fatal(err)
	}

	// 読み込んだボディの内容を出力します。
	// fmt.Printfの出力は、次の行の"// Output:"コメントと比較され、テストされます。
	fmt.Printf("%s", greeting)
	// 期待される出力。go test -run ExampleServer で検証されます。
	// Output: Hello, client
}

この例は、HTTPクライアントが特定のサーバーとどのようにやり取りするかをテストする際に非常に有効です。例えば、APIクライアントが正しくリクエストを構築し、レスポンスをパースできるかを検証するのに役立ちます。

これらのExample関数は、net/http/httptest パッケージの基本的な使い方を明確に示しており、Go言語のドキュメントとテストの品質向上に貢献しています。

関連リンク

参考にした情報源リンク

  • Go言語公式ドキュメント net/http/httptest パッケージ: https://pkg.go.dev/net/http/httptest
  • Go言語公式ドキュメント testing パッケージ (Example関数について): https://pkg.go.dev/testing
  • Go言語公式ドキュメント net/http パッケージ: https://pkg.go.dev/net/http
  • Go言語公式ブログ: Writing custom go vet checks (Example関数のテストについて言及): https://go.dev/blog/vet (直接の参照ではないが、Example関数のテスト実行メカニズムの理解に役立つ)
  • Go言語公式ブログ: The Go Blog (Go言語の設計思想や機能に関する一般的な情報): https://go.dev/blog/ (一般的な背景知識の補完)