コンテキスト認識リーダー
Context-aware Reader
この章では、Mat・Ryer と David・Hernandez が The Pace Dev Blogで書いた、コンテキストを意識したio.Reader
をテストドライブする方法を示します。
コンテキスト認識リーダー?
まず、io.Reader
の簡単な入門書です。
この本の他の章を読んだことがあれば、ファイルを開いたり、JSONをエンコードしたり、その他の一般的なタスクを実行したりしたときに、io.Reader
に出くわすことになります。 something からのデータの読み取りに関する単純な抽象化です。
io.Reader
を使用すると、標準ライブラリから多くの再利用を得ることができます。 これは、非常に一般的に使用される抽象化です(対応する io.Writer
とともに)
コンテキスト認識?
前の章でキャンセルを提供するために「コンテキストcontext
」を使用する方法について説明しました。これは、計算コストがかかる可能性のあるタスクを実行していて、それらを停止できるようにしたい場合に特に便利です。
io.Reader
を使用している場合、速度について保証はありません。1ナノ秒または数百時間かかる場合があります。自分のアプリケーションでこの種のタスクをキャンセルできると便利だと思うかもしれませんが、それはMatとDavidが書いたものです。
彼らはこの問題を解決するために2つの単純な抽象化(context.Context
とio.Reader
)を組み合わせました。
io.Reader
をラップしてキャンセルできるように、いくつかの機能をTDDで試してみましょう。
これをテストすることは興味深い挑戦を引き起こします。通常、io.Reader
を使用するときは、通常、他の関数にそれを提供しているので、詳細に気を使う必要はありません。 json.NewDecoder
やioutil.ReadAll
など。
デモしたいのは、次のようなものです。
"ABCDEF"を持つ
io.Reader
が与えられたとき、途中でキャンセル信号を送っても、読み続けようとすると何も出ないので、"ABC"しか出ません。
インターフェースをもう一度見てみましょう。
Reader
のRead
メソッドは、その内容を、提供する []byte
に読み込みます。
したがって、すべてを読むのではなく、次のことができます。
すべてのコンテンツに適合しない固定サイズのバイト配列を提供します
キャンセル信号を送信します
再試行してもう一度読み取ると、0バイトの読み取りエラーが返されます
とりあえず、キャンセルのない「ハッピーパス」テストを書いてみましょう。これは、まだ本番用のコードを記述しなくても問題に慣れることができるようにするためです。
いくつかのデータを含む文字列から
io.Reader
を作成します読み込む内容がリーダーの内容よりも小さいバイト配列
呼び出しを読んで、内容を確認し、繰り返します
これから、2回目の読み取りの前に動作を変更するために何らかのキャンセル信号を送信することを想像できます。
それがどのように機能するかを見てきましたので、残りの機能をTDDします。
最初にテストを書く
io.Reader
をcontext.Context
で作成できるようにしたいと考えています。
TDDでは、希望するAPIを想像することから始めて、そのためのテストを作成するのが最善です。
そこから、コンパイラーと失敗したテスト出力で解決策を導きましょう。
テストを実行してみます
テストを実行するための最小限のコードを記述し、失敗したテスト出力を確認します
この関数を定義する必要があり、io.Reader
を返す必要があります
試しに実行してみると
予想通り
成功させるのに十分なコードを書く
今のところ、渡したio.Reader
を返すだけです
これでテストに成功するはずです。
わかっています、わかっています、これは馬鹿げていて衒学的に見えますが、派手な作業に取り掛かる前に、io.Reader
の「通常の」振る舞いを壊していないかどうかを、ある程度検証することが重要です。
最初にテストを書く
次に、キャンセルしてみる必要があります。
最初のテストは多かれ少なかれコピーできますが、今は次のようになっています。
最初の読み取り後に「キャンセル
cancel
」できるように、キャンセル付きのcontext.Context
を作成しますコードを機能させるには、関数に
ctx
を渡す必要がありますそして、
cancel
後に何も読まれなかったことを主張します。
テストを実行してみます
テストを実行するための最小限のコードを記述し、失敗したテスト出力を確認します
コンパイラは何をすべきかを指示しています。コンテキストを受け入れるように署名を更新します。
(最初のテストが context.Background
を通過するように更新する必要があります。)
これで、非常に明確な失敗したテスト出力を見ることができるはずです。
成功させるのに十分なコードを書く
この時点では、MatとDavidによる元の投稿からコピーして貼り付けていますが、ゆっくりと繰り返し実行します。
読み込んだio.Reader
とcontext.Context
をカプセル化するタイプが必要であることはわかっているので、それを作成して、元のio.Reader
の代わりに関数からそれを返してみましょう
このサイトで何度も強調してきたように、ゆっくりと進み、コンパイラに助けてもらいましょう。
抽象化はいい感じだけど、必要なインターフェースが実装されてないから、メソッドを追加しよう。
テストを実行すると、それらはコンパイルするはずですが、パニックになります。これはまだ進行中です。
最初のテストを通過させるために、基礎となるio.Reader
への呼び出しを委任してみましょう。
この時点で、私たちは再び私たちの幸せなパスのテストが合格し、それは私たちのものがきれいに抽象化されているように感じています。
2回目のテストをパスするためには、context.Context
がキャンセルされたかどうかを確認する必要があります。
これですべてのテストが通過するはずです。エラーをcontext.Context
から返していることに気づくでしょう。これにより、コードの呼び出し元がキャンセルが発生した様々な理由を調べることができます。
まとめ
小さなインターフェイスが良く、構成が簡単
あるもの (
io.Reader
) を別のもので拡張しようとするとき、通常は委任パターン(delegation pattern)に到達したいと思います。
ソフトウェア工学では、デリゲーションパターンはオブジェクト指向の設計パターンであり、オブジェクトを構成して継承と同じコードの再利用を実現することができます。
この種の作業を始める簡単な方法は、他の部分の動作を変更するために他の部分を合成し始める前に、デリゲートをラップして、デリゲートが通常どのように動作するかを保証するテストを書くことです。これは、目標に向かってコードを書く際に、物事を正しく動作させておくのに役立ちます。
最終更新