10

DDD では、ドメイン オブジェクトを常に有効な状態にすることをお勧めします。集約ルートは、オブジェクトが有効な状態で初期化されるように、必要なすべての部分でオブジェクトを組み立てるための不変条件とファクトリを保証する責任があります。

ただし、これにより、単純で分離された単体テストを作成するタスクが非常に複雑になるようです。

Books を含む BookRepository があるとします。本には次のものがあります。

  • 著者
  • カテゴリー
  • その本を見つけることができる書店のリスト

これらは必須の属性です。書籍には、著者、カテゴリ、および少なくとも書籍を購入できる書店が必要です。これは非常に複雑なオブジェクトであるため、BookFactory が存在する可能性が高く、Factory は少なくとも前述のすべての属性で Book を初期化します。おそらく、Book コンストラクターを非公開 (および入れ子になった Factory) にして、Factory 以外の誰も空の Book をインスタンス化できないようにします。

次に、すべての本を返す BookRepository のメソッドを単体テストします。メソッドが本を返すかどうかをテストするには、一部の本がすでにリポジトリにあるテスト コンテキスト (AAA 用語でのアレンジ ステップ) を設定する必要があります。

C# の場合:

[Test]
public void GetAllBooks_Returns_All_Books() 
{
    //Lengthy and messy Arrange section
    BookRepository bookRepository = new BookRepository();
    Author evans = new Author("Evans", "Eric");
    BookCategory category = new BookCategory("Software Development");
    Address address = new Address("55 Plumtree Road");
    BookStore bookStore = BookStoreFactory.Create("The Plum Bookshop", address);
    IList<BookStore> bookstores = new List<BookStore>() { bookStore };
    Book domainDrivenDesign = BookFactory.Create("Domain Driven Design", evans, category, bookstores);
    Book otherBook = BookFactory.Create("other book", evans, category, bookstores);
    bookRepository.Add(domainDrivenDesign);
    bookRepository.Add(otherBook);

    IList<Book> returnedBooks = bookRepository.GetAllBooks();

    Assert.AreEqual(2, returnedBooks.Count);
    Assert.Contains(domainDrivenDesign, returnedBooks);
    Assert.Contains(otherBook, returnedBooks);
}

Book オブジェクトを作成するために自由に使用できる唯一のツールが Factory であることを考えると、単体テストは Factory を使用し、これらのオブジェクトに間接的に依存しています。テストコンテキスト。

これは、サービスの単体テストで、たとえばサービスが呼び出すリポジトリに依存するのと同じように、依存関係であると考えますか?

単純なことをテストできるようにするために、オブジェクトのクラスター全体を再作成しなければならないという問題をどのように解決しますか? この依存関係を壊して、テストで不要な Book 属性をすべて取り除くにはどうすればよいでしょうか? モックまたはスタブを使用して?

リポジトリに含まれるものをモックアップする場合、テスト対象のオブジェクトが通信または消費するものをモックアップするときとは対照的に、どの種類のモック/スタブを使用しますか?

4

7 に答える 7

4

2つのこと:

  • テスト内でモック オブジェクトを使用します。現在、具体的なオブジェクトを使用しています。

  • 複雑な設定に関しては、ある時点で有効な書籍が必要になります。このロジックをセットアップ メソッドに抽出して、各テストの前に実行します。そのセットアップ方法で、書籍などの有効なコレクションを作成します。

「単純なことをテストできるようにするために、オブジェクトのクラスター全体を再作成しなければならないという問題をどのように解決しますか?どのようにその依存関係を壊し、私たちのアプリケーションで必要のないこれらすべての Book 属性を取り除きますか?テスト?モックまたはスタブを使用して?"

モック オブジェクトを使用すると、これを行うことができます。テストで有効な著者の本のみが必要な場合、モック オブジェクトはその著者を指定し、他の属性はデフォルトになります。テストは有効な作成者のみを対象としているため、他の属性を設定する必要はありません。

于 2010-05-14T12:45:34.960 に答える
3

純粋な単体テストの場合、モックとスタブは間違いなくソリューションです。しかし、より統合レベルのテストを行っており、モック (またはスタブなど) では問題を解決できないため、実際には 2 つの合理的な選択肢があります。

  • 必要なデータのセットアップに役立つテスト ファクトリを作成します。これらはおそらくテスト固有のもので、書店を構築するだけでなく、合理的なセットアップ本をそこに投入します。そうすれば、セットアップ コードを 1 ~ 2 行に圧縮して、他のテストに使用できます。このコードは、統合タイプのテストに必要なさまざまなシナリオを作成するために成長する可能性があります。

  • セットアップテスト フィクスチャを作成します。これらは小さいですが、テストで使用する概念的には完全なデータ セットです。これらは通常、ある種のシリアル化された形式 (xml、csv、sql) で保存され、各テストの開始時にデータベースに読み込まれるため、有効な状態になります。それらは実際には、静的ファイルを読み取ることによって機能する単なる一般的なファクトリです。

