2010-02-11 72 views
25

我最近使用TDD完成了一个项目,发现这个过程有点噩梦。我喜欢先写测试并观察代码的增长,但只要需求开始改变,我开始重构,我发现我花了更多时间重写/修复单元测试,而不是编写代码,实际上需要更多的时间。可维护单元测试

我感觉,当我正在经历这个过程时,在应用程序完成后做测试会容易得多,但如果我这样做了,我会失去TDD的所有好处。

那么有没有关于编写可维护的TDD代码的命中/提示?我正在阅读Roy Osherove的The Art Of Unit Testing,有没有其他资源可以帮助我?

感谢

+0

工具只需修改一个例子,我决定有一天,我深深的代码属性的命名是错误的,所以我把它改成一个更好的。这个属性用于大约30个不同的测试,所以我必须经过每个测试并手动更改,这需要很长时间。我寻找一些实用的命中/提示,像resharper,使用var关键字等。 – 2010-02-11 11:26:14

+2

@Lee Tevail:你是说(a)你的IDE没有全局搜索和替换,(b)TDD是你的IDE的根本原因好可怜? – 2010-02-11 11:39:04

+0

我也不讨厌TDD,我只是想找办法更有效地做事。 – 2010-02-11 11:42:28

回答

16

实践

这需要一段时间来学习如何写像样的单元测试。一个困难的项目(更像项目)并不奇怪。

xUnit Test Patterns已推荐的书很好,我听说过你正在阅读的书的好处。

至于一般建议,它取决于你的测试很难。如果他们经常破产,他们可能不是单元测试,更多的是集成测试。如果它们难以建立,SUT(被测系统)可能显示出过于复杂的迹象,并且需要进一步的模块化。名单继续。

我住的一些建议是遵循AAA规则。

安排,行动和断言。每个测试应该遵循这个公式。这使得测试可读,易于维护,如果它们确实中断时。

设计仍然是重要的

我实践TDD,但任何代码写之前,我抓住一个白板,信手写了。虽然TDD允许您的代码发展,但一些前期设计总是有益的。那么你至少有一个起点,从这里你的代码可以由你写的测试驱动。

如果我正在执行一项特殊的难题,我会制作一个原型。忘掉TDD,忘记最佳实践,只需打开一些代码即可。显然这不是生产代码,但它提供了一个起点。从这个原型开始,我会考虑实际的系统,以及我需要的测试。

查看Google Testing Blog - 这是我开始TDD时的转折点。米斯科的文章(和网站 - 尤其是Guide to Testable代码)非常出色,并且应该指向正确的方向。

+0

+1。喜欢参考米斯科的文章。我认为他们也是我的转折点。 – 2010-02-11 11:27:33

2

是的,有一整本书叫xUnit Test Patterns是处理这个问题。

这是一本Martin Fowler签名书,因此它具有经典图案书的所有图案。无论你喜欢与否,都是个人品味的事情,但我认为它非常宝贵。

无论如何,问题的关键是你应该把你的测试代码当作你的生产代码来对待。首先,您应该坚持原则,因为这样可以更轻松地重构您的API。

2

你是否在自由使用接口,依赖注入和嘲讽?

我发现使用DI框架(例如ninject)设计接口然后注入这些接口的实现使模拟出应用程序的某些部分变得容易很多,这样您就可以单独地正确测试组件。

这样可以更轻松地在一个区域中进行更改,而不会影响其他区域,或者如果需要更改这些更改,则可以更新接口并一次处理每个不同的区域。

7

“一旦需求开始变化,我开始做重构我发现,我花更多的时间重写/定影单元测试”

所以呢?这是一个什么问题?

您的要求已更改。这意味着你的设计必须改变。这意味着你的测试必须改变。

“我花了更多的时间来重写/修改单元测试,而不是写代码,实际上花的时间更多。”

这意味着你正在做它的权利。要求,设计和测试影响都在测试中,您的应用程序不需要太多改变。

这就是它应该工作的方式。

回家快乐。你已经完成了这项工作。

+0

这样的思考正是我不接受TDD的原因。程序员的工作不是编写测试,而是编写有效的代码。如果验证代码比创建实际代码要花费更长的时间,我认为在这个过程中存在严重缺陷。 – erikkallen 2010-02-11 11:31:07

+7

@erikkallen:程序员的工作是“产生工作,良好的代码”*,在其中你完全有信心*。我无法强调足够的信心。测试是提高代码信心的好方法。它*应该花费很长时间来验证代码是否正确。 TDD不会改变这一点。没有什么改变。信心需要很多照顾 - 无论是大量的测试还是精心打造的证明。或两者。 – 2010-02-11 11:37:58

3

