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,并且不正确地依赖于 Category、Author 和 Store,因为我们需要这些对象来构建 Book,然后将其放入测试上下文。
您是否会认为这是一种依赖关系,就像在服务单元测试中我们将依赖于服务将调用的存储库一样?
您将如何解决必须重新创建整个对象集群才能测试简单事物的问题?您将如何打破这种依赖关系并摆脱我们在测试中不需要的所有这些 Book 属性?通过使用模拟或存根?
如果你模拟存储库包含的东西,你会使用什么样的模拟/存根,而不是模拟被测对象与之交谈或消费的东西?