单元测试:带有导航属性的POCO对象的TDD(关系修正)

Sil*_*Fox 6 c# tdd nunit entity-framework poco

我一直试图找到一个很好的解决方案,但没有运气,所以要么我不是在寻找合适的关键字,要么我们从一开始就做错了所以问题不应该存在.

更新澄清:我希望这可以作为单元测试而不是集成测试,所以我不希望这个命中数据库,但是我想模拟EF在单元测试中持续更改时所做的关联.

原始问题:

假设您正在测试这样的服务方法:

[Test]
public void AssignAuthorToBook_NewBookNewAuthor_SuccessfullyAssigned()
{
  IBookService service = new BookService();

  var book = new Book();

  var Author = new Author() { Id = 123 };

  service.AssignAuthorToBook(book, author);

  Assert.AreEqual(book.AuthorId, 123);
}
Run Code Online (Sandbox Code Playgroud)

现在让我们说这个测试失败了,因为AssignAuthorToBook实际上使用代码工作book.Author = author;所以它没有分配它AuthorId,它正在分配实体.当SaveChanges()在上下文中使用Entity Framework 方法持久保存时,它将关联实体,ID将关联.但是,在上面的示例中,不会应用实体框架的逻辑.我所说的是代码将SaveChanges()在调用后工作,但单元测试将失败.

在这个简单的例子中,您可能会立即知道为什么测试失败,因为您刚刚在代码之前编写了测试并且可以轻松修复它.但是,对于更复杂的操作以及可能改变实体关联方式的未来更改的操作,这将破坏测试但可能不会破坏功能,如何最好地接近单元测试?

我的想法是:

  • 服务层应该不知道持久层 - 我们应该在单元测试中模拟数据上下文来模拟它的工作方式吗?是否有一种简单的方法可以自动绑定关联(即如果Id使用则分配给正确的实体,或者如果使用实体则分配正确Id的实体)?
  • 或者测试的结构应该略有不同?

我在上面的例子中继承了当前项目中存在的测试,但是我发现这个方法存在问题并且我没有设法找到可能常见问题的简单解决方案.我相信应该嘲笑数据上下文,但这似乎需要将大量代码添加到模拟中以动态创建关联 - 当然这已经解决了?

更新:这是我到目前为止找到的最接近的答案,但它们并不是我所追求的.我不想这样测试EF,我只是想知道什么是测试访问存储库的服务方法的最佳实践(直接或通过导航属性通过共享相同上下文的其他存储库).

我如何模拟实体框架的导航财产情报?

模拟的datacontext和外键/导航属性

实体框架4.1的假DbContext测试

使用ADO.NET Mocking Context Generator时未设置导航属性

到目前为止的结论:使用单元测试是不可能的,并且只能使用与真实数据库的集成测试.您可以接近并可能编写一些动态关联导航属性的代码,但您的模拟数据上下文永远不会完全复制真实的上下文.如果任何解决方案使我能够自动关联导航属性,我会很高兴,这将使我的单元测试更好,如果不是完美的(成功的单元测试的性质无论如何都不保证功能)ADO.NET模拟上下文生成器接近,但似乎我必须有一个模拟版本的每个实体,如果在我的实现中使用部分类添加功能,它将不适合我.

Rui*_*Rui 5

我认为你期望你的测试结果意味着使用了几个依赖关系,可以说不是将它作为一个单元测试,特别是因为隐含了对EF的依赖.

这里的想法是,如果您承认您的BookService依赖于EF,您应该使用模拟来断言它与它正确交互,不幸的是EF似乎不喜欢被模拟,所以我们总是把它放在一个存储库下,这是一个如何使用Moq编写测试的示例:

[Test]
public void AssignAuthorToBook_NewBookNewAuthor_CreatesNewBookAndAuthorAndAssociatesThem()
{
  var bookRepositoryMock = new Mock<IBookRepository>(MockBehavior.Loose);
  IBookService service = new BookService(bookRepositoryMock.Object);
  var book = new Book() {Id = 0}
  var author = new Author() {Id = 0}; 

  service.AssignAuthorToBook(book, author);

  bookRepositoryMock.Verify(repo => repo.AddNewBook(book));
  bookRepositoryMock.Verify(repo => repo.AddNewAuthor(author));
  bookRepositoryMock.Verfify(repo => repo.AssignAuthorToBook(book, author));
}
Run Code Online (Sandbox Code Playgroud)

设置的id是你要使用集成测试的东西,但是我认为你不应该担心EF没有设置Id,我说这是因为你不应该担心测试是否.net框架做了它应该做的事情.

我在过去写过关于交互测试的文章(我认为这是在这种情况下正确的方法,你正在测试BookService和Repository之间的交互),希望它有所帮助:http://blinkingcaret.wordpress.com/2012/11/20 /相互作用测试-假货-嘲笑和存根/


Joe*_*eon 1

这永远不会像你现有的那样起作用。数据上下文封装在服务层的幕后,并且 BookID 的关联永远不会在本地变量上更新。

当我使用 EF 对某些内容进行 TDD 时,我通常将所有 EF 逻辑包装到某种 DAO 中,并为实体提供 CRUD 方法。

然后你可以做这样的事情:

[Test]
public void AssignAuthorToBook_NewBookNewAuthor_SuccessfullyAssigned()
{
  IBookService service = new BookService();

  var book = new Book();
  var bookID = 122;
  book.ID = bookId;
  var Author = new Author() { Id = 123 };

  service.AssignAuthorToBook(book, author);

//ask the service for the book, which uses EF to get the book and populate the navigational properties, etc...
  book = service.GetBook(bookID)

  Assert.AreEqual(book.AuthorId, 123);
}
Run Code Online (Sandbox Code Playgroud)