Contextパッケージの基本
Go言語のcontext
パッケージは、API境界やプロセス間でキャンセル信号、タイムアウト、値の受け渡しを行うためのメカニズムを提供します。
Contextの作成
context
パッケージは、context.Background()
とcontext.TODO()
の2つの関数を提供しています。これらの関数は、新しいContextを作成します。
ctx := context.Background()
Contextに値を設定する
context.WithValue()
関数を使用して、Contextに値を設定できます。この関数は新しいContextを返します。
ctx := context.WithValue(context.Background(), key, value)
Contextから値を取得する
Contextから値を取得するには、Value()
メソッドを使用します。
value := ctx.Value(key)
Contextのキャンセルとタイムアウト
context.WithCancel()
関数は、親Contextをキャンセル可能なContextに変換します。この関数は新しいContextとそのContextをキャンセルするための関数を返します。
ctx, cancel := context.WithCancel(parentCtx)
context.WithTimeout()
関数は、指定した時間が経過したら自動的にキャンセルされるContextを作成します。
ctx, cancel := context.WithTimeout(parentCtx, timeout)
以上がGo言語のcontext
パッケージの基本的な使い方です。このパッケージを理解し、適切に使用することで、Goの並行処理をより効果的に制御することができます。次のセクションでは、これらの概念を使用してユニットテストを書く方法について説明します。
Contextを使用したユニットテスト
Go言語のcontext
パッケージは、ユニットテストにおいても非常に有用です。特に、非同期操作やタイムアウトを伴う関数のテストにおいて、context
パッケージは重要な役割を果たします。
テストの準備
まず、テスト対象の関数がContextを引数として受け取るようにします。これにより、テスト時に任意のContextを渡すことができます。
func DoSomething(ctx context.Context, arg ArgType) (ResultType, error) {
// ...
}
タイムアウトを伴うテスト
context.WithTimeout()
を使用して、関数が指定した時間内に終了することを確認するテストを書くことができます。
func TestDoSomething(t *testing.T) {
ctx, cancel := context.WithTimeout(context.Background(), 100*time.Millisecond)
defer cancel()
result, err := DoSomething(ctx, arg)
if err != nil {
t.Fatalf("expected nil error, got %v", err)
}
// Check the result
// ...
}
このテストでは、DoSomething
関数が100ミリ秒以内に終了しない場合、Contextがキャンセルされ、関数はエラーを返します。
キャンセルを伴うテスト
同様に、context.WithCancel()
を使用して、関数がContextのキャンセルに適切に反応することを確認するテストを書くことができます。
func TestDoSomethingWithCancel(t *testing.T) {
ctx, cancel := context.WithCancel(context.Background())
go func() {
time.Sleep(50 * time.Millisecond)
cancel()
}()
_, err := DoSomething(ctx, arg)
if err != context.Canceled {
t.Fatalf("expected context.Canceled, got %v", err)
}
}
このテストでは、DoSomething
関数がContextのキャンセルに適切に反応し、context.Canceled
エラーを返すことを確認します。
以上が、Go言語のcontext
パッケージを使用したユニットテストの基本的な書き方です。これらのテストパターンを理解し、適切に使用することで、Goの並行処理を含むコードの品質を向上させることができます。次のセクションでは、HTTPリクエストとContextの関連性について説明します。
ContextとHTTPリクエスト
Go言語のcontext
パッケージは、HTTPリクエストの処理においても重要な役割を果たします。特に、リクエストのライフサイクル全体にわたるキャンセルやタイムアウトの制御、リクエスト固有の情報の受け渡しに使用されます。
HTTPリクエストとContextの関連性
Goのnet/http
パッケージのRequest
型は、Context()
メソッドを持っています。これにより、各リクエストはそれ自体がContextを持つことができます。
func handler(w http.ResponseWriter, r *http.Request) {
ctx := r.Context()
// ...
}
Contextを使用したリクエストのキャンセル
HTTPリクエストのContextは、クライアントが接続を閉じたときに自動的にキャンセルされます。これにより、リクエストの処理を中断することができます。
func handler(w http.ResponseWriter, r *http.Request) {
ctx := r.Context()
select {
case <-time.After(2 * time.Second):
fmt.Fprintln(w, "Hello")
case <-ctx.Done():
fmt.Fprint(w, "Request cancelled")
}
}
このハンドラは、2秒後に”Hello”をレスポンスとして返します。しかし、クライアントが接続を閉じると、ctx.Done()
チャネルが閉じられ、”Request cancelled”がレスポンスとして返されます。
Contextを使用したリクエスト固有の情報の受け渡し
Contextは、リクエスト固有の情報(例えば、認証情報やリクエストIDなど)をミドルウェアやハンドラ間で受け渡すのにも使用できます。
func middleware(next http.Handler) http.Handler {
return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
ctx := context.WithValue(r.Context(), "key", "value")
next.ServeHTTP(w, r.WithContext(ctx))
})
}
以上が、Go言語のcontext
パッケージとHTTPリクエストの関連性についての基本的な説明です。この知識を活用することで、リクエストのライフサイクルをより効果的に制御し、リクエスト固有の情報を適切に管理することができます。次のセクションでは、Contextのキャンセルとタイムアウトについて詳しく説明します。
Contextのキャンセルとタイムアウト
Go言語のcontext
パッケージは、キャンセルとタイムアウトの制御において重要な役割を果たします。これにより、長時間実行される操作を適切に制御することができます。
Contextのキャンセル
context.WithCancel(parent Context)
関数は、新しいContextとそのContextをキャンセルするための関数を返します。このキャンセル関数を呼び出すと、関連するContextとその子Contextはすぐにキャンセルされます。
ctx, cancel := context.WithCancel(context.Background())
// ...
cancel()
Contextのタイムアウト
context.WithTimeout(parent Context, timeout time.Duration)
関数は、新しいContextとそのContextをキャンセルするための関数を返します。この関数は、指定した時間が経過したら自動的にContextをキャンセルします。
ctx, cancel := context.WithTimeout(context.Background(), 2*time.Second)
defer cancel() // Important: Always defer the cancellation function to avoid leaks!
// ...
Contextのデッドライン
context.WithDeadline(parent Context, d time.Time)
関数は、新しいContextとそのContextをキャンセルするための関数を返します。この関数は、指定した時刻になったら自動的にContextをキャンセルします。
deadline := time.Now().Add(2 * time.Hour)
ctx, cancel := context.WithDeadline(context.Background(), deadline)
defer cancel() // Important: Always defer the cancellation function to avoid leaks!
// ...
以上が、Go言語のcontext
パッケージにおけるキャンセルとタイムアウトの基本的な使い方です。これらの機能を理解し、適切に使用することで、Goの並行処理をより効果的に制御することができます。次のセクションでは、Contextを使用したエラーハンドリングについて説明します。
Contextを使用したエラーハンドリング
Go言語のcontext
パッケージは、エラーハンドリングにおいても重要な役割を果たします。特に、非同期操作や並行処理を行う際に、エラーの伝播と処理を効率的に行うことができます。
Contextのキャンセルとエラーハンドリング
Contextがキャンセルされると、ctx.Done()
チャネルが閉じられ、ctx.Err()
関数は非nil
のエラーを返します。これを利用して、キャンセルされた操作のエラーハンドリングを行うことができます。
func doSomething(ctx context.Context) error {
// ...
select {
case <-ctx.Done():
return ctx.Err()
default:
// ...
}
return nil
}
この関数は、Contextがキャンセルされた場合にはctx.Err()
を返し、それ以外の場合にはnil
を返します。
タイムアウトとエラーハンドリング
タイムアウトも同様に、Contextがタイムアウトした場合にはctx.Err()
関数は非nil
のエラーを返します。これを利用して、タイムアウトした操作のエラーハンドリングを行うことができます。
ctx, cancel := context.WithTimeout(context.Background(), 100*time.Millisecond)
defer cancel()
err := doSomething(ctx)
if err != nil {
fmt.Println("Error:", err)
}
このコードは、doSomething
関数が100ミリ秒以内に終了しない場合にはエラーを出力します。
以上が、Go言語のcontext
パッケージを使用したエラーハンドリングの基本的な方法です。これらの機能を理解し、適切に使用することで、Goの並行処理を含むコードのエラーハンドリングを効率的に行うことができます。次のセクションでは、実践的なテストケースについて説明します。
実践的なテストケース
Go言語のcontext
パッケージを使用した実践的なテストケースを考えてみましょう。ここでは、HTTPリクエストを処理するサーバーのテストを例にします。
テストの準備
まず、テスト対象のサーバーを準備します。このサーバーは、指定した時間だけスリープした後にレスポンスを返すシンプルなものとします。
func server(w http.ResponseWriter, r *http.Request) {
sleep, _ := strconv.Atoi(r.URL.Query().Get("sleep"))
time.Sleep(time.Duration(sleep) * time.Millisecond)
fmt.Fprintln(w, "Hello, world!")
}
タイムアウトを伴うテスト
次に、このサーバーが指定した時間内にレスポンスを返すことを確認するテストを書きます。これにはcontext.WithTimeout()
を使用します。
func TestServer(t *testing.T) {
srv := httptest.NewServer(http.HandlerFunc(server))
defer srv.Close()
ctx, cancel := context.WithTimeout(context.Background(), 100*time.Millisecond)
defer cancel()
req, _ := http.NewRequestWithContext(ctx, http.MethodGet, srv.URL+"?sleep=200", nil)
_, err := http.DefaultClient.Do(req)
if err == nil || !errors.Is(err, context.DeadlineExceeded) {
t.Errorf("expected context.DeadlineExceeded, got %v", err)
}
}
このテストでは、サーバーが200ミリ秒スリープするようにリクエストを送ります。しかし、Contextのタイムアウトは100ミリ秒に設定しているため、context.DeadlineExceeded
エラーが返されることを期待します。
以上が、Go言語のcontext
パッケージを使用した実践的なテストケースの一例です。このように、context
パッケージは非同期操作や並行処理を含むコードのテストを効率的に行うための強力なツールとなります。この知識を活用して、品質の高いGoのコードを書くことができます。この記事があなたのGo言語の学習に役立てば幸いです。