在进入标题主题之前,我们有一个简单的编程示例。 在编程任务中,我将演示一些不良的编码样式,并以此为基础,我将更容易解释为什么在单元测试中相同的样式是不良的。 好吧,既然我写了这句话,这似乎是一个显而易见的陈述。 当编程不好时,为什么有些东西会在单元测试中好呢? 一件事是, 并非总是那样 ,而另一件事是,当我们创建单元测试时,相同的错误可能并不那么明显。
演示任务
演示任务非常简单。 让我们编写一个类来确定整数> 1是否为质数。 该算法很简单。 检查以2开头的所有数字,直到该数字的平方根为止。 如果数字不是素数,我们将找到一个将整数除以整数的数字;如果找不到除数,则数字是素数。
public class PrimeDecider {
final int number;
public PrimeDecider(int number) {
this.number = number;
}
public boolean isPrime() {
for (int n = 2; n * n < number; n++) {
if (number % n == 0) {
return false;
}
}
return true;
}
}
单元测试是
import static org.junit.Assert.assertFalse;
import static org.junit.Assert.assertTrue;
import org.junit.Test;
public class PrimDeciderTest {
@Test
public void sample_2_IsPrime() {
PrimeDecider decider = new PrimeDecider(2);
boolean itIsPrime = decider.isPrime();
assertTrue(itIsPrime);
}
@Test
public void sample_17_IsPrime() {
PrimeDecider decider = new PrimeDecider(17);
boolean itIsPrime = decider.isPrime();
assertTrue(itIsPrime);
}
@Test
public void sample_10_IsNotPrime() {
PrimeDecider decider = new PrimeDecider(10);
boolean itIsPrime = decider.isPrime();
assertFalse(itIsPrime);
}
}
这是一个很好的测试,可读性强,有一些复制粘贴,并且最重要的是它为我们提供了100%的代码覆盖率。 相信我:
出现错误
然而,有一天,有人想到了一个奇怪的想法来测试9是否为质数。 信不信由你,我们的程序说9是质数。 因此,测试人员(或者,如果您不是很幸运的客户)会打开一个故障单:
BGTCKT17645329-KT对于与3相乘的数字,Prime方法无法给出正确的答案。 例如,对于表示9的对象,结果为true。
然后是错误修复的繁琐工作。 通常是多么快乐。 首先,您克服了那种耳目一新的感觉,即“客户是愚蠢的”。 显然,客户是愚蠢的,因为他想使用该类来测试9,这本来就不是……哈哈! 并且因为错误描述根本是错误的。 没有方法Prime
! 并且代码正确地检测到例如数字3(它本身是3的乘法)是质数。 并且它还正确检测出6和12不是质数。 因此,客户如何敢于制作这样的错误报告。 这样的想法可能会帮助您冷静下来,但对业务没有帮助,这是像您这样的专业人员的首要任务。
冷静下来后,您承认该代码实际上不适用于数字9,因此您开始调试和修复它。 首先是失败的单元测试。 那就是我们要做TDD的方式:
@Test
public void demonstrationOf_BGTCKT17645329() {
PrimeDecider decider = new PrimeDecider(9);
boolean itIsPrime = decider.isPrime();
assertFalse(itIsPrime);
}
然后您提供了修复程序:
public boolean isPrime() {
if (number == 9)
return false;
for (int n = 2; n * n < number; n++) {
if (number % n == 0) {
return false;
}
}
return true;
}
我只是在开玩笑!
实际上,我已经在实际的生产代码中看到了类似的修复程序。 当您承受时间压力并且生活有限时,即使您知道适当的解决方案,也可能会提出类似的解决方案。 在这种情况下,只需简单地在循环条件中的<
符号前面插入a =
即可测试该数字实际上不是素数的平方。 本质上是代码
for (int n = 2; n * n =< number; n++) {
会好的。
在实际的生产情况下,这可能是一个真实而庞大的重构,并且如果由于代码通常用于小于25的数字而很少出现这些特殊情况,那么此修复在商业上是可以的。
实际修复错误
更现实一点,并假设您意识到问题不仅限于数字9,还包括所有平方数,然后应用此修复程序:
public class PrimeDecider {
final int number;
public PrimeDecider(int number) {
this.number = number;
}
public boolean isPrime() {
if (isASquareNumber(number))
return false;
for (int n = 2; n * n < number; n++) {
if (number % n == 0) {
return false;
}
}
return true;
}
private boolean isASquareNumber(int number) {
double d = Math.sqrt(number);
return Math.floor(d) == d;
}
}
这很丑,但是行得通。 包含数千行的上帝类的真实单词代码即使在重构后也不会比这更好。
完成了吗 并不是的。 让我们再次看一下单元测试。 它记录了该代码
sample 2 is prime
sample 17 is prime
sample 10 is not prime
demonstration of BGTCKT17645329
那并不是特别有意义,尤其是最后一行。 报告该错误(除了一些错误的陈述),指出数字9处理不当。 但是实际的错误是程序无法正确处理质数平方的数字。 如果您知道ITIL,则第一个是事件,第二个是问题。 我们为事件创建了单元测试,这很好,我们做到了。 它有助于调试。 但是,当我们确定问题所在时,在应用此修复程序之前,我们并未创建一个程序来测试该问题的修复程序。 这不是真正的TDD,因为对事件进行了单元测试,但我们并未创建它来测试修复程序。
适当的测试将使用类似以下的名称
some sample square number is not prime
(在方法名称中使用适当的骆驼套),它将有一些平方数,例如9、25、36作为测试数据。
结论
修复错误时,请小心TDD。 您可能会错误地应用它。 TDD说在编写代码之前先编写单元测试。 您编写的单元测试将定义您要编码的内容。 这不是演示该错误的单元测试。 您可以将其用作调试和查找根本原因的工具。 但这不是TDD的一部分。 当您知道要写什么时,无论您急切要修改代码:都要编写将测试您要编写的功能的单元测试。
这就是我想要在标题中暗示的意思:针对功能或功能更改编写单元测试,以修复错误而不是错误。
翻译自: https://www.javacodegeeks.com/2015/02/not-unit-test-bugs.html