【问题标题】:Does YAGNI also apply when writing tests?YAGNI 是否也适用于编写测试?
【发布时间】:2009-06-03 15:44:56
【问题描述】:

当我写代码时,我只写我需要的函数。

这种方法是否也适用于编写测试?

我应该提前为我能想到的每个用例编写测试以确保安全,还是应该只为遇到的用例编写测试?

【问题讨论】:

  • WTF是YANGI?是不是类似于 TMTOWTDI?
  • @darthcoder: YAGNI = 你不需要它。
  • 不,比尔,它是“不是”。真的,听起来好多了。

标签: unit-testing language-agnostic tdd yagni


【解决方案1】:

我认为当你编写一个方法时,你应该测试预期和潜在的错误路径。这并不意味着您应该扩展您的设计以涵盖所有潜在用途——将其留到需要的时候,但您应该确保您的测试已经定义了面对无效参数或其他条件时的预期行为。

据我了解,YAGNI 意味着您不应该开发还不需要的功能。从这个意义上说,您不应该编写一个测试来驱使您开发不需要的代码。不过,我怀疑这不是您要问的。

在这种情况下,我会更关心您是否应该编写涵盖意外用途的测试——例如,由于传递 null 或超出范围的参数而导致的错误——还是重复仅在数据方面有所不同的测试,不是功能。在前一种情况下,正如我上面指出的,我会说是的。您的测试将记录您的方法在遇到错误时的预期行为。这对于使用您的方法的人来说是重要的信息。

在后一种情况下,我无法给你一个明确的答案。你当然希望你的测试保持 DRY —— 不要编写一个简单地重复另一个测试的测试,即使它有不同的数据。或者,除非您使用数据的边缘情况,否则您可能不会发现潜在的设计问题。一个简单的例子是计算两个整数之和的方法:如果将maxint 作为两个参数传递会发生什么?如果您只有一个测试,那么您可能会错过这种行为。显然,这与上一点有关。只有您才能确定何时真正需要进行测试。

