我最近使用TDD完成了一个项目,我发现这个过程有点像噩梦。我喜欢先编写测试并观察我的代码增长但是一旦需求开始变化并且我开始进行重构,我发现我花了更多时间重写/修复单元测试,而不是编写代码,事实上更多的时间。
我觉得在我完成这个过程的过程中,在应用程序完成后进行测试要容易得多,但如果我这样做,我将失去TDD的所有好处。
那么编写可维护的TDD代码有什么命中/提示吗?我正在阅读Roy Osherove的The Art Of Unit Testing,还有其他资源可以帮助我吗?
由于
答案 0 :(得分:17)
<强>实践强>
学习如何编写体面的单元测试需要一段时间。一个困难的项目(更像是项目)并不奇怪。
推荐的xUnit Test Patterns书很好,我听说你正在阅读的这本书很好。
至于一般建议,它取决于你的测试很难。如果他们经常破坏,他们可能不是单元测试,更多的是集成测试。如果它们难以设置,SUT(被测系统)可能会显示过于复杂的迹象,需要进一步模块化。名单还在继续。
我所遵循的一些建议是遵循AAA规则。
安排,行动和断言。每项测试都应遵循此公式。这使得测试可读,并且在它们确实破坏时易于维护。
设计仍然很重要
我练习TDD,但在编写任何代码之前,我抓住了白板并随意涂鸦。虽然TDD允许您的代码发展,但一些预先设计始终是一个好处。那么你至少有一个起点,从这里你的代码可以由你编写的测试驱动。
如果我执行一项特殊的艰巨任务,我会制作原型。忘记TDD,忘记最佳实践,只是抨击一些代码。显然这不是生产代码,但它提供了一个起点。从这个原型我然后考虑实际的系统,以及我需要的测试。
查看Google Testing Blog - 这是我开始使用TDD时的转折点。 Misko的文章(以及网站 - 特别是Guide to Testable代码)非常出色,应该指出正确的方向。
答案 1 :(得分:7)
“一旦需求开始变化,我开始进行重构,我发现我花了更多时间重写/修复单元测试”
所以?这是怎么回事?
您的要求已更改。这意味着你的设计必须改变。这意味着你的测试必须改变。
“我花了更多时间重写/修复单元测试,而不是编写代码,事实上更多时间。”
这意味着你做对了。要求,设计和测试影响都在测试中,您的应用程序不需要太多更改。
这就是它的工作方式。
快乐回家。你已经完成了这项工作。
答案 2 :(得分:5)
我是单元测试的忠实粉丝,但在我最近的项目中遇到过TDD(或基本单元测试)的问题。在进行实施后审核后,我发现我们(我和团队的其他成员)在实施/理解TDD和单元测试时面临两个主要问题。
第一个问题是我们面临的是,我们并不总是将我们的测试视为一等公民。我知道这听起来好像我们反对TDD的哲学,但我们的问题是在我们完成了大部分初始设计并且急于进行即时更改之后出现的。不幸的是,由于时间限制,项目的后期部分变得匆忙,我们陷入了编写代码后编写测试的陷阱。当压力安装的工作代码被检查到源控制而不检查单元测试是否仍然通过。不可否认,这个问题与TDD或单元测试无关,而是紧迫的截止日期,平均团队沟通和糟糕的领导(我会在这里责怪自己)的结果。
在深入研究失败的单元测试时,我们发现我们测试的太多了,特别是考虑到我们的时间限制。我们使用TDD并为整个代码库编写测试,而不是使用TDD并将测试重点放在高回报的代码上。这使得我们的单元测试比例比我们维持的要高得多。我们(最终)决定只使用TDD并为可能发生变化的业务功能编写测试。这减少了我们维持大量测试的需要,这些测试在很大程度上很少(或从未)改变。相反,我们的努力得到了更好的关注,并为我们真正关心的应用程序部分提供了更全面的测试套件。
希望可以从我的经验中学习并继续开发TDD,或者至少仍然可以为您的代码开发单元测试。就个人而言,我发现以下链接对于帮助我理解选择性单元测试等概念非常有用。
答案 3 :(得分:3)
听起来你的单元测试很脆弱且重叠。理想情况下,单个代码更改应仅影响一个单元测试 - 测试与功能的一对一匹配,其他测试不依赖于给定功能。这可能有点太理想化了;在实践中,我们的许多测试都会重新运用相同的代码,但这是需要牢记的。当一个代码更改影响许多测试时,它就是一种气味。此外,关于您重命名的具体示例:找到一个工具,为您自动化这些重构。我相信Resharper和CodeRush都支持这种自动重构;与手动方法相比,它是一种更快,更容易,更可靠的重构方法。
为了更好地学习您的IDE,没有什么比与其他人配对更好的了。你们都会学习;你们都会发展新技能 - 而且不需要很长时间。几个小时将大大提高您使用该工具的舒适度。
答案 4 :(得分:2)
是的,有一本名为xUnit Test Patterns的书来处理这个问题。
这是一本Martin Fowler签名书,因此它具有经典图案书的所有特征。无论你喜欢与否,这都是个人品味的问题,但我个人认为这非常宝贵。
无论如何,问题的关键在于您应该将测试代码视为您的生产代码。首先,您应该遵循 DRY 原则,因为这样可以更轻松地重构您的API。
答案 5 :(得分:2)
您是否自由地使用接口,依赖注入和模拟?
我发现设计接口然后使用诸如ninject之类的DI框架注入这些接口的实现使得模拟应用程序的各个部分变得容易得多,以便您可以单独正确地测试组件。
这样可以更容易地在一个区域中进行更改,而不会过多地影响其他区域,或者如果需要传播更改,您可以更新接口并一次处理每个不同的区域。
答案 6 :(得分:2)
我认为您可能希望在测试和编码之间取得适当的平衡。
当我开始一个项目时,由于要求和目标一直在变化,我几乎没有写任何测试,因为正如你所观察到的那样,需要花费很多时间来不断修复测试。有一段时间我只是在写评论“这应该被测试”,这样我就不会忘记测试它。
在某些时候,您感觉您的项目正在形成。那是重型单元测试派上用场的时刻。我写的尽可能多。
当我开始进行大量重构时,在项目再次安定下来之前,我对这些测试并不感兴趣。我还提出了一些“测试这个”的评论。当重构结束时,是时候重写所有失败的测试(也许会抛弃其中的一些,当然也会写一些新的测试)。
以这种方式编写测试真的很愉快,因为它知道您的项目已达到里程碑。
答案 7 :(得分:1)
首先,重构不会破坏单元测试。您是否按the book申请重构?可能是您的测试正在测试实现而不是行为,这可以解释它们为什么会破坏。
单元测试应该是黑盒测试,测试单元的功能,而不是它是如何做的。
答案 8 :(得分:1)
我找到了一个名为 SpryTest 的Java半自动化开发人员测试工具。它提供了一个简单但功能强大的UI来创建随机数据。它还支持使用powermock和easymock进行模拟调用。它可以生成最接近手写测试的标准JUnit测试。它具有测试 - >源代码同步功能。
试了一下,对我来说效果很好。在http://www.sprystone.com
查看该工具答案 9 :(得分:0)
您使用的是IDE吗?几年前,当我第一次接受单元测试时,我问自己和你一样的问题。那时候,我正在使用Emacs
,find
和grep
的组合进行重构。这很痛苦。
值得庆幸的是,一位同事让我头疼,并说服我尝试使用“现代工具”,用他的白话意味着Intellij IDEA。 IDEA是我个人的偏好,但Netbeans或Eclipse也将处理基础知识。很难夸大这给我带来的生产力提升;很容易获得一个数量级的增益,特别是对于有大量测试的大型项目。
一旦你的IDE被平仓,如果你仍然遇到问题,那么就应该考虑DRY principle,这是为了确保信息只保存在一个地方(常量,属性文件等)如果你以后需要更改它,涟漪效应会被最小化。
答案 10 :(得分:0)
如果您的测试难以维护,则表明您的代码很脆弱。这意味着班级的定义经常发生变化。
考虑将类的职责定义为对注入的接口进行的调用。在传递数据或发送消息而不是操纵状态方面更多地考虑它。