フィクスチャを使用する場合は、単一または複数のフィクスチャ アプローチを使用できます。ほとんどの単体テストで単一の「標準的な」データ セットを使用できる場合、それはより簡単になりますが、場合によっては、レコードが多すぎて理解できない、または単に範囲を表現しないデータ セットが作成されることがあります。サポートする必要があるシナリオの。一部の問題では、複数のデータ セットを徹底的にテストする必要があります。

于 2010-05-17T15:00:41.137 に答える
2

Finglas さん、回答ありがとうございます。私は他のテストでもモックを使用しますが、主に対話テスト用であり、テスト コンテキストのセットアップ用ではありません。必要な値だけを持つこの種の中空オブジェクトをモックと呼ぶことができるかどうか、およびそれらを使用するのが良い考えであるかどうかはわかりませんでした。

Gerard Meszaros の xunitpatterns.com で、問題にかなり近い興味深いものを見つけました。彼は、長くて複雑なテスト セットアップを持つコードの匂いをIrrelevant Informationとして説明しており、可能な解決策はCreation MethodsまたはDummy Objectsです。ただし、彼のダミー オブジェクトの実装に完全に納得しているわけではありません。私の例では、非常に単純なコンストラクターを使用してダミーの Book を実装し、すべての Factory 作成ロジックをバイパスするために、IBook インターフェイス (うーん) が必要になるからです。

分離フレームワークで生成されたモックと作成方法を組み合わせることで、テストを明確にして単純化することができると思います。

于 2010-05-14T15:13:57.030 に答える
1

Test Data Builderを試してみてください。Nat Pryce からの素敵な投稿。

これは、モックの道を進みたくない場合に役立ちます。これらの醜いファクトリメソッドをすべて抽象化できます。また、本番コードで使用するビルダーをプッシュすることもできます。

于 2010-05-14T16:38:49.913 に答える
1

おそらく、Book コンストラクターを非公開 (および入れ子になった Factory) にして、Factory 以外の誰も空の Book をインスタンス化できないようにします。

プライベートBookコンストラクターが問題の原因です。

代わりにBookのコンストラクターを internal にすると、ファクトリをネストする必要がなくなります。その後、自由にファクトリにインターフェースを実装させ ( IBookFactory)、モック ブック ファクトリをリポジトリに挿入できます。

ブック ファクトリの実装のみがインスタンスを作成することを本当に確実にしたい場合は、ファクトリが必要とする引数を受け入れるメソッドをリポジトリに追加します。

public class BookRepository {

    public IBookFactory bookFactory;

    public BookRepository(IBookFactory bookFactory) {
        this.bookFactory = bookFactory;
    }

    // Abbreviated list of arguments
    public void AddNew(string title, Author author, BookStore bookStore) {
        this.Add(bookFactory.Create(title, author, bookStore));
    }

}
于 2010-05-17T15:05:31.940 に答える
0

私はCQRSと一緒にDDDを学び始めたので、IIは偏っているかもしれません。しかし、あなたが正しい境界線を描くかどうかはわかりません。アグリゲートは、その不変条件についてのみ知っている必要があります。あなたは本に著者がいると言います。はい、しかし本は著者の名前に不変のものを持っていません。したがって、集約された本を次のように描くことができます。

 public class Book
 {
     public Guid _idAuthor;

     public Book(Guid idAuthor)
     {
         if(idAuthor==guid.empty) throw new ArgumentNullException();

         _idAuthor = idAuthor;
     }
 }

一方、作者は作者に不変条件を持っています:

 public class Author
 {
     public string _name;

     public Book(string name)
     {
         if(name==nullorEmpty) throw new ArgumentNullException();

         _name= name;
     }
 }

クエリ側では、インフォメーションブック名と作成者名の両方が必要になる場合がありますが、これはクエリであり、IMOの単体テストには適していない可能性があります。

ライブラリに追加できるようにする必要がある場合は、作成者が「e」の文字を含む場合にのみ予約してください。全体の議論は異なりますが、私が理解したこととは異なり、今は必要ありません。

アグリゲートブックを作成するときは、書き込み側と真の不変条件に焦点を合わせるため、単体テストが簡単になります。

于 2013-01-02T15:06:48.167 に答える