【讨论】:

    【解决方案2】:

    是的,YAGNI 绝对适用于编写测试。

    例如,我不编写测试来检查任何属性。我假设属性以某种方式工作,直到我找到一个与常规不同的东西之前,我不会对它们进行测试。

    您应该始终考虑编写任何测试的有效性。如果编写测试对您没有明显的好处,那么我建议您不要这样做。然而,这显然是非常主观的,因为你可能认为不值得别人认为非常值得付出努力。

    另外,我会编写测试来验证输入吗?绝对地。但是,我会在一定程度上做到这一点。假设您有一个带有 3 个整数参数的函数,它返回一个双精度值。您将围绕该功能编写多少测试。我会在这里使用 YAGNI 来确定哪些测试会给你带来良好的ROI,哪些是无用的。

    【讨论】:

    • 如何编写测试以测试您从未打算使用的函数的使用。你会测试它以确保它不会在奇怪的情况下中断,还是你只是说 YAGNI?
    • +1 - 也不需要测试 simple setter 和 getter。我什至看过测试来检查构造函数是否返回了正确类型的对象。如果失败了你能做什么? :)
    • 如果你有一个新的用例需要以一种新颖的方式使用一个函数 - 编写那个新的测试。如果测试失败,请修复该功能。现有测试将确保您不会破坏现有代码。
    • 显然,我在下面的回答存在一些常识性限制,我认为 Joseph 在这里很好地谈到了这一点。通常,可以跳过代码的属性和某些其他“琐碎”方面——假设它们真的很琐碎——可以跳过。 (同样,覆盖并不是全部——确保你的测试彻底覆盖了所有的逻辑分支。)
    • Bill the Lizard:我实际上可以看到在某些语言(例如 Objective-C)中对“构造函数”(好吧,初始化方法)进行单元测试的案例。实际上,这些方法有可能返回 nil(ObjC 相当于 null/Nothing)...... 是的,你不能做太多,但它确实告诉你你的初始化程序完全被水洗了,你最好去修复危险的东西!
    【解决方案3】:

    根据需要编写测试。测试就是代码。预先编写一堆(最初失败的)测试会破坏 TDD 的红色/修复/绿色循环,并且更难识别有效失败与未编写的代码。

    【讨论】:

    • 失败的功能和未编写的代码有区别吗?
    • 在 TDD 中,唯一的测试失败应该是对您即将编写的代码的测试,然后您编写使测试通过的代码。需要解决任何其他回归问题。
    【解决方案4】:

    您应该为要在此开发阶段实施的用例编写测试。

    这有以下好处:

    1. 您的测试有助于定义此阶段的功能。
    2. 你知道你什么时候完成了这个阶段,因为你所有的测试都通过了。

    【讨论】:

      【解决方案5】:

      理想情况下,您应该编写涵盖所有代码的测试。否则,您的其余测试将失去价值,最终您将反复调试那段代码。

      所以,不。 YAGNI 不包括测试:)

      【讨论】:

      • 这是一个美好的世界。天空是什么颜色的?
      • 世界不完美并不意味着我们应该停止追求完美。
      • 好吧.. 假设我写了一个对一个对象做了一些事情的测试。如果它有效,那么我知道该类有效。这意味着我已经介绍了构造函数和属性,例如。我不是建议我们为每一个诀窍都写一个测试。它应该简单地涵盖所有代码。如果您不测试所有代码,那么您就没有很好地使用 TDD。从长远来看,您只会浪费时间。
      • 实际上,追求完美正是我们应该停止做的事情,因为它比生活在剩余的不完美中付出的代价更大。
      【解决方案6】:

      当然,为您完全不确定是否会实现的用例编写测试毫无意义——这对任何人来说都应该是显而易见的。

      对于您知道将要实现的用例,测试用例的收益会递减,即,当您可以用一半的工作覆盖所有重要和关键路径时,试图覆盖每一个可能的晦涩角落案例并不是一个有用的目标 -当然,假设忽略一个很少发生的错误的成本是可以承受的;在编写航空电子软件时,我当然不会满足于低于 100% 的代码和分支覆盖率。

      【讨论】:

      • 你什么时候会为一个晦涩的用例编写测试?什么时候第一次添加方法,或者在具体场景中实际去使用的时候?
      • 通常,“用例”是比方法更高层次的概念。但一般而言,TDD 要求您在实现特定功能之前为其编写测试。在您第一次添加方法之前,请编写一个测试,以确保它执行当时应该执行的操作。扩展其功能时,请先扩展或添加测试。
      【解决方案7】:

      您可能会在这里遇到一些差异,但通常,编写测试(对我而言)的目标是确保您的所有代码都按应有的方式运行,没有副作用,以可预测的方式并且没有缺陷。那么,在我看来,您讨论的仅针对遇到的用例编写测试的方法对您没有真正的好处,实际上可能会造成伤害。

      如果您忽略的被测单元的特定用例导致最终软件出现严重缺陷怎么办?在这种情况下,除了错误的安全感之外,花在开发测试上的时间给你带来了什么吗?

      (作为记录,这是我在使用代码覆盖率来“衡量”测试质量时遇到的问题之一——这个衡量标准如果低,可能表明您没有进行足够的测试,但如果高,不应该用来假设你是坚如磐石的。测试常见情况,测试边缘情况,然后考虑单元的所有 if、ands 和 buts 并测试它们。)

      轻度更新

      我应该注意,我的观点可能与这里的许多人不同。我经常发现我正在编写库风格的代码,也就是说,将在多个项目中为多个不同的客户重用的代码。因此,我通常不可能肯定地说某些用例根本不会发生。我能做的最好的事情是记录它们不是预期的(因此可能需要在之后更新测试),或者 - 这是我的偏好:) - 只是编写测试。我经常发现选项 #2 更适合日常使用,这仅仅是因为我在新应用程序 Y 中重用组件 X 时更有信心。在我看来,自动化测试是什么一切。

      【讨论】:

      • 但是如果用例从未出现在软件中怎么办?然后它通过或失败的事实是无关紧要的,事实证明我浪费时间试图让我不需要的东西通过测试。
      • 如果您正在测试真正不可能的分支,那么是的,您处于 YAGNI 世界中。但是您确实需要记住,您今天编写的代码也将在未来使用,并且您认为永远不会发生的用例可能会在一年后发生。最终,我不得不同意 tvanfosson:只有你知道你真正需要测试什么代码。只要确保彻底这样做。 :)
      【解决方案8】:

      您当然应该推迟为您还不会实现的功能编写测试用例。测试应该只针对现有功能或您将要加入的功能编写。

      但是,用例与功能不同。您只需要测试您已确定的有效用例,但可能会发生很多其他事情,并且您希望确保这些输入得到合理的响应(很可能是错误消息) .

      显然,您不会获得所有可能的用例;如果可以,则无需担心计算机安全性。您应该至少获得更合理的那些,并且当出现问题时,您应该将它们添加到用例中进行测试。

      【讨论】:

        【解决方案9】:

        我认为这里的答案是,因为它在很多地方都是如此,这取决于。如果一个函数提供的合约声明它执行 X,并且我看到它有相关的单元测试等,我倾向于认为它是一个经过良好测试的单元并按原样使用它,即使我没有在其他地方以完全相同的方式使用它。如果该特定使用模式未经测试,那么我可能会遇到令人困惑或难以追踪的错误。出于这个原因,我认为测试应该涵盖一个单元的所有(或大部分)已定义、记录在案的行为。

        如果您选择以增量方式进行测试,我可能会在文档中添加该函数“仅针对 [某些类型的输入] 进行测试,其他输入的结果未定义”。

        【讨论】:

          【解决方案10】:

          我经常发现自己正在为我不希望正常程序流调用的情况编写测试,TDD。 “假装直到你做到”的方法让我开始,通常,输入一个空值 - 足以让我知道函数调用应该是什么样子,它的参数将具有什么类型以及它将具有什么类型返回。需要明确的是,我不会只在我的测试中向函数发送 null ;我将初始化一个类型化变量以保存空值;这样当 Eclipse 的 Quick Fix 为我创建函数时,它就已经有了正确的类型。但是我并不期望程序通常会向函数发送空值,这种情况并不少见。所以,可以说,我正在编写一个我 AGN 的测试。但如果我从价值观开始,有时它太大了。我从一开始就在设计 API 并推动其真正的实现。因此,通过开始缓慢并假装它直到我成功,有时我会为我不希望在生产代码中看到的情况编写测试。

          【讨论】:

            【解决方案11】:

            如果您以 TDD 或 XP 风格工作,则不会像您所说的那样“提前”编写任何内容,您将在任何特定时刻处理非常精确的功能,所以您将编写所有必要的测试,以确保该部分功能按您的预期工作。

            测试代码与“代码”本身类似,您不会为应用程序的每个用例都提前编写代码,那为什么要提前编写测试代码呢?

            【讨论】:

              猜你喜欢
              • 2015-11-23
              • 2022-06-10
              • 1970-01-01
              • 2020-09-04
              • 1970-01-01
              • 2019-02-06
              • 1970-01-01
              • 1970-01-01
              • 1970-01-01
              相关资源
              最近更新 更多