在测试/原型设计过程中,单元测试是个坏主意吗?

时间:2009-08-13 12:39:51

标签: unit-testing testing tdd nunit automated-tests

我们开始的一个新项目引入了许多我们不熟悉的新技术,以及我们没有很多实践的架构。换句话说,服务类之间的接口和交互等由于内部和客户的反馈,我们正在建设的东西相当不稳定,更是如此。虽然我一直对不断变化的规格感到沮丧,但我认为这在某种程度上是构建我们以前从未构建的东西的必要部分 - 如果我们只是坚持原始设计和范围,最终产品可能会比它成为一个创新和有用的要少得多。

我还介绍了测试驱动开发(TDD),因为这些好处都有详细记录,从概念上讲我喜欢这个想法。还有两个要学习的新东西--NUnit和嘲讽 - 但是看到所有这些绿色圆圈都让它变得有价值。

然而,随着时间的推移,那些不断变化的设计似乎意味着我花了很多时间来改变我的测试,而不是编写代码本身。仅仅因为这个原因,我已经回到了旧的测试方式 - 也就是说,不是自动化的。

虽然毫无疑问,通过数百次优秀的单元测试,该应用程序将更加强大,但我发现推出该产品的时间权衡大多是不可接受的。我的问题是,那么 - 你们是否还发现如果你正在构建一个测试版/建立测试版,那么TDD会很麻烦? TDD是否更加自然地与规范更加固定的东西,或者开发人员在语言和技术方面拥有更多经验的东西相结合?或者我做了一些根本错误的事情?

请注意,我并不想在这里批评TDD - 只是我不确定它总是最适合所有情况。

15 个答案:

答案 0 :(得分:11)

简短的回答是TDD对于beta版本非常有价值,但可能对于原型设计不那么重要。

我认为区分测试版和原型是非常重要的。

beta 版本本质上是一个仍处于开发阶段的生产版本,所以你绝对应该在这种情况下使用TDD。

原型 /概念验证是您构建的内容,一旦您从中获得了所需的答案,就会将其抛弃。

项目经理确实会倾向于将原型作为生产代码的基础,但要抵制它是非常重要的。如果你知道那是不可能的,那就像处理你的生产代码那样处理原型代码,因为你知道 将来会成为你的生产代码 - 这意味着你应该使用TDD作为生产代码好。

当您学习新技术时,大多数代码示例等都没有考虑单元测试,因此很难将新技术转换为单元测试思维模式。它绝对感觉像很多开销。

但是,根据我的经验,单元测试通常会迫使您突破您正在学习的新技术的界限。通常,您需要研究和学习新技术提供的所有不同的钩子,因为您需要能够通过DI等来隔离技术。

单元测试经常迫使您更深入地学习技术,而不仅仅是遵循常规路径,因此可能感觉开销实际上只是一个更深入的原型 - 通常更有价值,因为它涵盖更多的基础。

就个人而言,我认为单元测试新技术是一种很好的学习工具。

我认为您在测试可维护性方面遇到的症状有点正交。您的测试可能 Overspecified ,这在使用已知技术时也会发生(但我认为,当您使用时,它可能更容易陷入此陷阱同时也学习新技术。)

本书xUnit Test Patterns描述了Overspecified Test反模式,并提供了许多指导和模式,可以帮助您编写更易于维护的测试。

答案 1 :(得分:10)

我发现彻底测试早期的结果会丢掉很多代码,并且在你的胃里感觉空虚。

测试需要测试的内容,而不是更多代码。当你弄清楚那是多少时,请告诉我。

答案 2 :(得分:6)

在进行原型设计时,我会说这取决于原型的类型。在进化原型设计中,原型演变为最终应用,我会尽早使用单元测试。如果您正在使用一次性原型设计,我不会打扰单元测试 - 最终的应用程序将与原型完全不同。

我不确定“beta”是什么意思,因为beta几乎是一个成品。一旦您开始处理将要发货或有可能发货的代码,请确保所有内容都经过充分测试。

现在,纯粹的测试驱动开发可能是极端的,但确保所有可交付代码在单元,集成和系统级别上尽可能经过测试非常重要。

答案 3 :(得分:5)

  

有没有人发现,如果您正在构建测试版的某些原型,那么TDD会很麻烦吗?

我有......很多次:)

  

TDD是否更加自然地与规范更加固定的东西,或者开发人员在语言和技术方面拥有更多经验的东西相结合?

不是真的。 TDD非常适合不断变化的需求,但TDD确实是为了确保稳定和契约驱动的设计:两个原型并不真正需要 的东西......

  

或者我做过一些根本错误的事情?

看起来不像:)你刚刚看到TDD包含除了金树之外的其他东西..

答案 4 :(得分:4)

“然而,随着时间的推移,那些不断变化的设计似乎意味着我花了很多时间来改变我的测试,而不是编写代码本身”

好。您花费大量时间进行测试。这很重要,而且你是如何证明你的代码是正确的。 “代码少于测试”是一个很好的基准。

这意味着您正在撰写有效的测试,以证明您对基础技术的期望。

您可能需要考虑这样做。

  1. 某些测试是应用程序的“必要”或“核心”或“持久”功能,与任何技术选择无关。专注于这些。永远不应该改变。

  2. 某些测试确认了技术或实施选择。这些一直在变化。也许你应该将这些分开,以便技术变化导致这里的重点变化。

