web-dev-qa-db-ja.com

C#とRhinoMocksを使用したテスト駆動開発のベストプラクティス

私のチームがテスト可能なコードを書くのを助けるために、C#コードベースをよりテスト可能にするためのベストプラクティスのこの簡単なリストを思いつきました。 (いくつかのポイントは、C#のモックフレームワークであるRhino Mocksの制限に言及していますが、ルールはより一般的に適用される場合があります。)従うべきベストプラクティスはありますか?

コードのテスト容易性を最大化するには、次の規則に従います。

  1. 最初にテストを記述し、次にコードを記述します。理由:これにより、テスト可能なコードを記述し、コードのすべての行にテストが書き込まれます。

  2. 依存性注入を使用してクラスを設計します。理由:見えないものをモックまたはテストすることはできません。

  3. Model-View-ControllerまたはModel-View-Presenterを使用してUIコードを動作から分離します。理由:テストできない部分(UI)を最小化しながらビジネスロジックをテストできます。 。

  4. 静的メソッドまたは静的クラスを記述しないでください。理由:静的メソッドは分離が困難または不可能であり、Rhino Mocksはそれらをモックできません。

  5. クラスではなく、インターフェイスからプログラムします。理由:インターフェイスを使用すると、オブジェクト間の関係が明確になります。インターフェイスは、オブジェクトがその環境から必要とするサービスを定義する必要があります。また、Rhino Mocksやその他のモックフレームワークを使用して、インターフェイスを簡単にモックできます。

  6. 外部依存関係を分離します。理由:未解決の外部依存関係はテストできません。

  7. モックするメソッドを仮想としてマークします。理由:Rhino Mocksは非仮想メソッドをモックできません。

86
Kevin Albrecht

間違いなく良いリストです。以下にいくつかの考えを示します。

最初にテストを記述し、次にコードを記述します。

高レベルで同意します。しかし、私はもっと具体的に言う:「最初にテストを書いてから、テストに合格するのに十分なコードを書いて、繰り返します。」そうでなければ、ユニットテストが統合テストや受け入れテストのように見えることを恐れます。

依存性注入を使用してクラスを設計します。

同意した。オブジェクトが独自の依存関係を作成する場合、それらを制御することはできません。制御の反転/依存性注入により、その制御が可能になり、テスト中のオブジェクトをmocks/stubs/etcで分離できます。これは、オブジェクトを分離してテストする方法です。

Model-View-ControllerまたはModel-View-Presenterを使用して、その動作からUIコードを分離します。

同意した。プレゼンター/コントローラーでさえ、スタブ化/モック化されたビューとモデルを渡すことで、DI/IoCを使用してテストできることに注意してください。詳しくは Presenter First TDDをご覧ください。

静的メソッドまたはクラスを記述しないでください。

私はこれに同意するかどうかわかりません。モックを使用せずに静的メソッド/クラスを単体テストすることができます。したがって、おそらくこれはあなたが言及したRhino Mock固有のルールの1つです。

クラスではなく、インターフェイスからプログラムします。

同意しますが、理由は少し異なります。インターフェースは、さまざまなモックオブジェクトフレームワークをサポートするだけでなく、ソフトウェア開発者に大きな柔軟性を提供します。たとえば、インターフェイスなしでDIを適切にサポートすることはできません。

外部依存関係を分離します。

同意した。インターフェイスを使用して、独自のファサードまたはアダプター(必要に応じて)の背後に外部依存関係を非表示にします。これにより、Webサービス、キュー、データベースなど、外部の依存関係からソフトウェアを分離できます。これは特にチームが依存関係(外部とも呼ばれます)を制御していない場合に重要です。

モックするメソッドを仮想としてマークします。

これはRhino Mocksの制限です。モックオブジェクトフレームワークよりもハンドコードされたスタブを好む環境では、それは必要ありません。

そして、考慮すべきいくつかの新しいポイント:

創造的なデザインパターンを使用します。 これはDIを支援しますが、そのコードを分離して、他のロジックから独立してテストすることもできます。

Bill WakeのArrange/Act/Assertテクニック を使用してテストを記述します。 この手法により、必要な構成、実際にテストされているもの、期待されるものが非常に明確になります。

自分のモック/スタブを転がすことを恐れないでください。 多くの場合、モックオブジェクトフレームワークを使用すると、テストが非常に読みにくくなることがわかります。独自にローリングすることで、モック/スタブを完全に制御でき、テストを読みやすく保つことができます。 (前のポイントに戻って参照してください。)

