将现有代码移至测试驱动开发

时间:2020-03-06 15:04:38  来源:igfitidea点击:

最近发现了这种开发方法后,我发现它是一种相当不错的方法。因此,对于我的第一个项目,我有一个价值DLL的小代码(在C#.NET中是有价值的),并且我想对此代码进行一组测试,但是我对如何使用和从哪儿开始。

我正在使用NUnit和VS 2008,对于任何类型的入门,编写测试内容的技巧以及通常如何将代码转移到基于测试的开发方面的任何技巧,都将不胜感激。

解决方案

请参阅迈克尔·费瑟斯(Michael Feathers)撰写的《有效处理旧版代码》一书。

总而言之,将现有代码重构为可测试和已测试的代码需要大量工作。有时,太多的工作不切实际。这取决于代码库的大小以及各种类和函数之间的相互依赖程度。

没有测试的重构会引入行为上的变化(即错误)。纯粹主义者会说这并不是真正的重构,因为缺乏检查行为不变的测试。

当我们在代码区域中工作时,不要立即将测试全面添加到整个应用程序中。我们很可能必须再次回到这些"热点"。

从下至上添加测试:测试少量,独立的类和函数的正确性。

从上至下添加测试:将整个子系统作为黑盒进行测试,以查看其行为是否随代码的变化而变化。因此,我们可以逐步解决它们,以了解发生了什么情况。这种方法可能会为我们带来最大的收益。

首先,不要太在意添加测试时的"正确"行为,而是要检测并避免行为上的变化。大型未经测试的系统通常具有内部行为,这些行为看似不正确,但这是系统其他部分所依赖的。

考虑隔离诸如数据库,文件系统,网络之类的依赖项,以便在测试过程中可以将它们换成模拟数据提供者。

如果程序没有内部接口,而这些接口定义了一个子系统/层与另一个子系统/层之间的边界,那么我们可能必须尝试引入它们并对其进行测试。

另外,像Rhinomocks或者Moq这样的自动模拟框架可能会在这里模拟现有的类。在针对可测试性设计的代码中,我还没有真正找到对它们的需求。

可测试的代码很容易通过附带的测试发现。如果有的话,它必须是可测试的。如果没有,则假设相反。 ;)

话虽这么说:测试驱动开发(TDD)并不是一种测试策略,而是一种设计策略。首先编写的测试有助于设计类的接口,以及正确确定类(或者子系统)的范围。

拥有在TDD期间创建的测试并在以后执行这些测试会产生很好的测试,但这仅仅是(非常受欢迎)该设计理念的副作用。

这就是说,期望代码对测试有所抵抗。听代码并更改界面,以便易于测试。开始编写测试时,我们很可能会重新设计它。

在不进行测试的情况下将代码迁移到经过单元测试的环境中时,我的圣经就是有效地使用遗留代码,它还为使代码易于测试以及如何对其进行测试提供了很多见识。

我还发现Cwith NUnit中的示例驱动测试开发和实用单元测试:很好地介绍了该环境中的单元测试。

启动TDD的一种简单方法是从今天开始首先开始编写测试,并确保每当需要触摸现有的(未经单元测试的)代码时,我们都可以编写通过测试以验证系统的现有行为,然后再进行更改以便我们可以在以后重新运行这些测试,以增强对未损坏任何东西的信心。

DLL提供了某种服务。对于每个服务,在获得此服务之前我们必须做什么,应该传递什么参数以获取该服务,如何得知所请求的服务已正确执行?

找到这些问题的答案后,就可以编写第一个测试。这样的测试宁可被称为特性测试而不是单元测试,但是如果未使用TDD开发DLL,则可能比单元测试更容易编写。

M. Feathers的"有效地使用旧版代码"中也讨论了特性测试,在其他答复中也建议使用。

另外,在添加任何新代码行之前,请确保编写失败的测试。

我称之为"测试驱动逆向工程"。

从"底部"开始-每个类都可以单独检查并为此编写测试。如有疑问,请猜测。

在向前进行普通TDD时,我们将测试视为神圣,并假定代码可能已损坏。有时测试是错误的,但是起点是代码。

在执行TDRE时,代码是神圣的-直到我们可以证明该代码具有长期存在的错误为止。在相反的情况下,我们将围绕代码编写测试,对测试进行调整,直到它们起作用并声称代码可以正常工作为止。

然后,我们可以深入研究错误的代码。一些糟糕的课程将具有明智的测试用例-这只是需要清理。但是,一些错误的代码也会有一个毫无意义的测试用例。这可能是错误,也可能是我们可以纠正的笨拙设计。

要判断代码是否真的错误,还需要从总体测试用例开始。实际有效的实时数据是一个开始。此外,产生任何已知错误的实时数据也是一个不错的起点。

我已经编写了一些代码生成器,可以将实时数据转换为单元测试用例。这样,我就具有测试和重构的一致基础。