我有一个“配方”方法,我试图用TDD写。它基本上会调用不同的方法,偶尔会根据这些方法的结果做出决定:
public void HandleNewData(Data data)
{
var existingDataStore = dataProvider.Find(data.ID);
if (data == null)
return;
UpdateDataStore(existingDataStore, data, CurrentDateTime);
NotifyReceivedData(data);
if (!dataValidator.Validate(data))
return;
//... more operations similar to above
}
我的膝跳反应将是开始编写测试用例,我在其中验证HandleNewData调用上面传递的方法传递预期的参数,并在方法调用失败的情况下返回。 但这对我来说有点像这样一个巨大的投入时间来编写这样的测试,几乎没有实际的好处。
那么编写这样的测试真正的好处是什么?还是真的不值得打扰?
似乎它只是一个过度规范的代码,并且只要代码必须调用另一个方法或决定不再调用当前方法之一就会导致维护问题。
答案 0 :(得分:9)
TDD并不意味着为已经存在的代码编写单元测试(尽管有时在改进遗留代码时可能是必要的)。
你可能听说过“红色,绿色,重构”这个词。这是我们在进行TDD时采用的方法。以下是测试驱动开发的三个定律,它们更进一步......
采用这种方法的好处是,您最终获得了非常接近100%的单元测试覆盖率,并且您知道您的代码完全符合规定。
它将减少维护问题,因为一旦有人对您的代码进行了更改并运行测试,他们就会知道他们是否已经破坏了任何内容。
在这种情况下,在为HandleNewData()
添加任何方法之前,我会逐步为HandleNewData()
调用的方法添加单元测试。
将单元测试添加到遗留代码是很困难的,但是可行并且非常值得付出努力。如果您还没有,我建议您按Working Effectively with Legacy Code阅读Michael Feathers。我发现将单元测试添加到一个已有25年历史的代码库时它非常有用。
答案 1 :(得分:3)
您遇到的问题很常见。你有一些讨厌的未经测试的遗留代码,它做得太多,而且与太多的合作者紧密相连。为此编写测试确实很痛苦。
问题在于,你不幸背负着这种代码债务,而且在某些时候,你将不得不支付它。
因此,要开始支付部分债务,如果您需要更改此代码,我会尽可能地模拟出测试运行中的方法,以便您可以获得测试的shell放置在您可以添加新功能的位置。如果可能的话,我会将您的新功能调用到另一个协作者,这是您可以放置(和测试!)新代码的地方。
通过这种方式,您可以对旧代码调用新代码以及通过TDD正确构建新代码有一些基本的信心。
你当然仍然拥有原始遗留代码的代码债务,但你可以将其解决为一个单独的问题。
答案 2 :(得分:1)
由于您已经澄清它是“ Legacy Code(TM)”,因此我将轻松设计该方法。名称本身含糊不清,反映在方法的内容中。我会稍微看一下改进设计 - 似乎做了很多。
但要做到这一点,我必须确保在“让它变得更好”的借口下,我不会让它变得更糟。我该如何证明?测试!
所以我首先将“副”测试放在顶级对象的“块”上。 因此,我已经根据我的知识进行了验证“HandleNewData”行为的测试(这可能包括一些代码挖掘)
一旦我通过一些自动“副”测试确定现有行为,我现在可以放心地进行更改/改进。也可能是这样,一旦设计被重构,就不再需要HandleNewData的包含类型。在这种情况下,您可以放弃这些测试 - 但是这些测试在现有改进之间的价值不容忽视。