ユニットテストの重複を抽象基本クラスまたはsetup/teardownメソッドにリファクタリングする誘惑を避けます。 そうすることで、ユニットテストを理解しようとする開発者から構成/クリーンアップコードが隠されます。この場合、個々のテストの明確さは、重複をリファクタリングするよりも重要です。

継続的インテグレーションを実装します。 すべての「緑色のバー」でコードをチェックインします。ソフトウェアをビルドし、チェックインごとに単体テストの完全なスイートを実行します。 (確かに、これはそれ自体コーディングの慣行ではありませんが、ソフトウェアをクリーンで完全に統合した状態に保つための信じられないほどのツールです。)

58
aridlehoover

.Net 3.5を使用している場合は、 Moq モックライブラリを調べてください。式ツリーとラムダを使用して、他のほとんどのモックライブラリの非直感的なレコード応答イディオムを削除します。

これを確認してください quickstart テストケースがどれほど直感的になるかを確認するには、簡単な例を示します:

// ShouldExpectMethodCallWithVariable
int value = 5;
var mock = new Mock<IFoo>();

mock.Expect(x => x.Duplicate(value)).Returns(() => value * 2);

Assert.AreEqual(value * 2, mock.Object.Duplicate(value));
10
zadam

フェイク、モック、スタブ とそれぞれを使用するタイミングの違いを理解します。

モックを使用して相互作用を過剰に指定しないでください。これはテストを行います 脆性

6
Hamish Smith

これは非常に役立つ投稿です!

コンテキストとテスト対象システム(SUT)を理解することは常に重要であると付け加えます。既存のコードが同じプリンシパルに従う環境で新しいコードを記述する場合、レターにTDDプリンシパルに従うことははるかに簡単です。しかし、TDD以外のレガシー環境で新しいコードを作成している場合、TDDの取り組みは予想や予想をはるかに超えて急速に膨れ上がることがあります。

完全にアカデミックな世界に住んでいる一部の人にとっては、スケジュールと配信は重要ではないかもしれませんが、ソフトウェアがお金のある環境では、TDDの努力を効果的に活用することが重要です。

TDDは、 Diminishing Marginal Return の法則の対象となります。要するに、TDDに向けた努力は、最大のリターンに達するまでますます貴重になり、その後、TDDに費やされる時間はますます価値を失います。

私は、TDDの主な価値は境界(ブラックボックス)にあるだけでなく、システムのミッションクリティカルな領域の時折のホワイトボックステストにもあると考えがちです。

3
user101964

インターフェースに対するプログラミングの本当の理由は、Rhinoを楽にするためではなく、コード内のオブジェクト間の関係を明確にするためです。インターフェイスは、オブジェクトが環境から必要とするサービスを定義する必要があります。クラスは、そのサービスの特定の実装を提供します。 Rebecca Wirfs-Brockの役割、責任、協力者に関する「オブジェクトデザイン」の本を読んでください。

2
Steve Freeman

良いリスト。あなたが確立したいと思うかもしれないことの一つ-そして私はそれについて自分で考え始めているので、私はあなたに多くのアドバイスを与えることができません-クラスが異なるライブラリ、名前空間、ネストされた名前空間にあるべきときです。事前にライブラリとネームスペースのリストを把握し、チームが2つをマージして新しいものを追加することを決定するように命じることもできます。

ああ、私もあなたがしたいと思うかもしれないことを考えました。通常、クラスポリシーごとにテストフィクスチャを備えたユニットテストライブラリがあり、各テストは対応するネームスペースに入ります。私はまた、より多くの BDDスタイル であるテストの別のライブラリ(統合テスト?)を持つ傾向があります。これにより、メソッドが実行すべきこととアプリケーションが全体的に実行すべきことを指定するテストを作成できます。

1
George Mauer

ここに、私がやりたいと思った別のものがあります。

TestDriven.NetやNAntではなく単体テストGuiからテストを実行する場合、単体テストプロジェクトタイプをライブラリではなくコンソールアプリケーションに設定する方が簡単であることがわかりました。これにより、テストを手動で実行し、デバッグモードでテストを実行できます(前述のTestDriven.Netが実際に実行できます)。

また、慣れていないコードやアイデアをテストするためにPlaygroundプロジェクトを常に開いておくのが好きです。これをソース管理にチェックインしないでください。さらに良いのは、開発者のマシン上にある別のソース管理リポジトリ内にあることです。

0
George Mauer