5

私は、テストに適したコードを書くためのベスト プラクティスを見つけようとしていますが、より具体的には、オブジェクトの構築に関連するプラクティスを見つけようとしています。ブルーブックでは、エンティティや値オブジェクトなどの破損を回避するために、オブジェクトを作成するときに不変条件を適用する必要があることを発見しました。これに従うと、次のようなコードを書くことになります。

class Car
{
   //Constructor
   public Car(Door door, Engine engine, Wheel wheel)
   {
      Contract.Requires(door).IsNotNull("Door is required");
      Contract.Requires(engine).IsNotNull("Engine is required");
      Contract.Requires(wheel).IsNotNull("Wheel is required");
      ....
   }
   ...
   public void StartEngine()
   {
      this.engine.Start();
   }
}

まあ、これは一見良さそうですよね?オブジェクトが作成されるたびにCar、オブジェクトが「有効」であることを確認できるように、必要なコントラクトを公開する安全なクラスを構築しているようです。

では、テスト主導の観点からこの例を見てみましょう。

テストに適したコードを作成したいのですが、オブジェクトを分離してテストできるようにするには、Carオブジェクトを作成するためだけに、依存関係ごとにスタブまたはダミー オブジェクトのモックを作成する必要があります。メソッドのように、これらの依存関係の 1 つだけを使用するStartEngineメソッド。Misko Hevery のテスト哲学に従って、コンストラクターに null 参照を渡すだけの Door または Wheel オブジェクトを気にしないことを明示的に指定するテストを書きたいと思いますが、null をチェックしているので、それを行うことはできません。

これはほんの小さなコードですが、実際のアプリケーションに直面すると、サブジェクトの依存関係を解決する必要があるため、テストを書くのはますます難しくなります。

Misko は、コード内で null チェックを悪用するべきではないと提案しています (これは Design By Contract に反します)。それを行うと、テストを書くのが苦痛になります。別の方法として、彼は次のように述べています。私たちのコードは、どこにでもヌルチェックがあるという理由だけで安全です。」

これについてどう思いますか?どのようにしますか?ベストプラクティスは何ですか?

4

5 に答える 5

6

依存関係ごとにスタブまたはダミー オブジェクトのモックを作成する必要があります。

これは一般的に述べられています。しかし、それは間違っていると思います。aCarがオブジェクトに関連付けられている場合、クラスの単体テスト時に実際のEngineオブジェクトを使用してみませんか? EngineCar

しかし、それを行うと、コードの単体テスト行っていないことを誰かが宣言するでしょう。Carテストはクラスとクラスの両方に依存しますEngine: 2 つのユニットなので、ユニット テストではなく統合テストです。しかし、それらの人々もStringクラスを嘲笑しますか?それともHashSet<String>?もちろん違います。単体テストと統合テストの境界線はそれほど明確ではありません。

より哲学的に言えば、多くの場合、適切なモック オブジェクトを作成することはできません。その理由は、ほとんどのメソッドでは、オブジェクトが関連付けられたオブジェクトに委譲する方法がundefinedであるためです。デリゲートを行うかどうか、およびその方法は、実装の詳細としてコントラクトによって残されます。唯一の要件は、委任時に、メソッドがその委任の前提条件を満たしていることです。このような状況では、完全に機能する(非モック) デリゲートのみが対応します。実際のオブジェクトがその前提条件をチェックする場合、委任に関する前提条件を満たさないと、テストが失敗します。そして、そのテストの失敗をデバッグするのは簡単です。

于 2012-03-14T13:28:53.840 に答える
4

テスト データ ビルダの概念を見てください。

事前構成されたデータを使用してビルダーを一度作成し、必要に応じてプロパティをオーバーライドし、呼び出しBuild()てテスト対象のシステムの新しいインスタンスを取得します。

または、 Enterprise Libraryのソースを参照することもできます。ArrangeActAssertテストには、 BDD っぽいテストを適切にサポートするという基本クラスが含まれています。AAA から派生したクラスのメソッドでテスト セットアップを実装するArrangeと、特定のテストを実行するたびに呼び出されます。

