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