考虑以下事件序列:
A()
B()
A()
B()
编写了一个测试,以确保没有错误
4.1函数B()
的测试覆盖函数A()
因此,您至少有2个测试涵盖了一些相同的功能 问题1:为函数A()
编写测试是否值得开始?
问题2:通过执行以下步骤,代码包含许多不止一次覆盖相同内容的测试。有没有一种技术可以避免这种情况?
承担:
出于这个问题的目的,请假设B做两件事,其中一件是A的全部
void performLifeChoice() { // B()
if (timeIsRight) {
askForPromotion(); // A()
} else {
goBackToSchool();
}
}
答案 0 :(得分:3)
我的一般答案是肯定的,值得为A()编写单元测试,原因有两个:
1)您可能最终从与B()不同的上下文中调用A(),在这种情况下,您将很高兴知道A()正在工作,并避免测试重复。如果你没有单独测试A(),那么对于等效的代码分支,你最终会重写两次实际上相同的测试。
2)相关地,如果你不单独测试A(),你可能最终会让乌龟一直下来。想象一下,函数C()在其中一个分支中调用B(),D()调用C()等...如果你按照仅测试更高级函数的路径,你最终会通过集成测试,必须涵盖越来越多的前置条件。在尽可能小的环境下对各个单元进行测试,原则上可以避免这个问题。
我的答案的一个隐含的结论是,如果永远不会从B()以外的地方调用A(),那么将A()私有化是值得的,并且此时,测试可能变为“可选的”。另一方面,保持该测试可能仍然有用,因为它将帮助您确定当B()失败时,这是因为您打破了A()。用Kent Beck的话来说,“如果测试失败了,有多少东西可能出错?答案越接近1,测试的单位越多。” - 进行单元测试非常有用,它可以帮助您精确查明代码中出错的位置。
现在你怎么能用附加的前置条件来测试B(),而不是复制A()的测试?
这是Mocking / Stubbing或类似技术可以发挥作用的地方。如果你考虑B()正在做什么,它实际上没有执行A(),它充当“协调者”,管理各种路径的条件。在我看来,一个充分的测试是“当TimeIsRight,然后B()应该调用A()”时,A()提供的答案与B()无关,它是A()的责任,你的单元测试封面。
在那个框架中,对B()应该断言的是,在TimeIsRight时调用A(),而不是A()返回。根据代码的具体情况,您可以考虑在B()中使A()“可替换”,例如通过接口,或通过注入代替A()的函数。
答案 1 :(得分:3)
由于这些方法似乎属于同一类,
如果A()
是私有的,只需测试B()
。 A()
将作为B()
测试的一部分进行测试。
如果A()
是公开的,请在测试B()
时不要对其进行测试。在单独的测试中验证A()
的正确性。然后在另一个测试中,只需检查B()
正确调用 A()
。在您的示例中,这意味着要验证askForPromotion()
情况下是否调用了timeIsRight
。
请注意,如果A()和B()在不同的类中,情况会略有不同。
顺便说一句,使用TDD方法,您的活动顺序应为2. 1. 4. 3.;)
答案 2 :(得分:1)
在这个例子中,不,在我看来,仅仅为A编写测试并不值得。但是,在大多数情况下,A和B都将采用参数,而B将永远不会运用完整的范围。 A的可能参数输入,但A的单元测试将(应该!),因此在这种情况下A的单元测试不是浪费,因为它们涵盖了B未传递的参数,无论B的参数输入是什么。