依存性注入
Dependency Injection
これにはインターフェースの理解が必要になるため、構造体のセクションをすでに読んでいることが前提です。
プログラミングコミュニティには、依存性注入に関する誤解がたくさんあります。
このガイドでは、
フレームワークは必要ありません
デザインが複雑になりすぎない
テストを容易にします
優れた汎用関数を作成できます
hello-world
の章で行ったように、誰かに挨拶する関数を書きたいのですが、今回は actual Printing をテストします。
要約すると、その関数は次のようになります。
しかし、これをどのようにテストできますか? fmt.Printf
を呼び出すと stdout に出力されますが、テストフレームワークを使用してキャプチャするのはかなり困難です。
私たちがする必要があるのは、表示の依存関係を注入 (過ぎたるは及ばざるが如し)をできるようにすることです。 関数は気にする必要はありません _ 場所 _ または _ 方法 _ 表示が行われるため、 _ インターフェースを受け入れる必要があります_ 具体的なタイプではありません。
その場合は、実装を変更して表示するように制御し、テストできるようにします。 実際では、stdoutに書き込むものを注入します。
fmt.Printf
のソースコードを見ると、フックする方法がわかります。
面白いですね! 内部では、 Printf
はos.Stdout
を渡して Fprintf
を呼び出しているだけです。
os.Stdout
とは正確に何ですか? Fprintf
は第1引数として何が渡されることを期待していますか?
io.Writer
さらに多くのGoコードを書くと、このインターフェイスが「このデータをどこかに置く」ための優れた汎用インターフェイスであるため、多くのポップアップが表示されます。
つまり、私たちは最終的に Writer
を使用して挨拶をどこかに送信していることを知っています。この既存の抽象化を使用して、コードをテスト可能にし、再利用可能にします。
最初にテストを書く
bytes
パッケージのbuffer
タイプは Writer
インターフェースを実装しています。
テストでこれを使用してWriter
として送信し、Greet
を呼び出した後に何が書き込まれたかを確認できます。
テストを試して実行する
テストはコンパイルされません
テストを実行するための最小限のコードを記述し、失敗したテスト出力を確認します
コンパイラを読んで、問題を修正してください。
Hello, Chris di_test.go:16: got '' want 'Hello, Chris'
テストは失敗します。名前は出力されますが、標準出力になることに注意してください。
成功させるのに十分なコードを書く
テストでは、ライターを使用して挨拶をバッファに送信します。fmt.Fprintf
はfmt.Printf
に似ていますが、代わりに Writer
を使用して文字列を送信しますが、fmt.Printf
のデフォルトはstdoutです。
テストに合格しました。
リファクタリング♪
以前のコンパイラーは、bytes.Buffer
へのポインターを渡すように指示しました。これは技術的には正しいですが、あまり役に立ちません。
これを実証するために、Greet
関数を標準出力に出力するGoアプリケーションに接続してみてください。
./di.go:14:7: cannot use os.Stdout (type *os.File) as type *bytes.Buffer in argument to Greet
前に説明したように、fmt.Fprintf
を使用すると、os.Stdout
と bytes.Buffer
の両方の実装がわかっているio.Writer
を渡すことができます。
より汎用的なインターフェースを使用するようにコードを変更すると、テストとアプリケーションの両方で使用できるようになります。
io.Writerの詳細
io.Writer
を使用してデータを書き込むことができる他の場所は何ですか? Greet
関数はどれほど一般的な目的ですか?
インターネット
以下を実行します
プログラムを実行し、http://localhost:5000に移動します。グリーティング機能が使用されているのがわかります。
HTTPサーバーについては後の章で説明しますので、詳細についてはあまり気にしないでください。
HTTPハンドラーを作成すると、 http.ResponseWriter
と、リクエストの作成に使用された http.Request
が与えられます。サーバーを実装するときは、ライターを使用して応答を write します。
http.ResponseWriter
もio.Writer
を実装していると思われるので、ハンドラー内で Greet
関数を再利用できます。
まとめ
最初のコードは、制御できない場所にデータを書き込んだため、簡単にテストできませんでした。
テストによって動機付けされたコードをリファクタリングして、制御できるようにしました。 データを、依存関係を注入することによって書き込まれ、次のことが可能になりました:
Motivated by our tests we refactored the code so we could control where the data was written by injecting a dependency which allowed us to:
コードをテストする関数を簡単にテストできない場合は、通常、依存関係が関数またはグローバルな状態に組み込まれているためです。たとえば、ある種のサービス層で使用されているグローバルデータベース接続プールがある場合、テストが困難になる可能性が高く、実行が遅くなります。DIは、(インターフェイスを介して)データベースの依存関係を挿入するように動機付けし、テストで制御できるものでモックアウトできます。
懸念事項を分離して、「データの移動先」と「生成方法」を分離します。メソッド/関数の責任が多すぎると感じた場合は、(データの生成、およびデータベースへの書き込み、HTTPリクエストの処理、およびドメインレベルのロジックの実行)おそらくDIが必要なツールになるでしょう。
コードをさまざまなコンテキストで再利用できるようにするコードを使用できる最初の「新しい」コンテキストは、テスト内です。しかし、さらに誰かがあなたの関数で何か新しいことを試したい場合、彼らは彼ら自身の依存関係を注入することができます。
モックにするのはどうなの? DIにも必要だそうですが、それも悪だそうです。
モックについては後で詳しく説明します(そしてそれは悪ではありません)。 モックを使用して、実際に注入するものを、テストで制御および検査できる偽バージョンに置き換えます。 私たちの場合でも、標準ライブラリには、使用する準備ができています。
Go標準ライブラリは本当に良いです。時間をかけて勉強してください。
このようにio.Writer
インターフェースにある程度慣れていることで、テストでbytes.Buffer
を Writer
として使うことができ、標準ライブラリの他のWriter
を使ってコマンドラインアプリやウェブサーバで関数を使うことができます。
標準ライブラリに慣れるほど、これらの汎用インターフェイスが表示され、独自のコードで再利用して、ソフトウェアをさまざまなコンテキストで再利用可能にすることができます。
この例は、プログラミング言語Go, の章に大きく影響されているため、これを楽しんだ場合、是非買ってみてください!
最終更新