[インデックス 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.Printf
やfmt.Fprintln
などが使われます。io/ioutil
: I/O操作に関するユーティリティ関数を提供するパッケージ。ioutil.ReadAll
はio.Reader
からすべてのデータを読み込むために使用されます。Go 1.16以降ではio.ReadAll
に移行されていますが、このコミット時点ではioutil.ReadAll
が一般的でした。log
: シンプルなロギング機能を提供するパッケージ。log.Fatal
はエラーメッセージを出力し、プログラムを終了させます。
技術的詳細
このコミットで追加された example_test.go
ファイルは、net/http/httptest
パッケージの二つの主要な機能、すなわち httptest.NewRecorder
と httptest.NewServer
の具体的な使用方法を、それぞれ ExampleRecorder
と ExampleServer
という二つの Example
関数で示しています。
ExampleRecorder()
の技術的詳細
ExampleRecorder
関数は、HTTPハンドラ単体のテスト方法を示しています。
- ハンドラの定義: 匿名関数として
http.HandlerFunc
を定義しています。このハンドラは、常にHTTPステータスコード500 Internal Server Error
と "something failed" というメッセージを返すように設定されています。これは、テスト対象となるハンドラの典型的な例です。 - リクエストの作成:
http.NewRequest("GET", "http://example.com/foo", nil)
を使用して、テスト用のHTTP GETリクエストを作成します。このリクエストは、ハンドラに渡される仮想的な入力となります。第三引数のnil
はリクエストボディがないことを意味します。 httptest.NewRecorder()
の利用:w := httptest.NewRecorder()
を呼び出すことで、http.ResponseWriter
インターフェースを実装したhttptest.ResponseRecorder
のインスタンスw
を取得します。このw
は、ハンドラがレスポンスを書き込む「仮想的な場所」として機能します。- ハンドラの実行:
handler(w, req)
を呼び出し、作成したリクエストreq
とレコーダーw
をハンドラに渡して実行します。ハンドラはw
にレスポンスを書き込みます。 - レスポンスの検証: ハンドラの実行後、
w.Code
でステータスコード(この場合は500
)を、w.Body.String()
でレスポンスボディ(この場合は "something failed")を取得し、fmt.Printf
で出力します。// Output:
コメントにより、この出力が期待される結果と一致するかどうかがgo test
実行時に検証されます。
このアプローチの利点は、実際のネットワーク通信を伴わないため、テストが高速かつ安定している点です。ハンドラのロジックが正しく動作するかどうかを、外部要因に依存せずに検証できます。
ExampleServer()
の技術的詳細
ExampleServer
関数は、HTTPクライアントのテストや、実際のHTTPサーバーとの連携をシミュレートする方法を示しています。
- テストサーバーの起動:
ts := httptest.NewServer(...)
を使用して、テスト用のHTTPサーバーを起動します。引数には、このサーバーがリクエストを受け取った際に処理するhttp.HandlerFunc
が渡されます。このハンドラは、常に "Hello, client" というメッセージをクライアントに返します。 - サーバーのクリーンアップ:
defer ts.Close()
は、ExampleServer
関数が終了する際に、起動したテストサーバーを確実にシャットダウンするための重要な処理です。これにより、リソースリークを防ぎます。 - HTTPリクエストの送信:
http.Get(ts.URL)
を使用して、起動したテストサーバーのURL (ts.URL
はランダムなポートでリッスンしているサーバーの完全なURLを提供します) に対してHTTP GETリクエストを送信します。これは、実際のHTTPクライアントがサーバーにアクセスするのと同様の動作をシミュレートします。 - レスポンスの読み込みと検証:
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
パッケージの ExampleRecorder
と ExampleServer
の二つの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 CL 7314046: https://golang.org/cl/7314046
参考にした情報源リンク
- 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/ (一般的な背景知識の補完)