選択
Select
2つのURLを取得し、それらをHTTP GETでヒットして最初に返されたURLを返すことで「競合」するWebsiteRacer
と呼ばれる関数を作成するように求められました。 10秒以内に戻らない場合は、「エラー(error
)」を返します。
これには
HTTP呼び出しを行うための
net/http
。net/http/httptest
はテストを支援するためのものです。ゴルーチン。
プロセスを同期するための
select
。
最初にテストを書く
まずは、単純なことから始めましょう。
これは完璧ではなく、問題があることはわかっていますが、うまくいくでしょう。 物事を最初から完璧にするのにあまり夢中にならないようにすることが重要です。
テストを実行してみます
./racer_test.go:14:9: undefined: Racer
テストを実行するための最小限のコードを記述し、失敗したテスト出力を確認します
racer_test.go:25: got '', want 'http://www.quii.co.uk'
成功させるのに十分なコードを書く
各URLについて
time.Now()
を使用して、URL
を取得しようとする直前に記録します。次に、
http.Get
を使用して、URL
のコンテンツを取得します。この関数はhttp.Response
とerror
を返しますが、今のところこれらの値には興味がありません。time.Since
は開始時間を取り、差のtime.Duration
を返します。
これを実行したら、期間を比較してどちらが最も速いかを確認します。
問題
これにより、テストに合格する場合と合格しない場合があります。 問題は、実際のWebサイトに連絡して、独自のロジックをテストしていることです。
HTTPを使用するコードのテストは非常に一般的であるため、Goの標準ライブラリには、テストに役立つツールがあります。
モックと依存性注入の章では、コードをテストするために外部サービスに依存したくないという理想的な方法について説明しました。
スロー(Slow)
フレーク状(Flaky)
エッジケースをテストできません(Can't test edge cases)
標準ライブラリには、net/http/httptest
というパッケージがあり、模擬HTTPサーバーを簡単に作成できます。
テストをモックを使用するように変更して、制御できる信頼性の高いサーバーをテストできるようにします。
構文は少しせわしなく見えるかもしれませんが、時間をかけてください。
httptest.NewServer
は、anonymous function を介して送信するhttp.HandlerFunc
を受け取ります。
http.HandlerFunc
は、type HandlerFunc func(ResponseWriter, *Request)
のようなタイプです。
実際に言っているのは、ResponseWriter
とRequest
を受け取る関数が必要なことだけです。 これは、HTTPサーバーにとってそれほど驚くべきことではありません。
ここには特別な魔法はありません。これは、Goで _ 実際に _ HTTPサーバーを作成する方法でもあります。唯一の違いは、それをhttptest.NewServer
でラップすることです。これにより、リッスンする開いているポートが見つかり、テストが完了したら閉じることができるため、テストでの使用が簡単になります。
2つのサーバー内では、遅いサーバーに他のサーバーよりも遅いリクエストを受け取ったときに、遅い方のtime.Sleep
を作成します。
次に、両方のサーバーが、w.WriteHeader(http.StatusOK)
を使用してOK
応答を呼び出し元に返します。
テストを再実行すると、テストは確実に成功し、より高速になるはずです。 これらの睡眠を試して、意図的にテストを中断します。
リファクタリング♪
製品コードとテストコードの両方に重複があります。
このドライアップ(DRY-ing up)により、Racer
コードが非常に読みやすくなります。
偽のサーバーの作成をmakeDelayedServer
という関数にリファクタリングし、興味のないコードをテストから除外して繰り返しを減らしました。
defer
defer
関数呼び出しの前にdefer
を付けることで、その関数を含まれている関数の最後に呼び出します。
場合によっては、ファイルを閉じるなどのリソースをクリーンアップする必要があります。この場合、サーバーがポートをリッスンし続けないようにサーバーを閉じる必要があります。
これを関数の最後に実行したいが、将来のコードの読む人のために、サーバーを作成した場所の近くに命令を置いておきます。
私たちのリファクタリングは改善であり、これまでに取り上げたGo機能を考えると合理的なソリューションですが、ソリューションをよりシンプルにすることができます。
プロセスの同期
Goが同時実行性に優れているのに、なぜWebサイトの速度を次々にテストするのですか?両方を同時にチェックできるはずです。
リクエストの「正確な応答時間」については特に気にしません。どちらが最初に返されるかを知りたいだけです。
これを行うために、プロセスを非常に簡単かつ明確に同期するのに役立つselect
と呼ばれる新しい構成を導入します。
ping
ping
chan struct{}
を作成して返す関数ping
を定義しました。
私たちのケースでは、チャネルに送信されるタイプを ケア するのではなく、完了したことを通知したいだけです。 チャネルを閉じることは完全に機能します!
なぜstruct{}
で、bool
のような別の型ではないのですか?まあ、chan struct{}
はメモリの観点から利用できる最小のデータ型なので、bool
に対して割り当てはありません。 ちゃんと閉じて何も送信しないので、なぜ何かを割り当てるのですか?
同じ関数内で、http.Get(url)
を完了すると、そのチャネルに信号を送信するゴルーチン(goroutine
)を開始します。
常にチャネルを作成する
チャネルを作成するときに、make
を使用する方法に注意してください。 「var ch chan struct{}
」と言うのではなく。 var
を使用すると、変数は型の「ゼロ」値で初期化されます。したがって、string
の場合は""
、int
の場合は0
になります。
チャネルの場合、ゼロ値はnil
であり、<-
で送信しようとすると、nil
チャネルに送信できないため、永久にブロックされます。
これは Go Playground で実際に見ることができます
select
select
同時実行の章を思い出すと、myVar := <-ch
を使用して値がチャネルに送信されるのを待つことができます。値を待っているので、これは blocking 呼び出しです。
select
でできることは、multiple チャネルで待機することです。 値を送信する最初のものは「勝ち」、case
の下のコードが実行されます。
select
でping
を使用して、URL
ごとに2つのチャネルを設定します。 最初にチャネルに書き込む方は、コードがselect
で実行され、その結果、URL
が返されます(勝者となります)。
これらの変更後、コードの背後にある意図は非常に明確になり、実装は実際にはより単純になります。
タイムアウト
最後の要件は、Racer
に10秒以上かかる場合にエラーを返すことでした。
最初にテストを書く
テストサーバーがこのシナリオを実行するために戻るまでに10秒以上かかるようにしました。 ここでは、Racer
が2つの値を返すことを期待しています。勝つURL(このテストでは_
で無視)と error
。
テストを実行してみます
./racer_test.go:37:10: assignment mismatch: 2 variables but 1 values
テストを実行するための最小限のコードを記述し、失敗したテスト出力を確認します
勝者とerror
を返すようにRacer
の署名を変更します。ハッピーケースの場合はnil
を返します。
コンパイラーは first test が1つの値しか検索しないと文句を言うので、この行をgot, _ := Racer(slowURL, fastURL)
に変更します。 確認すると、私たちの幸せなシナリオでエラーが発生しないことを確認する必要があります。
11秒後に実行すると、失敗します。
成功させるのに十分なコードを書く
time.After
は、select
を使用する場合に非常に便利な関数です。
今回のケースでは発生しませんでしたが、リッスンしているチャネルが値を返さない場合、永久にブロックするコードを書く可能性があります。time.After
は、chan
( ping
のように)を返し、指定した時間が経過すると信号を送ります。
私たちにとってこれは完璧です。a
またはb
が戻って成功した場合、10秒に到達すると、time.After
がシグナルを送信し、error
を返します。
遅いテスト
問題は、このテストの実行に10秒かかることです。そのような単純なロジックの場合、これは気分が良くありません。
私たちができることは、タイムアウトを構成可能にすることです。したがって、テストでは非常に短いタイムアウトを設定できます。コードを実際に使用する場合は、10秒に設定できます。
タイムアウトを指定していないため、テストはコンパイルされません。
急いでこのデフォルト値を両方のテストに追加する前に、リッスンしてみましょう_。
「ハッピー」テストのタイムアウトを気にしますか?
タイムアウトに関する要件は明示的でした
この知識を踏まえて、テストとコードのユーザーの両方に同情するように少しリファクタリングしてみましょう。
ユーザーと最初のテストでは、Racer
(これは内部でConfigurableRacer
を使用します)を使用でき、悲しいパステストではConfigurableRacer
を使用できます。
最初のテストに最後のチェックを1つ追加して、error
が発生しないことを確認しました。
まとめ
select
select
複数のチャネルで待機するのに役立ちます。
場合によっては、
case.
の1つにtime.After
を含めて、システムが永久にブロックされるのを防ぐ必要があります。
httptest
httptest
テストサーバーを作成して、信頼性の高い制御可能なテストを作成できる便利な方法。
「実際の」
net/http
サーバーと同じインターフェースを使用します。これは一貫性があり、習得するのに時間がかかります。
最終更新