然而,这似乎使创建简单,孤立的单元测试的任务复杂化.
假设我们有一个包含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",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属性?通过使用模拟或存根?
如果你模拟了Repository包含的东西,你会使用什么样的模拟/存根,而不是当你模拟测试中的对象与之对话或消费时?
>在测试中使用模拟对象.您目前正在使用具体对象.
>关于复杂的设置,在某些时候你需要一些有效的书籍.将此逻辑提取到设置方法,以在每次测试之前运行.让该设置方法创建有效的书籍集合等等.
“How would you solve the problem of
having to re-create a whole cluster of
objects in order to be able to test a
simple thing ? How would you break
that dependency and get rid of all
these Book attributes we don’t need in
our test ? By using mocks or stubs ?”
模拟对象可以让你这样做.如果测试只需要一本有效作者的书,那么您的模拟对象将指定该作者,其他属性将被默认.由于您的测试只关心有效的作者,因此无需设置其他属性.