代码之家  ›  专栏  ›  技术社区  ›  Konrad Garus

TDD-重构成黑盒?

  •  6
  • Konrad Garus  · 技术社区  · 14 年前

    我有一个用TDD开发的重要服务对象。它从一个简单的任务开始:对于队列中的对象,构造一个异步处理的尝试。所以我写了一个测试 constructAttempt() 方法:

    void constructAttempt() {...}
    

    有许多可能的场景需要考虑,所以我有很多关于这个方法的测试。


    然后我实现了我真正需要它做的事情:扫描整个队列并构造一批尝试。所以代码看起来更像:

    public void go() {
        for (QueuedItem item : getQueuedItems()) {
            constructAttempt(item);
        }
    }
    

    所以我为这个增加了一两个测试 go() 方法。


    最后我发现我需要一些预处理,有时会影响 施工尝试() . 现在代码看起来更像:

    public void go() {
        preprocess();
        for (QueuedItem item : getQueuedItems()) {
            constructAttempt(item);
        }
    }
    

    我对我现在应该做什么有一些疑问。

    我要保持代码不变吗 施工尝试() , preprocess() () 独立测试?为什么是/为什么不是?我的风险不包括预处理和破坏封装的副作用。

    或者我应该将整个测试套件重构为只调用 () (哪一种是唯一的公共方法)?为什么是/为什么不是?这将使测试变得更加模糊,但另一方面,它将考虑所有可能的交互。实际上,它将成为一个只使用公共API的黑盒测试,这可能与TDD不符。

    3 回复  |  直到 14 年前
        1
  •  6
  •   Jeff Sternal    14 年前

    这个 go 方法实际上只是协调了几个交互,本身并不是很有趣。如果你写测试 与您的下属方法不同,测试可能非常复杂,因为您必须考虑到 preprocess constructAttempt (甚至可能甚至 getQueuedItems 尽管听起来相对简单)。

    相反,您应该为下级方法编写测试——以及 施工尝试 需要考虑所有预处理的潜在影响。如果你不能 模拟 这些副作用(通过操纵底层队列或双重测试)将类重构到可以重构为止。

        2
  •  3
  •   Gutzofter    14 年前

    @杰夫说得对。您真正拥有的是在这个对象中发生的两个职责。您可能希望将排队的项目拉入它们自己的类中。推 preprocess constructAttempt 到单个项目中。imho当您有一个处理单个项目的类和一个项目列表时,您会闻到代码的味道。职责是列表容器对项目进行操作。

    public void go() {
        for (QueuedItem item : getQueuedItems()) {
            item.preprocess();
            item.constructAttempt();
        }
    }
    

    注意:这类似于使用命令对象模式

    [编辑1A] 这使得模拟测试非常容易。这个 go 方法只需要使用单个队列项或不使用队列项进行测试。也各 item 现在可以让他们的个人测试与 .

    [编辑1B] 你甚至可以折叠 预处理 进入项目:

    public void go() {
        for (QueuedItem asyncCommunication: getQueuedItems()) {
            asyncCommunication.attempt();
        }
    }
    

    现在你有了一个真实的 command pattern .

        3
  •  1
  •   Nitin Chaudhari    14 年前

    我说让您的测试套件只调用go(),因为它是唯一的公共API。这意味着,一旦您涵盖了go方法的所有场景(包括预处理和队列),那么如果您更改内部实现,就不再重要了。就公共用途而言,您的类保持正确。

    我希望您对用于预处理/排队的类使用依赖性倒置——这样您就可以独立地测试预处理,然后在go()测试中模拟它。