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


10

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

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

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

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

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

私は道を外れていますか?上記の仮定2の欠点はありますか?デザインをリファクタリングすることを実際に考えるべきですか?


1
「ユニットテスト」の「ユニット」は必ず1つのクラスでなければならないというのはよくある誤解だと思います。あなたの例は、これらの2つのクラスが1つのユニットを形成するほうがよい場合を示していると思います。しかし誤解しないでください、私はロバート・ハーベイの答えに完全に同意します。
Doc Brown

回答:


11

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

しかし、私CsvRecordは独立してテストできないという概念に異議を唱えています。 CsvRecord基本的にDTOクラスですが、そうではありませんか?これはフィールドのコレクションであり、ヘルパーメソッドがいくつかある場合があります。またCsvRecord、他のコンテキストでも使用できますCsvFileCsvRecordたとえば、sのコレクションまたは配列を持つことができます。

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


1
はい、CsvRecordは確実に独立してテスト可能です。問題は、CsvRecordで何かが壊れると、CsvDataテストが失敗することです。しかし、それは大きな問題ではないと思います。
Phil

1
あなたはそれが起こることを望んでいると思います。:)
ロバートハーベイ

1
@RobertHarvey:理論的には、CsvRecordとCsvFileが非常に複雑なクラスになると問題になる可能性があり、CsvFileのテストが中断した場合、CsvFileまたはCsvRecordの問題であるかどうかはすぐにはわかりません。しかし、それはもっと仮説的なケースだと思います。もし私がそのようなクラスを実際のプログラムのためにプログラミングするタスクを持っていたなら、私はあなたがそれを説明するとおりにそれをします。
Doc Brown、

2
@Phil:CsvRecord壊れた場合、明らかにCsvData失敗します。しかし、これは問題ありません。CsvRecord最初にテストし、それが失敗した場合、CsvFileテストは無意味です。あなたはまだでエラーを区別することができますCsvRecordし、でCsvFile
tdammers

5

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

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

しかしCsvRecord、それ自体の本当の論理がない場合、それをあざけることによって得られるものは何もありません。これは実際には古い格言です- 「値オブジェクトをモックしないでください」

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


+1。結果が複数のオブジェクトの動作の正確さに依存するテストは、単体テストではなく統合テストです。実際の単体テストを行うには、これらのオブジェクトの1つをモックアウトする必要があります。ただし、実際の動作がないオブジェクトには適用されません。たとえば、getterとsetterのみを使用します。
guillaume31

1

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


0

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

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

そのため、CsvRecordを単体テストして、目的の動作を確認します。

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

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


1
-1、密結合は必ずしも循環依存関係を意味するわけではありません。それは誤解です。この例でCsvFile 密に結合されていますCsvRecord(ただし逆ではありません)。OPは、CsvFileCsvRecord介してそれを分離することによってテストするのが良いアイデアであるかどうかを尋ねICsvRecordます。
Doc Brown

2
@DocBrown:結合が緊密であるかどうかはCsvFile、の内部動作CsvRecord、つまりファイルがレコードについて持っている仮定の量にどの程度依存するかによって決まります。インターフェースはそのような仮定(または他の仮定がないこと)を文書化して適用するのに役立ちますが、カップリングの量は同じですが、インターフェースを使用すると、異なるレコードクラスをにフックできますCsvFile。結合を減らしたと言えるようにインターフェースを導入するのはばかげています。
tdammers 2012

0

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

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

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

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

弊社のサイトを使用することにより、あなたは弊社のクッキーポリシーおよびプライバシーポリシーを読み、理解したものとみなされます。
Licensed under cc by-sa 3.0 with attribution required.