【问题标题】:testing a huge decomposed method and its inner helper methods测试一个巨大的分解方法及其内部辅助方法
【发布时间】:2012-10-15 17:10:15
【问题描述】:

我最近写了很多单元测试。有一种情况我找不到干净的解决方案...

假设你有一种方法:

public void bigMethod() {
    // a lot of code goes in here
}

为了让您的生活更轻松和代码更简洁,您通常会将这种野兽分解为更小的内部方法:

public void bigMethod() {
    a();
    b();
    c();
    // etc.
}

您可以独立测试所有内部方法(a()、b()、c() 等)。问题是 bigMethod() 也应该进行测试,但它唯一要做的就是链接其他一些方法的调用,并且这些方法已经过彻底测试!

您如何处理这种情况?你不能不测试 bigMethod(),因为你需要确保 a()、b() 和 c() 是按正确的顺序调用的。但是为 bigMethod() 编写测试会导致大量重复测试。而且每次都减少这种重复会很麻烦,因为你会经常这样做。

我想到的一个想法是:

public void bigMethod() {
    helperA.a();
    helperB.b();
    helperC.c();
    // etc.
}

在这种情况下,您测试每个帮助程序类,然后确保 bigMethod() 按顺序调用它们。漂亮干净,但在项目中引入了很多非常小的类。

帮助测试忍者!

【问题讨论】:

  • Nice and clean but introduces a lot of very small classes into the project. 小巧、干净、简单、易于测试、可模拟、可重复使用,并且只有一个职责。对我来说听起来没那么糟糕......

标签: java unit-testing testing tdd


【解决方案1】:

通常最佳做法是让您的单元测试测试 bigMethod(),因为它是公开的方法,并且测试不知道内部的私有辅助方法。

正如您所指出的,如果您重构 bigMethod() 的内部实现,那么对类的内部工作了解太多的测试往往很脆弱,并且必须进行大量修改。

所以我的建议是重写你的测试以专注于bigMethod(),然后不必担心代码重复。

【讨论】:

    【解决方案2】:

    您找不到好的解决方案的原因是,将方法分成连续的部分并没有真正改善事情。事实上,它可能使调试变得更加困难,因为您必须跨方法边界共享状态,可能使用实例变量。

    我知道这不是你想听到的答案,但如果你真的想改进代码,你会退后一步,重新分析 bigMethod 所做的事情并重写它,将其各种函数分成类具有适当的接口。理想情况下,您还可以重构调用 bigMethod 的位置,但我意识到这可能超出了您分配的任务的范围。

    【讨论】:

    • 我不同意。将方法分成连续的部分确实提高了可读性。它被称为“提取直到你放弃”:) google.com/…
    • 这一切都取决于原始代码及其分解方式。仅仅为了拆分而拆分可能是好是坏,根据我的经验(通过阅读数千行遗留代码)通常不是一个好主意。
    • @Mik378:Bob Martin 是一位善于写出糟糕建议的高手,让其看起来像是很棒的建议。他使用的代码适合被分解成非常小的部分;很多代码都是这样,但有些代码不是。我曾经有一位同事,他过去常常在 Eclipse 中盲目地敲击“提取方法”重构,直到我们的代码变得难以理解、无法维护、一团糟。他可能读了很多鲍勃·马丁的书。
    • @Tom Anderson 我认为这真的是一个品味问题。就我而言,更少的算法和更少的 cmets,我更好:) => 分裂促进了这一点。分裂的质量也非常决定性。当然,一个开发人员可以很快让事情变得可怕......
    【解决方案3】:

    首先,如果a()b()c() 是公共方法,则以下所有文本都是有意义的。

    bigMethod() 测试关注内部调用的后果,这意味着调用a() 后跟b(),然后是c()

    这完全是另一种情况,而不是独立测试这些方法。

    所以根据我的说法,为bigMethod() 添加一个特定的测试并没有发现一些重复,而是一个新的有趣的案例来检查:内部调用的协作

    我会保留您的第一个代码版本并对其进行测试:)

    实际上,这似乎是项目交付中类似的常见问题:

    假设三个项目分别开发并最终收集起来。

    三种可能的做法:

    • 仅独立测试三个项目
    • 等待单独测试三个项目的混合
    • 测试这两种方式!

    从逻辑上讲,第三种可能性是希望的;单元测试也是如此。

    a()b()c() 可能会出现意外行为,但不会阻止 bigMethod() 获得预期结果,反之,尽管独立测试成功,bigMethod() 可能会出现意外结果方法。

    【讨论】:

      【解决方案4】:

      我认为解决方案对您来说不清晰的原因是因为您没有真正考虑并定义a()b()c() 的性质。您肯定需要弄清楚以下a()b()c() 中的哪一个是:

      1. bigMethod() 内部不可分割的子部分,在它之外毫无意义。

      2. 作为类公共契约一部分的方法,有助于其凝聚力,并且可以独立于外部调用。

      3. 毕竟不属于该类的方法。将它们取出将有利于班级的凝聚力,并使它们更易于重用。

      一旦你弄清楚了,如何测试它们的答案就很容易了。

      1. 只需将 bigMethod() 测试为原子块,不要试图调整其内部子部分的可见性范围以使其可测试。

      2. 分别测试a()b()c()。然后测试bigMethod(),使用部分模拟并验证子方法是否以正确的顺序调用。您还可以测试bigMethod() 的结果,使其更像是集成测试而不是单元测试,但存在您提到的重复问题。

      3. 分别测试a()b()c()。然后测试bigMethod(),对其对象使用模拟并验证bigMethod() 与这些对象正确对话。

      【讨论】:

        猜你喜欢
        • 2011-03-27
        • 2010-12-24
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 2011-10-26
        • 1970-01-01
        相关资源
        最近更新 更多