关于如何编写重构友好单元TDD测试的提示

时间:2010-01-13 12:35:26

标签: unit-testing refactoring tdd

我已经在ASP.NET MVC项目上工作了大约8个月了。在大多数情况下,我一直在使用TDD,只有在我编写实际代码之后,单元测试才涵盖了某些方面。总的来说,该项目的测试覆盖率非常高。

到目前为止,我对结果非常满意。重构真的更容易,我的测试帮助我在第一次运行我的软件之前发现了很多错误。此外,我开发了更复杂的假货和帮手,以帮助我最小化测试代码。

然而,我真的不喜欢的事实是我经常发现自己必须更新现有的单元测试以解释我对软件所做的重构。重构软件现在快速而轻松,但重构我的单元测试非常乏味和乏味。事实上,维护我的单元测试的成本高于首先编写它们的成本。

我想知道我是否可能做错了,或者这种测试开发与测试维护成本的关系是否正常。我已经尝试过编写尽可能多的测试,以便它们覆盖我的用户故事,而不是像this blog article中建议的那样系统地覆盖我的对象界面。

另外,您是否有关于如何编写TDD测试的进一步提示,以便重构尽可能少地进行测试?

编辑:正如Henning和tvanfosson正确评论的那样,通常设置部分的编写和维护成本最高。破坏的测试(根据我的经验)通常是对域模型进行重构的结果,与那些测试的设置部分不兼容。

7 个答案:

答案 0 :(得分:8)

这是一个众所周知的问题,可以通过根据最佳实践编写测试来解决。这些做法在优秀的xUnit Test Patterns中有所描述。本书描述了导致无法测试的测试气味,并提供了如何编写可维护单元测试的指导。

在长时间跟踪这些模式之后,我写了AutoFixture这是一个开源库,它封装了很多这些核心模式。

它可以作为Test Data Builder使用,但也可以连接起来作为Auto-Mocking容器,并做许多其他奇怪和美妙的事情。

它在维护方面有很大帮助,因为它提高了编写测试的抽象级别。测试变得更加声明性,因为您可以声明您想要某种类型的实例,而不是明确地写 它是如何创建的。

想象一下,你有一个带有这个构造函数签名的类

public MyClass(Foo foo, Bar bar, Sgryt sgryt)

只要AutoFixture可以解析所有构造函数参数,您只需创建一个这样的新实例:

var sut = fixture.CreateAnonymous<MyClass>();

主要的好处是,如果您决定重构MyClass构造函数,那么没有测试会中断,因为AutoFixture会为您解决这个问题。

这只是AutoFixture可以做的一瞥。它是一个独立的库,因此它可以与您选择的单元测试框架一起使用。

答案 1 :(得分:2)

您可能正在编写的单元测试距离您的课程太近。你应该做的是测试公共API。当我指的是公共API时,我并不是指所有课程上的公共方法,我的意思是你的公共控制者。

通过让测试模仿用户与控制器部件交互的方式而不直接触及模型类或辅助函数,您可以自己重构代码而无需重构测试。当然,有时甚至你的公共API都会发生变化,然后你仍然需要改变你的测试,但这种情况不会那么频繁发生。

这种方法的缺点是你经常需要通过复杂的控制器设置才能测试你想要引入的新的小辅助函数,但我认为最终它是值得的。此外,您最终将以更智能的方式组织测试代码,使编写代码更容易编写。

答案 2 :(得分:2)

这篇文章给了我很多帮助:http://msdn.microsoft.com/en-us/magazine/cc163665.aspx

另一方面,没有奇迹方法可以避免重构单元测试。

一切都需要付出代价,如果您想进行单元测试,尤其如此。

答案 3 :(得分:1)

我认为他的意思是,设置部分维护起来非常繁琐。 我们遇到了完全相同的问题,特别是当我们引入新的依赖,拆分依赖或以其他方式更改代码应该如何使用时。

在大多数情况下,当我编写和维护单元测试时,我会花时间编写设置/编排代码。 在我们的许多测试中,我们有完全相同的设置代码,我们有时使用私有帮助器方法来进行实际设置,但使用不同的值。

然而,这不是一件好事,因为我们仍然需要在每次测试中创建所有这些值。因此,我们现在正在研究以更多规范/ BDD样式编写测试,这应该有助于减少设置代码,从而减少维护测试所花费的时间。 您可以查看的一些资源是http://elegantcode.com/2009/12/22/specifications/,以及使用MSpec http://elegantcode.com/2009/07/05/mspec-take-2/进行的BDD测试方式

答案 4 :(得分:1)

大多数时候,我看到这样的重构会影响单元测试的设置,经常涉及添加依赖项或改变对这些依赖项的期望。这些依赖关系可能由后来的功能引入,但会影响早期的测试。在这些情况下,我发现重构设置代码非常有用,以便它由多个测试共享(参数化以便可以灵活配置)。然后,当我需要对影响设置的新功能进行更改时,我只需要在一个地方重构测试。

答案 5 :(得分:0)

当我开始感觉到设置周围的重构痛苦时,我关注的两个方面是让我的单元测试更具体,我的方法/类更小。基本上我发现我正在远离SOLID / SRP。或者我有很多尝试做的测试。

值得注意的是,我尝试远离BDD /上下文规范,远离我得到的UI。测试一个行为是很好的,但总是引导我(也许我做得不对?)更大的混乱测试,更多的上下文规范比我喜欢。

我看到这种情况发生在我身上的另一种方式是代码借记,随着时间的推移逐渐扩展其业务逻辑。当然总是有大的方法和类有多个依赖关系,但是我的“测试重写”越少越少。

答案 6 :(得分:0)

如果您发现自己正在创建涉及Russian dolls等深层对象图的复杂测试脚手架,请考虑重构您的代码,以便Class Under Test在其构造函数/参数中获得所需的内容,而不是让它走在图中

intead of:

public class A {

   public void foo(B b) {
      String someField = b.getC().getD().getSomeField();
      // ...
   }
} 

将其更改为:

public class A {

   public void foo(String someField) {
      // ...
   }
} 

然后您的测试设置变得微不足道。