【问题标题】:Integration Testing: Am I doing it right?集成测试:我做对了吗?
【发布时间】:2011-06-21 08:28:36
【问题描述】:

这是我为与数据库交互的类编写的集成测试:

[Test]
public void SaveUser()
{
    // Arrange
    var user = new User();
    // Set a bunch of properties of the above User object

    // Act
    var usersCountPreSave = repository.SearchSubscribersByUsername(user.Username).Count();
    repository.Save(user);
    var usersCountPostSave = repository.SearchSubscribersByUsername(user.Username).Count();

    // Assert
    Assert.AreEqual(userCountPreSave + 1, userCountPostSave);
}

在我看来,如果不涉及SearchSubscriberByUsername 函数来确定用户是否成功保存,我就无法测试Save 函数。我意识到集成测试并不意味着应该是一次测试一个代码单元的单元测试。但理想情况下,如果我每次测试都可以在我的存储库类中测试一个函数,但我不知道如何实现这一点,那就太好了。

到目前为止,我编写代码的方式还不错,还是有更好的方法?

【问题讨论】:

  • 你在测试哪个类,你什么时候将初始数据输入数据库?
  • (Unit|Integration)测试是有用的,如果你对你编写的软件更有信心并且对改变它更有信心。如果您对文献中的内容不太介意,请不要为了使其更符合大师的要求而破坏有用的测试
  • @Danny:我正在测试我的存储库类,我删除所有表并在每次测试运行开始时重新创建它们(而不是在每次测试之前)。
  • 您应该在每个测试运行之前执行此操作(在 TestInitialize 中,而不是 ClassInitialize 中),否则您的测试相互依赖以及它们的运行顺序。 (除非没有个测试写入数据库。)
  • @Danny:不是删除所有表并为每个测试重新创建它们吗?只要测试代码不对数据库的状态做任何假设,我为什么要为每个测试重新创建所有表?

标签: c# nunit automated-tests integration-testing


【解决方案1】:

您的测试有问题。当您测试数据是否保存到数据库中时,您应该测试它是否在数据库中,而不是存储库说它在数据库中。

如果您正在测试存储库的功能,那么您无法通过询问它是否正确完成来验证该功能。这相当于对某人说“你做对了吗?”他们会说是的。

想象一下,存储库永远不会提交。您的测试会顺利通过,但数据不会在数据库中。

所以,我要做的是打开与数据库的连接(纯 SQL)并检查数据是否已正确保存。只需要前后一个 select count(*) 就可以确保用户已经被保存。如果这样做,您也可以避免使用 SearchSubscribersByUsername。

如果您正在测试存储库的功能,根据定义,您不能信任存储库。

【讨论】:

  • 我希望我可以避免这样做,这就是为什么我发表这篇文章希望找到一些“最佳实践”来避免编写不可避免的代码。 +1 表示“如果您正在测试存储库的功能,则根据定义,您不能信任存储库。”
【解决方案2】:

要对诸如“保存”功能之类的东西进行单元测试,您肯定需要一些值得信赖的渠道来检查操作结果。如果你信任SearchSubscribersByUsername(因为你已经为这个函数做了一些单元测试),你可以在这里使用它。

如果您不信任 SearchSubscribersByUsername 并且您认为您的单元测试也可能因为该函数中存在错误(而不是在 Save 中)而中断,您应该考虑一个不同的通道(也许您有一个是否可以绕过 SQL 访问您的数据库以检查Save 结果,这可能比SearchSubscribersByUsername 的实现更简单)?但是,不要再次重新实现SearchSubscribersByUsername,那将变得毫无意义。无论哪种方式,您至少需要一些其他您可以信任的功能。

【讨论】:

  • 不需要先测试任何频道吗?假设我为 SearchSubscriberByUsername 函数编写了一个测试。那你觉得靠谱吗?
  • 如果你认为你有足够的单元测试,你可以信任它。我说“你”,因为我对SearchSubcribersByUsername 一无所知,它的内部复杂性,所以你必须自己决定“足够”是什么意思。但是,如果您认为它(或可能变得)过于复杂而不值得信赖,并且您有一个更简单且不易出错的渠道来测试数据库中的真实内容,那么您应该使用后者。
【解决方案3】:

除非您正在测试的方法返回有关您所做操作的明确信息,否则我看不到任何避免调用其他方法的方法。我认为您的假设是正确的,即集成测试需要与单元测试不同的思维方式。

我仍然会构建专注于单个方法的测试。所以在测试 Save() 时,我可能会很好地使用 Search() 的功能,但我的重点是 Save() 的边缘情况。我构建了处理重复插入或无效输入数据的测试。然后,我构建了一整套 Search() 测试来处理 Search() 的边缘情况。

现在一种可能的想法是保存和搜索有一些共性,搜索中的错误可能会掩盖保存中的错误。例如,想象一下,如果你在下面有一个缓存层。因此,一种替代方法可能是使用其他一些验证机制。例如对数据库的直接 JDBC 调用,或者在您的基础架构中的某个位置引入模拟层。在构建复杂的集成系统时,这种“后门”验证可能是必不可少的。

【讨论】:

  • 我考虑编写一个直接调用数据库的方法来计算保存操作前后系统中的用户数量,但我想知道这是否是最佳实践,因此我发了这篇文章。有太多关于单元测试的资料,因此很难找到有关集成测试的资料。
【解决方案4】:

就个人而言,我已经编写了无数与此非常相似的测试,并且认为它很好。另一种方法是将数据库存根,这样 searchSubscribers 就不会真正做任何事情,但我会说这些工作量很大,但收效甚微。

【讨论】:

    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2017-12-26
    • 1970-01-01
    • 2015-04-03
    相关资源
    最近更新 更多