【发布时间】:2009-05-05 05:44:33
【问题描述】:
我是 TDD 新手,不知道如何解决以下问题。 我有相当大的类,它生成特定格式的文本文件,用于导入外部系统。我将重构这个类,并且我想在此之前编写单元测试。
这些测试应该是什么样子?实际上主要目标 - 不要破坏文件的结构。但这不是说我应该比较文件前后的内容吗?
【问题讨论】:
标签: c# unit-testing refactoring tdd
我是 TDD 新手,不知道如何解决以下问题。 我有相当大的类,它生成特定格式的文本文件,用于导入外部系统。我将重构这个类,并且我想在此之前编写单元测试。
这些测试应该是什么样子?实际上主要目标 - 不要破坏文件的结构。但这不是说我应该比较文件前后的内容吗?
【问题讨论】:
标签: c# unit-testing refactoring tdd
我认为你会从一个我不愿称之为“单元测试”的测试中受益——尽管可以说它测试了当前生成文本文件的“单元”。这将简单地运行当前代码并在其输出和“黄金主”文件之间进行比较(您可以通过运行一次测试并复制到其指定位置来生成该文件)。如果代码中有很多条件行为,您可能希望使用多个示例运行它,每个示例都有不同的测试用例。使用现有代码,根据定义,所有测试都应该通过。
现在开始重构。提取一个方法——或者更好,为一个你可以设想提取的方法编写一个测试,一个真正的单元测试——提取该方法,并确保所有测试,对于新的小方法和更大的系统,仍然通过。起泡,冲洗,重复。系统测试给你一个安全网,让你在重构中充满信心;单元测试驱动新代码的设计。
有一些库可以使这种测试更容易(尽管即使没有它们也很容易)。见http://approvaltests.sourceforge.net/。
【讨论】:
在这种情况下,我使用以下策略:
为每个方法编写一个测试(仅覆盖其默认行为,不进行任何错误处理等)
运行代码覆盖工具并找到测试未覆盖的块。编写涵盖这些块的测试。
这样做直到代码覆盖率超过 80%
开始重构类(主要是按照关注点分离原则生成更小的类)。
使用测试驱动开发来编写新类。
【讨论】:
实际上,这是一个很好的起点(将众所周知的输出与当前类生成的输出进行比较)。 如果单个生成器类可以产生不同的结果,则为每种情况创建一个。 这将确保您不会破坏当前的生成器类。
如果您有当前课程的规范文档,可能会对您有所帮助。您可以将其用作重构工作的基础。
【讨论】:
如果您还没有,请拿起 Michael Feathers 的书“Working Effectively with Legacy Code”。这完全是关于如何向现有代码添加测试,这正是您正在寻找的。p>
但是在你读完这本书之前,我建议从回归测试开始:创建类,让它将文件写入磁盘,然后将该文件与你隐藏的“已知良好”文件进行比较在您的源存储库中的某个地方。如果它们不匹配,则测试失败。
然后开始看看你的班级做出的有趣决定。看看如何让它们接受测试。也许您将一些复杂的 if 条件提取到返回 bool 的公共函数中,然后编写一系列测试来证明在给定正确输入的情况下,该函数返回正确的值。也许特定字符串的生成有一些有趣的逻辑;开始测试吧。
一路上,你可能会发现想要出去的物体。例如,如果有一个单独的类生成单行输出,您可能会发现代码(或测试!)会更简单。随它去吧。如果你搞砸了,你的回归测试就会抓住你。
坚持不懈地消除依赖关系(但请确保您进行了更高级别的测试,例如回归测试,以便在您犯错时及时发现)。如果您的类创建自己的 FileStream 并写入文件系统,请将其更改为在其构造函数中采用 TextWriter,这样您就可以编写传递 StringWriter 的测试,并且永远不会触及文件系统。一旦完成,您就可以摆脱将文件写入磁盘的旧测试(但前提是您在尝试编写新测试时没有破坏它!)如果您的类需要数据库连接,请重构直到您可以编写通过虚假数据的测试。等等。
【讨论】: