- 除非要通过失败的单元测试,否则不允许编写任何生产代码。
- 您不得编写任何足以使单元测试失败的单元测试。编译失败就是失败。
- 您不能编写任何足以通过一项失败的单元测试的生产代码。
当我编写一个看似值得但未更改生产代码的测试通过时:
- 这是否表示我做错了?
- 如果可以帮助,将来是否应该避免编写此类测试?
- 我应该将该测试留在那里还是将其删除?
注意: 我在这里试图问这个问题:我可以从通过单元测试开始吗? 但是直到现在我还不能很好地阐明这个问题。
当我编写一个看似值得但未更改生产代码的测试通过时:
注意: 我在这里试图问这个问题:我可以从通过单元测试开始吗? 但是直到现在我还不能很好地阐明这个问题。
Answers:
这意味着:
后者的情况比您想象的更普遍。作为一个完全似是而非的(但仍然是说明性的)示例,假设您编写了以下单元测试(伪代码,因为我很懒):
public void TestAddMethod()
{
Assert.IsTrue(Add(2,3) == 5);
}
因为您真正需要的只是2和3相加的结果。
您的实现方法是:
public int add(int x, int y)
{
return x + y;
}
但是,假设我现在需要将4和6加在一起:
public void TestAddMethod2()
{
Assert.IsTrue(Add(4,6) == 10);
}
我不需要重写我的方法,因为它已经涵盖了第二种情况。
现在让我们说我发现我的Add函数确实需要返回一个有一定上限的数字,比如说100。我可以编写一个测试此方法的新方法:
public void TestAddMethod3()
{
Assert.IsTrue(Add(100,100) == 100);
}
并且此测试现在将失败。我现在必须重写我的函数
public int add(int x, int y)
{
var a = x + y;
return a > 100 ? 100 : a;
}
使它通过。
常识表明,如果
public void TestAddMethod2()
{
Assert.IsTrue(Add(4,6) == 10);
}
通过,您不会故意使方法失败,只是为了使测试失败,然后编写新代码以使该测试通过。
add(2,3)
则要通过测试,您实际上会返回5。硬编码。然后,您将编写测试,add(4,6)
该测试将迫使您编写生产代码,以使其通过而不会同时断裂add(2,3)
。你会最终有return x + y
,但你不会开始使用它。理论上。自然,马丁(或者我不记得它可能是其他人)喜欢提供这样的教育示例,但是并不希望您实际上以这种方式编写如此琐碎的代码。
实际上,昨晚在道场上也遇到了同样的问题。
我对此进行了快速研究。这是我想出的:
基本上,TDD规则并未明确禁止它。也许需要一些额外的测试来证明一个函数对于通用输入正确地起作用。在这种情况下,TDD练习将被搁置一会儿。请注意,只要在此期间内未添加任何生产代码,就可以暂时退出TDD练习并不一定会违反TDD规则。
可以编写其他测试,只要它们不是多余的即可。一个好的做法是进行等效类划分测试。这意味着将测试每个等效类的边缘情况和至少一个内部情况。
但是,这种方法可能会出现的一个问题是,如果从一开始就通过了测试,就不能保证没有误报。这意味着可能存在通过测试的原因,因为测试未正确实现,而不是因为生产代码运行正常。为避免这种情况,应稍微更改生产代码以打破测试。如果这使测试失败,则很可能正确地执行了测试,并且可以将生产代码改回以使测试再次通过。
如果您只想练习严格的TDD,则可能不会编写从头开始通过的任何其他测试。另一方面,在企业开发环境中,如果其他测试似乎有用,则实际上应该离开TDD实践。
在不修改生产代码的情况下通过的测试并不是天生就糟糕的,通常对于描述其他需求或边界情况很有必要。只要您的测试“值得”,就可以坚持下去。
遇到麻烦的地方是编写一个已经通过的测试来代替实际理解问题空间的测试。
我们可以想像有两个极端:一个程序员编写大量测试以防万一,一个程序发现一个错误;第二位程序员在编写最少数量的测试之前仔细分析问题空间。假设两者都在尝试实现绝对值函数。
第一个程序员写道:
assert abs(-88888) == 88888
assert abs(-12345) == 12345
assert abs(-5000) == 5000
assert abs(-32) == 32
assert abs(46) == 46
assert abs(50) == 50
assert abs(5001) == 5001
assert abs(999999) == 999999
...
第二位程序员写道:
assert abs(-1) == 1
assert abs(0) == 0
assert abs(1) == 1
第一个程序员的实现可能导致:
def abs(n):
if n < 0:
return -n
elif n > 0:
return n
第二个程序员的实现可能会导致:
def abs(n):
if n < 0:
return -n
else:
return n
所有测试都通过了,但是第一个程序员不仅编写了多个冗余测试(不必要地减慢了开发周期),而且还没有测试边界情况(abs(0)
)。
如果您发现自己编写的测试在不修改生产代码的情况下通过了,请问一下自己,您的测试是否真的在增加价值,或者您是否需要花费更多的时间来理解问题空间。
abs(n) = n*n
并通过了测试。
abs(-2)
。与一切一样,节制是关键。