【问题标题】:How can I isolate a data source such as DbSet?如何隔离 DbSet 等数据源?
【发布时间】:2014-07-07 18:43:07
【问题描述】:

我正在测试许多控制器,每个控制器都依赖于存储库。这就是我在每个测试夹具的情况下提供模拟存储库的方式:

[SetUp]
public void SetUp()
{
    var repository = RepositoryMockHelper.MockRepository();
    controller = new HomeController(repository.Object);
}

这里是MockRepository 辅助方法,可以很好地衡量:

internal static Mock<IRepository> MockRepository()
{
    var repository = new Mock<IRepository>();

    var posts = new InMemoryDbSet<Post>()
    {
        new Post {
            ...
        },
        ...
    };

    repository.Setup(db => db.Posts).Returns(posts);

    return repository;
}
... = code removed for the sake of brevity. 

我的意图是为每个测试使用InMemoryDbSet 的新实例。我认为使用SetUp 属性可以实现这一点,但显然不是。

当我运行所有测试时,结果不一致,因为测试似乎不是孤立的。例如,一个测试将从数据存储中删除一个元素并断言计数已减少,但根据测试运行者的想法,另一个测试可能会增加计数,导致两个测试都失败。

我是否以正确的方式进行这些测试?我该如何解决这个问题?

【问题讨论】:

  • 您使用的是哪个测试框架/运行器?
  • 好问题。我更新了标签。我正在使用 NUnit。

标签: c# unit-testing mocking tdd nunit


【解决方案1】:

您引用的用于InMemoryDataSet 的包使用static backing data 结构,因此将在测试运行中持续存在。这就是为什么您会看到不一致的行为。您可以通过使用另一个包(正如您提到的)来解决这个问题,或者在每个测试中将新的 HashSet 传递给 the constructor,这样它就不会使用静态成员。

至于您的其余问题,我认为您正在顺利完成测试。唯一的问题是,由于您的所有测试都具有相同的设置(在您的 SetUp 方法中),它们可能会相互影响。即,如果您的测试依赖于集合中没有 Foo 对象而一个至少需要一个对象,那么您要么在 SetUp 中添加一个对象,然后在不需要的测试中将其删除需要它,反之亦然。正如@BillSambrone 提到的那样,有特定的设置程序可能会更清楚。

【讨论】:

  • 最初是 @BillSambrone 帮助 me 解决了这个问题,但我觉得这个答案对遇到类似问题的人最有用,因此将其标记为接受的答案。
【解决方案2】:

正如@PatrickQuirk 指出的那样,我认为您的问题是由于InMemoryDbSet 在幕后所做的。

关于“我接近这个权利吗?”部分:

如果我怀疑你的存储库暴露了某种IDbSet,那么它可能是一个泄漏的抽象。 IDbSet 的合约对于典型的存储库客户端想要对数据执行的操作来说过于具体。最好返回一个 IEnumerable 或某种只读集合。

根据您的描述,Posts 的消费者似乎会将其作为读写集合进行操作。这不是典型的 Repository 实现 - 您通常有单独的方法,例如 Get()、Add() 等。实际的内部数据集合永远不会暴露,这意味着您可以轻松地存根或模拟单独的 Repository您需要的操作,而不必担心您的测试数据遇到的那种问题。

【讨论】:

  • 您对存储库提出了一些好处。以前我使用传统的存储库,但根据我的经验,这是一个障碍。在这里对抗IDBSet 要容易得多。
【解决方案3】:

每次测试都会调用 [SetUp] 方法中的任何内容。这可能是您不希望的行为。

您可以将您拥有的代码放在每个单独的测试中的 [SetUp] 方法中,也可以在单元测试类中创建一个单独的私有方法,该方法将启动一个新模拟的 DbSet,以使您保持干燥。

【讨论】:

  • 我不认识。我可以看到您的建议有效,但在我看来 SetUp 方法应该自动执行相同的操作。根据文档,调用SetUp 方法“就在调用每个测试方法之前。”这听起来很像在每次测试之前手动调用私有方法。
  • 您的测试是否相互依赖,因为测试需要按特定顺序运行?如果没有,您希望每个测试都有一个隔离的模拟存储库。在 setup 方法中放置一个断点,你会看到它在每个测试中都会被命中。
  • 它们在设计上并不相互依赖。每个测试的独立模拟存储库是我想要的。
  • 无论出于何种原因,inmemorydbset 在测试之间持续存在。调查背后“为什么”的答案,一旦我发现有用的东西就会更新我的答案。
  • 好的,比尔。我会把它作为我的重点。 Here 是我正在使用的包。您可能有兴趣知道我已验证 MockRepository 辅助方法被调用了适当的次数。
猜你喜欢
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2011-03-24
  • 2015-08-02
相关资源
最近更新 更多