4

考虑以下事件序列:

  1. 你编写了一个函数A()来完成一个工作单元
  2. 您为函数 A() 编写测试以确保不存在错误
  3. 您编写B()使用函数的函数A()
  4. 您为功能编写了一个测试B()以确保不存在任何错误
    4.1 功能B()覆盖功能A()的测试结果您至少有 2 个测试涵盖了一些相同的功能

问题 1:一A()开始就为函数编写测试值得吗?

  1. 你写了很多代码
  2. 你编写了一个巨大的回归测试来测试程序的端到端功能
    6.1 这个单一的回归测试有效地重复了绝大多数已经编写的测试

问题 2:按照这些步骤,代码包含许多测试,这些测试不止一次地涵盖了同一件事。有没有避免这种情况的技术?

认为:

出于这个问题的目的,请假设 B 做了两件事,其中一件是 A

void performLifeChoice() { // B()
  if (timeIsRight) {
     askForPromotion();   // A()
  } else {
     goBackToSchool();
  }
}
4

3 回答 3

4

我的一般回答是肯定的,值得为 A() 编写单元测试,原因有两个:

1) 你最终可能会从与 B() 不同的上下文中调用 A(),在这种情况下,你会很高兴知道 A() 正在工作,并避免重复测试。如果您没有单独对 A() 进行测试,您最终会为等效的代码分支重写两次实际上是相同的测试。

2) 与此相关的是,如果您不单独测试 A(),您最终可能会一直遇到海龟。现在想象一下,一个函数 C() 在它的一个分支中调用 B(),而 D() 调用 C() 等等......如果你遵循只测试更高级别函数的路径,你最终会集成测试必须涵盖越来越多的前提条件。在尽可能少的上下文中测试单个单元,原则上可以避免这个问题。

我的回答的一个隐含结论是,如果 A() 永远不会从 B() 以外的地方被调用,那么将 A() 设为私有可能是值得的,此时,测试可能会变得“可选”。另一方面,保留该测试可能仍然有用,因为它将帮助您确定当 B() 失败时,是因为您破坏了 A()。用 Kent Beck 的话来说,“如果测试失败,有多少事情会出错?答案越接近 1,测试就越“unit-y”。” - 进行单元测试非常有用,它可以帮助您准确找出代码中哪里出了问题。

现在你怎么能去测试 B(),而不复制 A() 的测试,增加前置条件?

这就是 Mocking/Stubbing 或类似技术可以发挥作用的地方。如果您考虑 B() 正在做什么,它实际上并没有执行 A(),它充当“协调器”,将条件输送到各种路径。在我看来,一个充分的测试应该是“当 TimeIsRight 时,B() 应该调用 A()”,A() 提供的答案与 B() 无关,这是 A() 的责任,你的单元测试涵盖。

在那个框架中,对 B() 的测试应该断言是在 TimeIsRight 时调用 A(),而不是 A() 返回的内容。根据您的代码的具体情况,您可以考虑使 A() 在 B() 中“可替代”,例如通过接口或通过注入代替 A() 的函数。

于 2012-08-10T23:27:21.220 回答
3

由于这些方法似乎在同一个类中,

  • 如果A()是私有的,只需 test B()A()将作为B()'s 测试的一部分进行测试。

  • 如果A()是公开的,请不要在测试时对其进行测试B()A()在单独的测试中验证's 的正确性。然后在另一个测试中,只需检查B() 调用 A()是否正确。在您的示例中,这意味着验证是否askForPromotion()在某些情况下被调用timeIsRight

请注意,如果 A() 和 B() 在不同的类中,情况会略有不同。

顺便说一句,使用 TDD 方法,事件的顺序应该是 2. 1. 4. 3. ;)

于 2012-08-11T01:29:08.743 回答
1

在这个例子中,不,在我看来,只为 A 编写测试并不值得。然而,在大多数情况下,A 和 B 都会接受参数,而 B 永远不会使用可能的参数输入的全部范围对于 A,但 A 的单元测试将(应该!),因此在这种情况下,对 A 的单元测试并不是浪费,因为它们涵盖了 B 未传递的参数,无论 B 的参数输入是什么。

于 2012-08-10T22:51:09.967 回答