将TDD回溯应用到C#代码库的最佳选择

时间:2011-10-10 16:40:09

标签: c# tdd mocking mstest legacy-code

我有一个由5个C#库组成的现有框架,该框架自2006年以来使用得很好,并且是我的大多数项目的主要代码库。我公司希望出于软件质量的原因推出TDD;通过许多教程和阅读理论,我了解TDD的好处。

时间不是无限的我需要制定一个务实的方法来制定计划。据我所知,我看到的选项是:

A)可以使用一个测试项目来重叠来自所有5个库组件的对象。一系列高级测试可能是最初被视为非常大的软件库的起点。

B)5个库组件中每个组件的测试项目。这些项目将在最低级别测试功能,与其他库组件隔离。

C)由于代码被广泛认为有效,因此只需对错误修复或新功能添加单元测试。使用重现错误的步骤编写一个测试,该测试在包含错误的逻辑上失败。然后重新计算代码,直到测试通过。现在,您可以确信错误已得到修复,并且以后也不会在循环中引入

无论选择哪个选项,都可能需要“Mocking”来替换外部依赖项,例如:

  • 数据库
  • 网络服务
  • 配置文件

如果有人有任何更多的输入,这将是非常有帮助的。 我计划在Visual Studio 2010中使用Microsoft内置的MSTest。

5 个答案:

答案 0 :(得分:6)

我们有一百五十行代码库。我们的方法是首先编写一些集成测试(您的选项A)。这些测试几乎贯穿整个系统的端到端:它们从存储库复制数据库文件,连接到该数据库,对数据执行某些操作,然后将报告输出到CSV并将它们与已知良好的输出进行比较。它们远非全面,但它们运用了我们的客户依赖我们的软件做的大量事情。

当然,这些测试运行得非常缓慢;但是我们仍然会在六年之后连续运行所有这些(现在分布在八台不同的机器上),因为它们会捕捉到我们仍然没有进行单元测试的东西。

一旦我们有了一个不错的集成测试基础,我们花了一些时间在系统的高流量部分(你的选项B)中添加更精细的测试。我们有时间这样做,因为我们的代码质量很差。

一旦我们将质量提高到一定的门槛,他们就开始要求我们再做一次真正的工作。所以我们选择了为新代码编写测试的节奏(你的选项C)。此外,如果我们需要对尚未进行单元测试的现有代码进行更改,我们可能会花一些时间在开始进行更改之前用测试覆盖现有功能。

您的所有方法都有其优点,但随着时间的推移获得测试覆盖率,相对收益将会发生变化。对于我们的代码库,我认为我们的策略很好;集成测试将有助于捕获您在尝试破坏依赖项以添加单元测试时所犯的任何错误。

答案 1 :(得分:5)

(A)和(B)都不能恰当地被认为是TDD。代码已经写好了;新测试不会驱动其设计。这并不意味着追求其中任何一条路径都没有价值,但将它们视为TDD是错误的。关于“代码被广泛认为是有效的”,我怀疑如果你开始(B),你会发现它有一些漏洞。未经测试的代码几乎总是包含错误。

我的建议是追求(B),因为我发现单元测试比集成测试更有价值(尽管更大的价值在于你为时已晚的设计优势)。集成测试也很有价值,可以告诉你关于代码的不同重要事项,但我喜欢从单元测试开始。选择5个组件中的一个,然后开始编写我们称之为characterization tests的内容。开始发现行为,建立你编写单元测试的经验。选择最容易测试的东西;建立在你学到的东西上,用简单的方法逐步提升以测试棘手的位。在编写这些表征测试时,您几乎肯定会发现令人惊讶的行为。请注意,当然,并考虑是否应该修复它(或修复是否可能破坏依赖于令人惊讶的行为的代码)。

当然,在实现它们的代码之前,为任何新功能或错误修复编写测试。祝你好运!

答案 2 :(得分:3)

根据定义,如果要为现有代码库创建测试,则不是TDD。

我会把C)视为一个给定:每当你有一个bug时,写一个“证明”这个bug的测试,并永远消除它。

我同意Carl Manaster的建议。问题的另一个角度是“经济学”:为遗留应用程序编写测试可能会很昂贵,那么您将在哪里获得最大的收益?考虑a)最常用的类和方法,b)最有可能存在错误的类和方法(通常是代码复杂度最高的类)。

还要考虑使用像Pex和Code Contracts这样的工具,这些工具可以帮助您考虑您没有考虑过的测试,以及代码中可能存在的问题。

答案 3 :(得分:0)

我会选择选项C.尝试围绕非单元测试设计的代码进行单元测试可能是一个重要的时间。我建议只在你复制部分代码时添加测试,即使这样你也可能必须重构该代码以允许它进行单元测试。

在遗留代码上也可能需要考虑集成测试,因为我认为它们比单元测试更容易实现。

答案 4 :(得分:0)

选项A和B不符合TDD的定义,并且非常耗时。我会选择选项C,因为它是最实用的解决方案。