XMLデータを生成した
os/exec.Command()
を使用してコマンドを実行しています。コマンドは、GetData()
という関数で実行されます。
GetData()
をテストするために、作成したtestdata
をいくつか持っています。私の
_test.go
には、GetData()
を呼び出すTestGetData
がありますが、os.exec
を使用しますが、代わりにtestdata
を使用します。これを達成するための良い方法は何ですか?
GetData
を呼び出すときは、「テスト」フラグモードを使用して、GetData(mode string)
というファイルを読み取る必要がありますか?
いくつかのこと
何かをテストするのが難しい場合、懸念の分離が正しくないことが原因であることがよくあります
コードに「テストモード」を追加しないでください。代わりに、依存関係の注入(DI)を使用して、依存関係をモデル化し、懸念事項を分離できるようにします。
私は勝手にコードがどのように見えるかを推測しました。
type Payload struct {Message string `xml:"message"`}func GetData() string {cmd := exec.Command("cat", "msg.xml")out, _ := cmd.StdoutPipe()var payload Payloaddecoder := xml.NewDecoder(out)// these 3 can return errors but I'm ignoring for brevitycmd.Start()decoder.Decode(&payload)cmd.Wait()return strings.ToUpper(payload.Message)}
プロセスへの外部コマンドを実行できるexec.Command
を使用します
出力をcmd.StdoutPipe
にキャプチャして、io.ReadCloser
を返します(これは重要になります)
コードの残りの部分は、優れたドキュメントから多かれ少なかれコピーして貼り付けたものです。
stdoutからの出力をキャプチャしてio.ReadCloser
に取り込み、次にコマンドをStart
してから、Wait
を呼び出してすべてのデータが読み取られるのを待ちます。これらの2つの呼び出しの間で、データをPayload
構造体にデコードします。
これが msg.xml
内に含まれているものです。
<payload><message>Happy New Year!</message></payload>
実際の動作を示す簡単なテストを作成しました。
func TestGetData(t *testing.T) {got := GetData()want := "HAPPY NEW YEAR!"if got != want {t.Errorf("got %q, want %q", got, want)}}
テスト可能なコードは分離され、単一の目的です。 私には、このコードには2つの主な懸念があるように感じます
未加工のXMLデータを取得する。
XMLデータをデコードし、ビジネスロジックを適用します(この場合は<message>
のstrings.ToUpper
です)
最初の部分は、サンプルを標準lib
からコピーすることです。
2番目の部分はビジネスロジックがある場所です。コードを見ると、ロジックの「シーム"seam"
」がどこから始まるかがわかります。ここで、io.ReadCloser
を取得します。この既存の抽象化を使用して、問題を分離し、コードをテスト可能にすることができます。
GetDataの問題は、ビジネスロジックがXMLを取得する手段と結合していることです。デザインを改善するには、それらを切り離す必要があります
私たちのTestGetData
は2つの懸念事項の間の統合テストとして機能することができるため、それが機能し続けることを確認するためにそれを保持します。
新しく分離されたコードは次のようになります
type Payload struct {Message string `xml:"message"`}func GetData(data io.Reader) string {var payload Payloadxml.NewDecoder(data).Decode(&payload)return strings.ToUpper(payload.Message)}func getXMLFromCommand() io.Reader {cmd := exec.Command("cat", "msg.xml")out, _ := cmd.StdoutPipe()cmd.Start()data, _ := ioutil.ReadAll(out)cmd.Wait()return bytes.NewReader(data)}func TestGetDataIntegration(t *testing.T) {got := GetData(getXMLFromCommand())want := "HAPPY NEW YEAR!"if got != want {t.Errorf("got %q, want %q", got, want)}}
GetData
が入力をio.Reader
から取得するようになったので、これをテスト可能にして、データの取得方法を考慮しなくなりました。人々はio.Reader
(これは非常に一般的です)を返すもので関数を再利用できます。
たとえば、コマンドラインではなくURLからXMLのフェッチを開始できます。
func TestGetData(t *testing.T) {input := strings.NewReader(`<payload><message>Cats are the best animal</message></payload>`)got := GetData(input)want := "CATS ARE THE BEST ANIMAL"if got != want {t.Errorf("got %q, want %q", got, want)}}
これはGetData
の単体テストの例です。
懸念を分離し、Goのテストで既存の抽象化を使用することで、重要なビジネスロジックが簡単になります。