2010-01-13 41 views
12

我一直在ASP.NET MVC项目工作了8个月。大多数情况下,我一直在使用TDD,但是在写完实际的代码之后,有些方面才被单元测试所覆盖。总的来说,这个项目的测试覆盖率很高。关于如何编写重构友好的单元TDD测试技巧

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

不过,我真的不喜欢的是事实,我经常发现自己不得不更新现有的单元测试来占我的软件做的重构。重构软件现在是快速和无痛的,但重构我的单元测试是相当无聊和乏味的。实际上,维护我的单元测试的成本要高于首先编写它们的成本。

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

另外,你对如何编写TDD的测试,使重构的假期,因为一些测试尽可能任何进一步的提示吗?

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

+1

你可以举一个具体的例子来说明你的代码是如何重构你的单元测试的?虽然有时会发生这种情况,但我个人并不认为真正的“单元”测试应该受到重构的影响(除了移动一些测试和/或为编写代码编写新测试之外)。我意识到这不是一个非常有用的答案,我希望具体的例子可能会导致一个。 – kdgregory 2010-01-13 12:40:02

回答

8

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

在长期遵循这些模式之后,我写了一个开源的库,它封装了很多这些核心模式。

它可以作为Test Data Builder使用,但也可以作为Auto-Mocking容器工作,并执行许多其他奇怪而美妙的事情。

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

试想一下,你有这个构造函数签名

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

只要AA级为AutoFixture可以解决所有构造函数的参数,你可以简单地创建一个新的实例是这样的:

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

的主要的好处是,如果您决定重构MyClass构造函数,则不会有任何测试中断,因为AutoFixture会为您解决问题。

这只是AutoFixture可以做什么的一瞥。它是一个独立的库,所以它可以与您的单元测试框架一起工作。

+0

非常感谢,我会看看! – 2010-01-13 13:28:22

+1

@Mark Seemann谢谢你的回答。提到xUnit测试模式书对我来说特别有用。这看起来正是我要找的东西:我一定要订购一份。 – phuibers 2011-08-23 07:57:11

1

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

在大多数情况下,当我编写和维护的单元测试,我把我的时间以书面形式设置/安排代码。 在我们的许多测试中,我们都有完全相同的设置代码,而且我们有时使用专用帮助程序方法来执行实际设置,但使用不同的值。

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

2

您可能会将单元测试编写得离您的课程太近。你应该做的是测试公共API。当我指的是公共API时,我不是指所有类的公共方法,我的意思是你的公共控制器。

通过让测试模仿了用户如何将与您的控制器部分相互作用而没有触及你的模型类或辅助功能直接,你让自己重构你的代码,而无需重构你的测试。当然,有时候甚至是你的公共API改变,然后你仍然需要改变你的测试,但这种情况发生的频率会更低。

这种方法的不足之处在于,您经常需要通过复杂的控制器设置才能测试您想要介绍的新辅助函数,但我认为最终它是值得的。此外,您最终将以更智能的方式组织您的测试代码,从而使设置代码更易于编写。

+0

你的回答是正确的,但它总结了我已经引用的博客文章... – 2010-01-13 12:58:04

+0

然后,你必须做错了什么,因为不,当你重构你的代码时,你不应该经常重构你的测试。 – 2010-01-13 13:05:20

+0

我没有经常说。但我确实发现维护成本超过了创建tdd测试的成本。你的经历有什么不同? – 2010-01-13 13:12:54

2

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

在另一方面,没有奇迹的方法来避免重构单元测试。

一切都伴随着价格,尤其是如果你想做单元测试的话。

+0

感谢您的文章,我会看看。 我当然知道所有东西都是有代价的,我想知道为什么人们抱怨单元测试过于乏味,无法在TDD的真正意义上讨论,而我花更多的时间来维护它们而不是写它们。 – 2010-01-13 12:53:27

1

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

+1

我也通过添加假对象工厂来做类似的事情。我也有一个基础测试类,负责依赖注入。有时我也有一个由多个测试共享的设置方法。我想这就是重构设置代码的意思吗? – 2010-01-13 12:57:03

0

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

值得注意的是,我尽量远离BDD/context规范,远离我所获得的UI。测试一个行为是很好的,但总是会让我(也许我没有做对吗?)更大的messier测试,更多的上下文规范比我喜欢。

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

0

如果您发现自己创建涉及像Russian dolls这样的深度对象图的复杂测试脚手架,请考虑重构您的代码,以便受测试类完全获取其构造函数/参数中所需的内容,而不是让它走过图形。

的这一翻译:

public class A { 

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

将其更改为:

public class A { 

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

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