Moving existing code to Test Driven Development
最近发现了这种开发方法,我发现它是一种相当不错的方法。 所以,对于我的第一个项目,我有一个小DLL的代码(在C#.NET中,它的价值),我想为这段代码做一组测试,但我有点失去了如何和 从哪儿开始。
我正在使用NUnit和VS 2008,任何关于什么类开始的提示,为什么编写测试,以及关于如何将代码移动到基于测试的开发的一般提示都将非常感激。
请参阅Michael Feathers撰写的"有效利用遗产代码"一书。
总之,将现有代码重构为可测试和测试的代码是很多工作;实际上有时需要做太多工作。它取决于代码库的大小,以及各种类和函数相互依赖的程度。
没有测试的重构将引入行为的变化(即错误)。纯粹主义者会说它不是真正的重构,因为缺乏检查行为不会改变的测试。
不是一次性将测试添加到整个应用程序,而是在代码区域中添加测试。很可能你将不得不再次回到这些"热点"。
从下到上添加测试:测试很少的独立类和函数以确保正确性。
从上到下添加测试:将整个子系统测试为黑盒,以查看其行为是否随代码更改而变化。因此,您可以逐步了解它们以了解正在发生的事情。这种方法可能会给您带来最大的好处。
在添加测试时,不要太在意"正确"行为是什么,要检测并避免行为的变化。大型未经测试的系统通常具有可能看起来不正确的内部行为,但系统的其他部分依赖于此。
考虑隔离数据库,文件系统,网络等依赖关系,以便在测试期间将它们换成模拟数据提供者。
如果程序没有内部接口,这些行定义了一个子系统/层与另一个子系统/层之间的边界,那么您可能必须尝试引入这些接口并对它们进行测试。
此外,像Rhinomocks或Moq这样的自动模拟框架可能有助于在这里模拟现有的类。我还没有真正发现在为可测试性设计的代码中需要它们。
在将没有测试的代码迁移到经过单元测试的环境中时,有效地使用遗留代码是我的圣经,它还提供了很多有关如何使代码易于测试以及如何测试它的深入见解。
我还通过示例和实用单元测试找到了测试驱动开发:在C#中使用NUnit,可以很好地介绍该环境中的单元测试。
启动TDD的一个简单方法是从今天开始编写测试,并确保无论何时需要触摸现有的(未经单元测试的)代码,都要编写通过测试,以便在更改之前验证系统的现有行为这样你就可以重新运行这些测试,以增加你没有破坏任何东西的信心。
我称之为"测试驱动的逆向工程"。
从"底部"开始 - 可以单独检查每个类并为其编写测试。如果有疑问,猜猜。
当你在前进方向上进行普通的TDD时,你会认为测试是神圣的,并假设代码可能已被破坏。有时测试是错误的,但是你的起始位置是代码。
当您进行TDRE时,代码是神圣的 - 直到您可以证明代码有一个长期存在的错误。在相反的情况下,您可以围绕代码编写测试,调整测试直到它们工作并声明代码有效。
然后,你可以挖掘坏代码。一些不好的cade将有合理的测试用例 - 这只需要清理。然而,一些不好的代码也会有一个毫无意义的测试用例。这可能是您可能能够纠正的错误或笨拙的设计。
要判断代码是否真的错误,您还需要从整体测试用例开始。实际工作的实时数据是一个开始。此外,生成任何已知错误的实时数据也是一个很好的起点。
我编写了很少的代码生成器来将实时数据转换为单元测试用例。这样,我就有了一致的测试和重构基础。
可测试的代码很容易被发现 - 通过随附的测试。如果有一些,它必须是可测试的。如果没有 - 假设相反。 ;)
这就是说:测试驱动开发(TDD)不是一个测试策略,而是一个设计策略。您首先编写的测试有助于设计类的接口,以及正确获取类(或子系统)的范围。
拥有在TDD期间创建并稍后执行它们的测试可以进行很好的测试,但这只是该设计理念的一个(非常受欢迎的)副作用。
这就是说,期望您的代码有一些阻力来进行测试。聆听您的代码并更改界面以便轻松测试。当你开始编写测试时,你很可能会重新设计它。
您的DLL提供某种服务。对于每项服务,在获得此服务之前您需要做什么,您应该通过哪些参数来获取此服务,您如何知道所请求的服务已正确执行?
获得这些问题的答案后,您可以编写第一个测试。这样的测试宁可称为Characterization测试,而不是单元测试,但如果不使用TDD开发DLL,则可能比单元测试更容易编写。
M. Feathers的"有效使用遗留代码"中也讨论了特征测试,这在其他答复中是推荐的。
此外,请务必在添加任何新代码之前编写失败的测试。