于 2012-03-14T10:56:29.810 に答える
1

この種の作業を行うためのツールを検討する必要があります。AutoFixtureのように。基本的に、オブジェクトを作成します。簡単に聞こえるかもしれませんが、AutoFixture はここで必要なことを正確に行うことができます

MyClass sut = fixture.CreateAnnonymous<MyClass>();

MyClassコンストラクターのパラメーター、プロパティなどのダミー値を使用して作成されます (これらは のようなデフォルト値nullではなく、実際のインスタンスになることに注意してください。ただし、同じものになります。存在する必要がある偽の無関係な値です)。

編集:導入を少し拡張するには...

AutoFixure also comes with AutoMoq extension to become full-blown auto-mocking container. When AutoFixture fails to create an object (namely, interface or abstract class), it delegates creation to Moq - which will create mocks instead.

So, if you had class with constructor signature like this:

public ComplexType(IDependency d, ICollaborator c, IProvider p)

Your test setup in scenario when you don't care about any dependencies and want just nulls, would consist entirely of 2 lines of code:

var fixture = new Fixture().Customize(new AutoMoqCustomization());
var testedClass = fixture.CreateAnonymous<ComplexType>();

That's all there is. The testedClass will be created with mocks generated by Moq under the hood. Note that testedClass is not a mock - it's real object you can test just as if you have created it with constructor.

それはさらに良くなります。いくつかのモックを AutoFixture-Moq によって動的に作成したいが、他のいくつかのモックをより制御したい場合はどうでしょうか。特定のテストで検証するには?必要なのは、追加の 1 行のコードだけです

var fixture = new Fixture().Customize(new AutoMoqCustomization());
var collaboratorMock = fixture.Freeze<Mock<ICollaborator>>();
var testedClass = fixture.CreateAnonymous<ComplexType>();

ICollaborator完全にアクセスできるモックになり、実行でき.Setup.Verify関連するすべてのものになります。AutoFixture を試してみることを強くお勧めします。これは素晴らしいライブラリです。

于 2012-03-14T11:28:16.570 に答える
1

単体テストでこの問題を解決します。

車のテストクラスは次のようになります。

public sealed class CarTest
{
   public Door Door { get; set; }
   public Engine Engine { get; set; }
   public Wheel Wheel { get; set; }

   //...

   [SetUp]
   public void Setup()
   {
      this.Door = MockRepository.GenerateStub<Door>();
      //...
   }

   private Car Create()
   {
      return new Car(this.Door, this.Engine, this.Wheel);
   }
}

テスト メソッドでは、「興味深い」オブジェクトのみを指定する必要があります。

public void SomeTestUsingDoors()
{
   this.Door = MockRepository.GenerateMock<Door>();
   //... - setup door

   var car = this.Create();
   //... - do testing
}
于 2012-03-14T10:28:42.243 に答える
0

誰もが私に同意するわけではないことはわかっています (Mark Seemann が私に同意しないことはわかっています) が、コンストラクター インジェクションを使用してコンテナーによって作成された型に対して、コンストラクターで null チェックを行うことは通常ありません。2 つの理由から、まず第一に (場合によっては) テストが複雑になります (既にお気づきのとおり)。しかし、それ以外にも、コードにノイズが増えるだけです。すべての DI コンテナー (私が知っている) では、null 参照をコンストラクターに挿入することは許可されないため、いずれにしても発生しない何かのためにコードを複雑にする必要はありません。

もちろん、サービス タイプに対して null チェックアウトを残したので、これらのタイプは暗黙的に DI コンテナーの存在を認識するようになったと主張することもできますが、これは私のアプリケーションでは許容できるものです。再利用可能なフレームワークを設計する場合、もちろん事情は異なります。その場合、おそらくすべての null チェックが必要になります。

于 2012-03-14T11:42:57.757 に答える