【问题标题】:unit testing philosophy单元测试理念
【发布时间】:2011-08-31 12:30:24
【问题描述】:

我有一个“配方”方法,我正在尝试使用 TDD 编写它。它基本上会调用不同的方法,偶尔会根据这些方法的结果做出决定:

  public void HandleNewData(Data data)
  {
     var existingDataStore = dataProvider.Find(data.ID);
     if (data == null)
        return;

     UpdateDataStore(existingDataStore, data, CurrentDateTime);

     NotifyReceivedData(data);

     if (!dataValidator.Validate(data))
        return;

     //... more operations similar to above
  }

我的下意识反应是开始编写测试用例,我验证 HandleNewData 调用了上面看到的方法并传递了预期的参数,并且它在方法调用失败的情况下返回。 但这对我来说有点像是在时间上的巨大投资来编写这样一个测试,几乎没有实际收益。

那么编写这样一个测试的真正好处是什么?还是真的不值得费心?

这似乎只是对其自身代码的过度规范,并且每当该代码必须调用另一个方法或决定不再调用当前方法之一时都会导致维护问题。

【问题讨论】:

    标签: unit-testing tdd methodology


    【解决方案1】:

    既然您已经澄清它是“Legacy Code (TM)”,我将简单介绍该方法的设计。名称本身是模糊的,这反映在方法的内容中。我会稍微改进一下设计 - 它似乎做了很多。

    但要做到这一点,我必须确保我不会以“让它变得更好”为借口使情况变得更糟。我如何证明这一点?测试!

    所以我首先对顶级对象的“块”功能进行“副”测试。 因此,我今天将尽我所能进行测试以验证“HandleNewData”的行为(这可能包括一些代码挖掘)

    • 在数据存储中查找 ID
    • 使用新数据更新数据存储并修改时间戳
    • 通知感兴趣的听众
    • 验证数据(从外观上看应该是第 2 步) 等等。

    一旦我通过一些自动化的“副”测试确定了现有行为,我现在就可以自信地进行更改/改进了。也可能是这样,一旦设计被重构,HandleNewData 的包含类型就不再需要了。在这种情况下,您可以取消这些测试 - 但是这些测试在 EXISTING-IMPROVED 之间的价值不容忽视。

    【讨论】:

      【解决方案2】:

      TDD 并不意味着为已经存在的代码编写单元测试(尽管有时在改进遗留代码时可能需要这样做)。

      您可能听说过“红、绿、重构”这个词。这是我们在做 TDD 时采用的方法。以下是测试驱动开发的三个法则,它们更进一步......

      1. 您不得编写生产代码 直到你写了一个失败的 单元测试。
      2. 您编写的单元测试不能超过足以导致失败的程度,并且 不编译失败。
      3. 您编写的生产代码不能超过足以通过 目前未通过测试。

      采用这种方法的好处是您最终获得了非常接近 100% 的单元测试覆盖率,并且您知道您的代码完全按照指定的方式工作。

      这将减少维护问题,因为一旦有人对您的代码进行更改并运行测试,他们就会知道他们是否破坏了任何东西。

      在这种情况下,我会在为HandleNewData() 添加任何方法之前,逐步为从HandleNewData() 调用的方法添加单元测试。

      向遗留代码添加单元测试很困难,但可行且非常值得付出努力。如果您还没有,我真的建议您阅读Michael FeathersWorking Effectively with Legacy Code。在向 25 年历史的代码库添加单元测试时,我发现它非常宝贵。

      【讨论】:

      • 我应该澄清一下,我正在做的是重构一些旧的遗留代码。我的愿望是让它看起来像上面的简单示例,并且我正在尝试使用 TDD 来实现上述实现
      • 我认为 Johnsyweb 的观点是,测试驱动设计首先不会达到上述实现,您的实现很难测试是有充分理由的,这是一个警告表明这是一个糟糕的设计。您当然可以将此实现包装在测试中,但您可能还想考虑如果您的测试与当前实现无关,它们会是什么样子。如果您要开始一个高级别并深入研究行为,此方法将实现您将编写哪些测试,针对哪些组件,您可以在这里使用该结构吗?
      【解决方案3】:

      您遇到的问题很常见。你有一些令人讨厌的未经测试的遗留代码,它们做得太多,并且与太多的合作者紧密耦合。为此编写测试确实很痛苦。

      问题是,不幸的是,您背负着这个代码债务,并且在某些时候,您将不得不支付它。

      因此,要开始偿还部分债务,如果您需要更改此代码,我会尽可能多地模拟以使被测方法运行一次,这样您就可以获得测试的外壳放置到可以添加新功能的位置。如果可能的话,我会将您的新功能作为对另一个合作者的一次调用,您可以在其中放置(和试驾!)您的新代码。

      这样您就有了一些基本的信心,即旧代码调用了您的新代码,并且新代码已通过 TDD 正确构建。

      当然,您仍然有原始遗留代码的代码债务,但您可以将其作为一个单独的问题来解决。

      【讨论】:

        猜你喜欢
        • 1970-01-01
        • 2022-11-14
        • 1970-01-01
        • 2010-12-10
        • 2022-10-06
        • 1970-01-01
        • 2022-08-17
        • 1970-01-01
        • 1970-01-01
        相关资源
        最近更新 更多