Language agnostic TDD-重构成黑箱?

Language agnostic TDD-重构成黑箱?,language-agnostic,testing,tdd,Language Agnostic,Testing,Tdd,我用TDD开发了一个非平凡的服务对象。它从一个简单的任务开始:对于队列中的对象,构造一个异步处理的尝试。因此,我围绕我的constructTest()方法编写了一个测试: void constructAttempt() {...} 有许多可能的场景需要考虑,所以我对这种方法进行了十几次测试 然后我实现了我真正需要它做的事情:扫描整个队列并构造一批尝试。因此,代码看起来更像: public void go() { for (QueuedItem item : getQueuedItem

我用TDD开发了一个非平凡的服务对象。它从一个简单的任务开始:对于队列中的对象,构造一个异步处理的尝试。因此,我围绕我的
constructTest()
方法编写了一个测试:

void constructAttempt() {...}
有许多可能的场景需要考虑,所以我对这种方法进行了十几次测试


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

public void go() {
    for (QueuedItem item : getQueuedItems()) {
        constructAttempt(item);
    }
}
public void go() {
    preprocess();
    for (QueuedItem item : getQueuedItems()) {
        constructAttempt(item);
    }
}
因此,我为这个
go()
方法添加了一两个新测试


最后,我发现我需要一些预处理,这些预处理有时可能会影响
constructTest()
。现在代码看起来更像:

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

我对我现在该做什么有些怀疑

我应该保持代码的原样,独立测试
constructAttrint()
preprocess()
go()
?为什么是/为什么不是?我冒着不包括预处理和破坏封装的副作用的风险


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

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


我希望您对用于预处理/队列的类使用依赖项反转-这样您就可以独立地测试预处理,然后在go()测试中模拟它。

go方法实际上只是编排了几个交互,本身并不是很有趣。如果您针对
go
而不是您的下属方法编写测试,那么测试可能会非常复杂,因为您必须考虑
preprocess
constructExport
之间交互的组合爆炸(甚至可能
getQueuedItems
,尽管这听起来相对简单)


相反,您应该为从属方法编写测试,而对
constructTest
的测试需要考虑所有预处理的潜在影响重构你的类,直到你能重构为止。

@Jeff说得对。你真正要做的是在这个对象中发生两个责任。你可能想把排队的项目拉到他们自己的类中。把
预处理
构造尝试
推到单个项目中。当你有一个处理单个项目的类时,我怎么办还有一个有代码气味的项目列表。职责是列表容器对项目进行操作

public void go() {
    for (QueuedItem item : getQueuedItems()) {
        item.preprocess();
        item.constructAttempt();
    }
}
注意:这类似于使用命令对象模式

[编辑1a] 这使得模拟测试变得非常简单。
go
方法只需要使用单个队列项目或不使用队列项目进行测试。此外,每个
项目
现在可以将各自的测试与
go
的组合爆炸分开

[编辑1b] 您甚至可以将
预处理
折叠到项目中:

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

现在,如果您重构测试套件以只测试go()方法,那么您就拥有了一个真正的
命令模式

。。您将如何识别检测到的bug是在preprocess()还是constructAttempt??