Comment on page
依存性注入
Dependency Injection
これにはインターフェースの理解が必要になるため、構造体のセクションをすでに読んでいることが前提です。
プログラミングコミュニティには、依存性注入に関する誤解がたくさんあります。
このガイドでは、
- フレームワークは必要ありません
- デザインが複雑になりすぎない
- テストを容易にします
- 優れた汎用関数を作成できます
hello-world
の章で行ったように、誰かに挨拶する関数を書きたいのですが、今回は actual Printing をテストします。要約すると、その関数は次のようになります。
func Greet(name string) {
fmt.Printf("Hello, %s", name)
}
しかし、これをどのようにテストできますか?
fmt.Printf
を呼び出すと stdout に出力されますが、テストフレームワークを使用してキャプチャするのはかなり困難です。私たちがする必要があるのは、表示の依存関係を注入 (過ぎたるは及ばざるが如 し)をできるようにすることです。 関数は気にする必要はありません _ 場所 _ または _ 方法 _ 表示が行われるため、 _ インターフェースを受け入れる必要があります_ 具体的なタイプではありません。
その場合は、実装を変更して表示するように制御し、テストできるようにします。 実際では、stdoutに書き込むものを注入します。
fmt.Printf
のソースコードを見ると、フックする方法がわかります。// It returns the number of bytes written and any write error encountered.
func Printf(format string, a ...interface{}) (n int, err error) {
return Fprintf(os.Stdout, format, a...)
}
面白いですね! 内部では、
Printf
はos.Stdout
を渡して Fprintf
を呼び出しているだけです。os.Stdout
とは正確に何ですか? Fprintf
は 第1引数として何が渡されることを期待していますか?func Fprintf(w io.Writer, format string, a ...interface{}) (n int, err error) {
p := newPrinter()
p.doPrintf(format, a)
n, err = w.Write(p.buf)
p.free()
return
}
io.Writer
type Writer interface {
Write(p []byte) (n int, err error)
}
さらに多くのGoコードを書くと、このインターフェイスが「このデータをどこかに置く」ための優れた汎用インターフェイスであるため、多くのポップアップが表示されます。
つまり、私たちは最終的に
Writer
を使用して挨拶をどこかに送信していることを知っています。この既存の抽象化を使用して、コードをテスト可能にし、再利用可能にします。func TestGreet(t *testing.T) {
buffer := bytes.Buffer{}
Greet(&buffer, "Chris")
got := buffer.String()
want := "Hello, Chris"
if got != want {
t.Errorf("got %q want %q", got, want)
}
}
bytes
パッケージのbuffer
タイプは Writer
インターフェースを実装しています。テストでこれを使用して
Writer
として送信し、Greet
を呼び出した後に何が書き込まれたかを確認できます。テストはコンパイルされません
./di_test.go:10:7: too many arguments in call to Greet
have (*bytes.Buffer, string)
want (string)
コンパイラを読んで、問題を修正してください。
func Greet(writer *bytes.Buffer, name string) {
fmt.Printf("Hello, %s", name)
}
Hello, Chris di_test.go:16: got '' want 'Hello, Chris'
テストは失敗します。名前は出力されますが、標準出力になることに注意してください。
テストでは、ライターを使用して挨拶をバッファに送信します。
fmt.Fprintf
はfmt.Printf
に似ていますが、代わりに Writer
を使用して文字列を送信しますが、fmt.Printf
のデフォルトはstdoutです。func Greet(writer *bytes.Buffer, name string) {
fmt.Fprintf(writer, "Hello, %s", name)
}
テストに合格しました。
以前のコンパイラーは、
bytes.Buffer
へのポインターを渡すように指示しました。これは技術的には正しいですが、あまり役に立ちません。これを実証するために、
Greet
関数を標準出力に出力するGoアプリケーションに接続してみてください。func main() {
Greet(os.Stdout, "Elodie")
}
./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
を渡すことができます。より汎用的なインターフェースを使用するようにコードを変更すると、テストとアプリケーションの両方で使用できるようになります。
package main
import (
"fmt"
"os"
"io"
)
func Greet(writer io.Writer, name string) {
fmt.Fprintf(writer, "Hello, %s", name)
}
func main() {
Greet(os.Stdout, "Elodie")
}
io.Writer
を使用してデータを書き込むことができる他の場所は何ですか? Greet
関数はどれほど一般的な目的ですか?以下を実行します
package main
import (
"fmt"
"io"
"net/http"
)
func Greet(writer io.Writer, name string) {
fmt.Fprintf(writer, "Hello, %s", name)
}
func MyGreeterHandler(w http.ResponseWriter, r *http.Request) {
Greet(w, "world")
}
func main() {
http.ListenAndServe(":5000", http.HandlerFunc(MyGreeterHandler))
}
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が必要なツールになるでしょう。
- コードをさまざまなコンテキストで再利用できるようにするコードを使用できる最初の「新しい」コンテキストは、テスト内です。しかし、さらに誰かがあなたの関数で何か新しいことを試したい場合、彼らは彼ら自身の依存関係を注入することができます。
モックについては後で詳しく説明します(そしてそれは悪ではありません)。 モックを使用して、実際に注入するものを、テストで制御および検査できる偽バージョンに置き換えます。 私たちの場合でも、標準ライブラリには、使用する準備ができています。
このように
io.Writer
インターフェースにある程度慣れていることで、テストでbytes.Buffer
を Writer
として使うことができ、標準ライブラリの他のWriter
を使ってコマンドラインアプリやウェブサーバで関数を使うことができます。標準ライブラリに慣れるほど、これらの汎用インターフェイスが表示され、独自のコードで再利用して、ソフトウェアをさまざまなコンテキストで再利用可能にすることができます。
最終更新 1yr ago