这听起来像你的单元测试是脆弱和重叠的。理想情况下,单个代码更改应该只影响一个单元测试 - 对测试与功能进行一对一匹配,其他测试不取决于给定功能。这可能有点过于理想化;在实践中,我们的许多测试都会重新使用相同的代码,但需要牢记。当一个代码更改影响很多测试时,这是一种气味。另外,关于您重命名的具体示例:找到一个工具,可以为您自动执行这些重构。我相信Resharper和CodeRush都支持这种自动重构;它比手动方法更快,更简单,更可靠地进行重构。

为了更好地学习您的IDE,没有什么比与其他人搭档。你们都会学习;你们都会开发新的技能 - 而且不需要很长时间。几个小时将大大增加您对该工具的舒适度。

+0

+1用于推荐配对学习IDE。我喜欢每隔几个星期左右花半天时间,并仅花时间学习关于IDE或其他工具的新知识。长期来看,它会带来巨大的回报。 – 2010-02-15 05:29:38

2

我想你可能想在测试和编码之间取得体面的平衡。

当我开始一个项目时,由于要求和目标一直在改变,所以我根本没有写任何测试,因为正如你所观察的那样,要不断修复测试需要很多时间。有时候我只是在评论中写下“这应该被测试”,以便我不会忘记测试它。

在某个时间点,您感觉您的项目正在形成。这是重度单元测试派上用场的时刻。我尽可能写。

当我开始进行大量的重构工作时,在项目再次安顿下来之前,我对测试不感兴趣。我也放了一些“测试这个”的评论。重构结束后,是时候重新编写所有失败的测试(也许还会抛弃其中的一些测试,当然也会写一些新测试)。

用这种方式编写测试真的很高兴,因为它确信你的项目已经达到了一个里程碑。

5

我是单元测试的狂热粉丝,但在我最近的项目中遇到过TDD(或基本单元测试)的问题。在进行实施后评审后,我发现我们(我和团队的其他成员)在实施/理解TDD和单元测试时遇到了两个主要问题。

第一个问题是我们面临的问题是我们并不总是把我们的测试当成一等公民。我知道这听起来像是我们反对TDD的哲学,但是在我们完成了大部分最初的设计之后,我们的问题就出现了,并且急于进行实时更改。不幸的是,由于时间限制,项目的后期部分变得匆忙,我们陷入了编写代码之后编写测试的陷阱。当压力安装的工作代码被检查到源代码控制,而不检查单元测试是否仍然通过。诚然,这个问题与TDD或单元测试没有任何关系,而是紧迫期限,平均团队沟通和糟糕领导力的结果(我要在这里责怪自己)。

当对失败的单元测试进行更深入的研究时,我们发现测试过多,特别是考虑到时间限制。我们没有使用TDD并将测试集中在高回报的代码上,而是使用TDD并为整个代码库编写测试。这使得我们的单元测试代码的比例比我们可以维护的代码高得多。我们(最终)决定只使用TDD并编写可能会改变的业务功能的测试。这减少了我们维持大部分测试的需要,这些测试大部分很少(或从不)改变。相反,我们的努力更专注于为我们真正关心的应用程序的各个部分提供更全面的测试套件。

希望能从我的经验中学习,并继续开发TDD或至少仍然为您的代码开发单元测试。就我个人而言,我发现以下链接对帮助我理解诸如选择性单元测试等概念非常有用。

1

首先,重构不破的单元测试。你有申请the book的重构吗?这可能是因为你的测试正在测试实现而不是行为,这可能解释了它们为什么会崩溃。

单元测试应该是黑匣子测试,测试单元做什么,而不是测试它是如何做的。

0

你使用的是一个好的IDE吗?当我第一次接受单元测试时,我正在问自己和几年前一样的问题。那时候,我用Emacs,findgrep的组合来做重构。这很痛苦。

值得庆幸的是,一位同事让我感到头痛,并说服我尝试使用“现代工具”,他用白话意思是Intellij IDEA。 IDEA是我个人的偏好,但Netbeans或Eclipse也会处理基本知识。这很难夸大我提供的生产力收益;轻松获得一个数量级的增益,特别是对于有大量测试的大型项目。

一旦你有一个IDE平方了,如果你是仍然运行到的问题是时候考虑DRY principle,其目的是确保信息只保存在一个地方(不变,属性文件等),所以如果你以后需要改变它,波纹效应会被最小化。

0

如果你的测试是很难维持的,这是一个迹象,表明你的代码是脆弱的。这意味着班级的定义经常发生变化。

考虑定义为调用它使一个注入接口的类的责任。在传递数据,发送消息而不是操纵状态方面考虑更多。

1

我发现一个Java的半自动化的开发人员测试工具命名为SpryTest。它提供了一个简单但功能强大的UI来创建随机数据。它还支持使用powermock和easymock模拟电话。它可以生成最接近手写测试的标准JUnit测试。它具有测试 - >源代码同步功能。

尝试过了并且工作非常适合我。看看在http://www.sprystone.com