如何保持单元测试简单和隔离,并仍然保证DDD不变量?

gui*_*e31 9 unit-testing domain-driven-design mocking stub ddd-repositories

DDD建议域对象随时都应处于有效状态.聚合根负责保证不变量和工厂用于组装具有所有必需部分的对象,以便它们在有效状态下初始化.

然而,这似乎使创建简单,隔离的单元测试的任务变得复杂化.

假设我们有一个包含Books的BookRepository.一本书有:

  • 一位作家
  • 一个类别
  • 您可以在其中找到该书的书店列表

这些是必需的属性:一本书必须有一个作者,一个类别和至少一个书店,你可以从中购买这本书.可能有一个BookFactory,因为它是一个非常复杂的对象,而Factory将至少用所有提到的属性初始化Book.也许我们还会将Book构造函数设置为私有(以及Factory嵌套),这样除了Factory之外没有人可以实例化一个空的Book.

现在我们要对BookRepository的一个方法进行单元测试,该方法返回所有的Books.为了测试该方法是否返回书籍,我们必须设置一个测试上下文(AAA术语中的Arrange步骤),其中一些Books已经存储在Repository中.

在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);
}
Run Code Online (Sandbox Code Playgroud)

鉴于我们可以使用的唯一工具来创建Book对象,单元测试现在使用并依赖于Factory并且仅依赖于Category,Author和Store,因为我们需要这些对象来构建Book然后将它放入测试环境.

您是否会认为这是一种依赖,就像在服务单元测试中我们将依赖于服务所调用的存储库一样?

您如何解决重新创建整个对象集群以便能够测试简单事物的问题?你如何打破这种依赖性并摆脱我们在测试中不需要的所有这些Book属性?通过使用模拟或存根?

如果你模拟了一个Repository 包含的东西,你会使用什么样的模拟/存根,而不是当你模拟测试中的对象与之对话消费时

Fin*_*las 4

两件事情:

  • 在测试中使用模拟对象。您当前正在使用具体对象。

  • 对于复杂的设置,有时您将需要一些有效的书籍。将此逻辑提取到设置方法中,以便在每次测试之前运行。让该设置方法创建有效的书籍集合等等。

“您将如何解决必须重新创建整个对象集群以便能够测试简单事物的问题?您将如何打破这种依赖性并摆脱我们在我们的应用程序中不需要的所有这些 Book 属性测试?通过使用模拟或存根?”

模拟对象可以让你做到这一点。如果测试只需要一本具有有效作者的书,您的模拟对象将指定该作者,其他属性将被默认。由于您的测试只关心有效的作者,因此无需设置其他属性。