答案 5 :(得分:4)

  • 给出带有移动X的路线图令人沮丧。 TDD或没有TDD ..'不得不花费大部分时间来改变测试而不是代码'表明规格是根本改变的,或者你只是过度嘲笑自己a.k.a“脆弱的测试”。我需要你提供更多意见以进一步评论。
  • Spike / Prototyping意味着尝试构建一些东西作为概念证明或验证设计。有了这个定义,我会说你不需要TDD你的原型,因为你的主要目标是学习/减少未知数。完成后,您应该丢弃原型并使用自动化测试构建生产版本(现在使用TDD)。您将这些邮件发送给客户而不是“测试原型”
  • 但是,如果手动测试一直很适合您,请坚持下去。我喜欢向自己和其他人证明我的代码只需按一下按钮即可工作 - 避免重复任务的人为无聊并进行全面测试。

运输原型会比你想象的更快,更难咬你。从我这里拿走。

答案 6 :(得分:3)

原型设计意味着用于“这种事情会起作用吗” - 探索 所以不需要测试。 但是!总是抛弃你的原型并从零开始编码!

答案 7 :(得分:2)

在敏捷开发中,存在“尖峰”的概念 - 对解决方案或技术的深入但狭隘的调查。一旦你对事情的运作方式感到满意,你就可以重新开始使用更高质量的新实现。

标有“beta”的软件存在一个陷阱 - 突然之间你最终得到了一些不适合生产的东西作为你应用程序的重要部分而且你没有时间重做它。这很可能会回来后再咬你。一个原型应该只是一个原型 - 不多也不少。

答案 8 :(得分:2)

我没有处方,但我确实有诊断:

  • 如果您最终进入调试器,则应该进行更多测试。即使在最早的原型中,您也希望您编写的代码能够正常工作,对吧?如果我不做TDD,并且我遇到了一个bug,那么很难改进单​​元测试以找到bug。很容易去调试器。所以,我的目标是我的TDD努力产生一组足够好的测试,这样我就不需要调试器了。做得好这需要很多练习。

  • 如果你想重构而不是因为风险,你应该有更多的测试。如果我要使用一些代码超过几个小时,我想重构以保持我的速度高。如果我犹豫重构,那将会影响我的工作效率,所以我尽我所能使重构变得安全和轻松。同样,我正是我需要的一系列测试。

答案 9 :(得分:1)

对于纯粹的原型设计,正如人们所说,不那么有用。但是,原型通常会包含应用程序的元素。

在构建它时,您期望继续的可靠概念是什么?什么是关键域模型?围绕这些模型构建测试是有意义的。然后,它们为探索想法提供了坚实的基础。 通过让代码库的一部分可靠并经过测试,它可以让您在没有测试的情况下进一步使用原型。

选择合适的东西来测试时间总是平衡的。从您的描述中可以看出,您同时采用了很多新事物 - 有时这可能太多了,您需要稍微退缩以优化您的进度。

答案 10 :(得分:0)

我通常为这个原型代码做的是在没有测试的情况下编写它(或者不是很多),但是在我的src / test / java下或者在我放置测试代码的地方工作。这样,我不会无意中投入生产。如果有我喜欢的原型,那么我将在src / main / java(我的杆代码)和TDD中创建一些东西,在我添加测试时,一次从原型中提取代码。

答案 11 :(得分:0)

  

然而,随着时间的推移,这些设计的不断变化似乎意味着我花了很多时间来改变我的测试,而不是编写代码本身。

我编写(并运行)系统I / O的自动测试。系统的I / O取决于功能要求,并且在系统实现发生变化时不会改变,因此我可以在不改变自动化测试的情况下重构实现:Should one test internal implementation, or only test public behaviour?

答案 12 :(得分:0)

当我接触到一项新技术时,我通常会编写几个测试作为原型的基础。这里的主要优点是它允许我习惯于在易消化的小部分中使用该技术,并且我用唯一有效的文档形式记录了我的进展:代码。

这样,我可以测试假设(当我在数据库中保留空字符串时会发生什么?DB2将返回一个空字符串,Oracle将返回NULL)并确保它们在我处理原型时不会更改。

想想这项工作,许多小型原型都进行了几次单元测试。

现在我的产品设计会随着时间而改变,但这些加标测试仍然有效:基础框架不会因为我现在追求不同的解决方案而改变。实际上,这些测试将允许我迁移到下一版本的技术,因为当我使用的一个假设发生变化时,它们会发出警报。否则,我必须保持当前版本或者对任何升级都非常小心,因为我无法确定它会破坏它。

答案 13 :(得分:0)

在原型设计时,我会对解雇TDD持谨慎态度。主要是因为原型倾向于(但不总是)演变成最终产品。如果你可以确定原型在它们已经建立起来之后就被抛弃了,那么你可能会有点松懈。但是,如果原型有可能演变成最终产品,或者部分代码将被移植到最终产品中,那么将尽力遵循TDD原则。

这不仅使您的代码更易于测试,更重要的是(在我看来)它鼓励您遵循良好的软件设计原则。

答案 14 :(得分:0)

如果它实际上是一个原型而且你要在完成后扔掉它,那么采取任何途径让你通过那个阶段。但是,问问自己,你的原型实际上被扔掉的频率是多少?它们经常会侵入你的最终产品?

在TDD上JavaOne上有一个很好的presentation。其中一个非常好的结果是,当你不得不编写测试来满足它们时,你往往会更好地理解你的实际需求。这是该方法带来的所有重构和代码质量优势的补充。