如果我有一个使用复杂方法的相当复杂的对象,并且编写了测试和最低要求以使其通过(在它第一次失败后为红色)。我什么时候回去写真实的代码?在重新测试之前我要写多少实际代码?我猜最后一个是直觉。
您无需“返回”并编写“真实代码”。这都是真实的代码。您要做的是返回并添加另一个测试,该测试迫使您更改代码以通过新测试。
至于在重新测试之前要编写多少代码?没有。您编写零代码而没有失败的测试,该测试迫使您编写更多的代码。
注意到模式了吗?
让我们来看一个(另一个)简单的示例,希望对您有所帮助。
Assert.Equal("1", FizzBuzz(1));
容易豆豆。
public String FizzBuzz(int n) {
return 1.ToString();
}
不是您所说的真实代码,对吗?让我们添加一个强制更改的测试。
Assert.Equal("2", FizzBuzz(2));
我们可以做一些愚蠢的事情,例如if n == 1
,但是我们将跳到理智的解决方案。
public String FizzBuzz(int n) {
return n.ToString();
}
凉。这将适用于所有非FizzBuzz编号。下一个将迫使生产代码更改的输入是什么?
Assert.Equal("Fizz", FizzBuzz(3));
public String FizzBuzz(int n) {
if (n == 3)
return "Fizz";
return n.ToString();
}
然后再次。编写尚未通过的测试。
Assert.Equal("Fizz", FizzBuzz(6));
public String FizzBuzz(int n) {
if (n % 3 == 0)
return "Fizz";
return n.ToString();
}
现在,我们涵盖了三个的所有倍数(不是五个的倍数,我们会注意并返回)。
我们还没有为“ Buzz”编写测试,因此让我们来编写它。
Assert.Equal("Buzz", FizzBuzz(5));
public String FizzBuzz(int n) {
if (n % 3 == 0)
return "Fizz";
if (n == 5)
return "Buzz"
return n.ToString();
}
再说一次,我们知道还需要处理另一种情况。
Assert.Equal("Buzz", FizzBuzz(10));
public String FizzBuzz(int n) {
if (n % 3 == 0)
return "Fizz";
if (n % 5 == 0)
return "Buzz"
return n.ToString();
}
现在我们可以处理5的所有倍数,而不是3的倍数。
到目前为止,我们一直忽略重构步骤,但是我看到了一些重复。让我们现在清理它。
private bool isDivisibleBy(int divisor, int input) {
return (input % divisor == 0);
}
public String FizzBuzz(int n) {
if (isDivisibleBy(3, n))
return "Fizz";
if (isDivisibleBy(5, n))
return "Buzz"
return n.ToString();
}
凉。现在,我们删除了重复项,并创建了一个命名良好的函数。我们可以编写的下一个将迫使我们更改代码的测试是什么?好吧,我们一直在避免数字被3和5整除的情况。让我们现在来编写它。
Assert.Equal("FizzBuzz", FizzBuzz(15));
public String FizzBuzz(int n) {
if (isDivisibleBy(3, n) && isDivisibleBy(5, n))
return "FizzBuzz";
if (isDivisibleBy(3, n))
return "Fizz";
if (isDivisibleBy(5, n))
return "Buzz"
return n.ToString();
}
测试通过了,但是我们有更多重复项。我们有选项,但是我要多次应用“提取局部变量”,以便我们重构而不是重写。
public String FizzBuzz(int n) {
var isDivisibleBy3 = isDivisibleBy(3, n);
var isDivisibleBy5 = isDivisibleBy(5, n);
if ( isDivisibleBy3 && isDivisibleBy5 )
return "FizzBuzz";
if ( isDivisibleBy3 )
return "Fizz";
if ( isDivisibleBy5 )
return "Buzz"
return n.ToString();
}
我们已经涵盖了所有合理的输入,但是不合理的输入呢?如果传递0或负数会怎样?编写那些测试用例。
public String FizzBuzz(int n) {
if (n < 1)
throw new InvalidArgException("n must be >= 1);
var isDivisibleBy3 = isDivisibleBy(3, n);
var isDivisibleBy5 = isDivisibleBy(5, n);
if ( isDivisibleBy3 && isDivisibleBy5 )
return "FizzBuzz";
if ( isDivisibleBy3 )
return "Fizz";
if ( isDivisibleBy5 )
return "Buzz"
return n.ToString();
}
这看起来像“真实代码”了吗?更重要的是,在什么时候它不再是“虚幻的代码”,而是过渡到“真实的”?这是值得深思的...
因此,我可以通过寻找一个我知道不会在每个步骤中通过的测试来简单地做到这一点,但是我已经做了很多练习。当我在工作时,事情从来都不是那么简单,而且我可能并不总是知道哪种测试将迫使变革。有时我会写一个测试,惊讶地发现它已经通过了!我强烈建议您在开始之前养成创建“测试列表”的习惯。该测试列表应包含您可以想到的所有“有趣”输入。您可能不会全部使用它们,并且可能会随时添加案例,但是此列表可作为路线图。我的FizzBuzz测试清单如下所示。
- 负
- 零
- 一
- 二
- 三
- 四个
- 五
- 六(3的非平凡倍数)
- 九(3平方)
- 十(5的非平凡倍数)
- 15(3和5的倍数)
- 30(3和5的非平凡倍数)