web-dev-qa-db-ja.com

TDD:密結合オブジェクトのモックアウト

時々、オブジェクトは密結合されている必要があります。たとえば、CsvFileクラスは、おそらくCsvRecordクラス(またはICsvRecordインターフェース)と緊密に連携する必要があります。

ただし、私が過去に学んだことから、テスト駆動開発の主な信条の1つは、「一度に複数のクラスをテストしない」です。つまり、ICsvRecordの実際のインスタンスではなく、CsvRecordモックまたはスタブを使用する必要があります。

しかし、このアプローチを試した後、CsvRecordクラスをモックアウトすると少し面倒になることに気づきました。これにより、2つの結論のうちの1つに導かれます。

  1. 単体テストを書くのは難しい!それはコードの匂いです!リファクタリング!
  2. すべての依存関係をモックアウトするのは不合理です。

モックを実際のCsvRecordインスタンスに置き換えたところ、状況はよりスムーズになりました。他の人の考えを探し回っているとき、偶然偶然に遭遇しました このブログ投稿 。これは上記の#2をサポートしているようです。自然に密結合されているオブジェクトの場合、モッキングについてそれほど心配する必要はありません。

私は道を外れていますか?上記の仮定#2にマイナス面はありますか?実際にデザインのリファクタリングを検討すべきですか?

10
Phil

これら2つのクラス間の調整が本当に必要な場合は、2つのクラスをカプセル化するCsvCoordinatorクラスを記述して、それをテストします。

ただし、CsvRecordは独立してテスト可能ではないという考えに異議を唱えます。 CsvRecordは基本的に [〜#〜] dto [〜#〜] クラスですが、違いますか?これはフィールドのコレクションであり、ヘルパーメソッドがいくつかある場合があります。また、CsvRecordCsvFile以外のコンテキストでも使用できます。たとえば、CsvRecordsのコレクションまたは配列を持つことができます。

最初にCsvRecordをテストします。すべてのテストに合格することを確認してください。次に、テスト中にCsvRecordCsvFileクラスで使用します。事前テスト済みのスタブ/モックとして使用してください。関連するテストデータを入力し、CsvFileに渡して、それに対するテストケースを記述します。

11
Robert Harvey

一度に1つのクラスをテストする理由は、1つのクラスのテストが2番目のクラスの動作に依存しないようにするためです。つまり、クラスAのテストでクラスBの機能のいずれかを実行する場合は、クラスBをモックして、クラスB内の特定の機能への依存関係を削除する必要があります。

CsvRecordのようなクラスは、主にデータを格納するためのものであるように見えます-それはそれ自体の機能が多すぎるクラスではありません。つまり、コンストラクタ、ゲッター、セッターはありますが、実際のロジックを持つメソッドはありません。もちろん、私はここで推測しています-おそらく、あなたは多数の複雑な計算を行うCsvRecordというクラスを書いたことでしょう。

しかし、CsvRecordに独自の論理がない場合、それをモックしても何も得られません。これは本当に古い格言です- "値オブジェクトをモックしないでください"

したがって、特定のクラスをモックするかどうかを検討するとき(別のクラスのテストの場合)、そのクラスが持つ独自のロジックの量と、テストの過程で実行されるそのロジックの量を考慮する必要があります。

5

いいえ。#2で結構です。物事は可能であり、-すべき概念が密結合されている場合は密結合されています。これはまれであり、通常は回避する必要がありますが、指定した例ではそれが理にかなっています。

1
Telastyn

ここには本当に2つの質問があります。 1つ目は、オブジェクトのモックが推奨されない状況が存在する場合です。他の優れた答えが示すように、それは間違いなく真実です。 2番目の質問は、特定のケースがそのような状況の1つであるかどうかです。その質問について、私は確信していません。

おそらく、クラスをモックしない最も一般的な理由は、それが値クラスであるかどうかです。ただし、ルールの背後にある理由を確認する必要があります。モックされたクラスがどういうわけか悪くなるからではなく、本質的に元のクラスと同じになるからです。その場合は、元のクラスを使用してユニットテストを行うのは簡単ではありません。

コードがリファクタリングが役に立たないまれな例外の1つである可能性は非常に高いですが、そのような宣言は、入念なリファクタリングの努力がうまくいかなかった場合にのみ行う必要があります。熟練した開発者でさえ、自分のデザインの代替案を見つけるのに苦労する可能性があります。あなたがそれを改善するための可能な方法を考えることができない場合は、経験のある人にそれを再検討するよう依頼してください。

ほとんどの人は、あなたのCsvRecordが値クラスであると想定しているようです。一つ作ってみてください。可能であれば不変にします。相互にポインタを持つ2つのオブジェクトがある場合は、それらの1つを削除して、それを機能させる方法を理解します。クラスと関数を分割する場所を探します。クラスを分割するのに最適な場所は、ファイルの物理的なレイアウトと常に一致するとは限りません。クラスの親子関係を逆にしてみてください。多分あなたはcsvファイルを読み書きするための別のクラスが必要です。おそらく、ファイルI/Oと上位層へのインターフェイスを処理するために、個別のクラスが必要になるでしょう。リファクタリング不可能と宣言する前に試すことはたくさんあります。

0
Karl Bielefeldt

「結合された」クラスは相互に相互に依存しています。これはあなたが説明していることでは当てはまりません-CsvRecordはそれを含むCsvFileを本当に気にする必要はないので、依存関係は一方向に過ぎません。それは結構です、そしてnot密結合です。

結局のところ、クラスに可変のString名が含まれている場合、それがStringと密結合しているとは主張しないでしょうか。

したがって、CsvRecordの単体テストを実行して、目的の動作を確認します。

次に、モッキングフレームワーク(Mockitoはすばらしい)を使用して、ユニットが依存するオブジェクトと正しく相互作用しているかどうかをテストします。実際にテストしたい動作は、CsvFileがCsvRcordsを期待どおりに処理することです。 CvsRecordの内部の仕組みは重要ではありません。CvsFileがそれと通信する方法です。

最後に、TDDはonly単体テストではありません。より大きなコンポーネントがどのように機能するか(つまり、ユーザーストーリーまたはシナリオ)の機能的な動作を調べる機能テストから始めることは確かに可能です(また、そうすべきです)。単体テストで期待値を設定し、部分を検証します。機能テストは全体に対して同じことを行います。

0
Matthew Flynn