2 回答
TA贡献1833条经验 获得超4个赞
将您在其中执行的代码提取whenComplete到一个字段中,并提供一个构造函数来替换它。
class Runner implement Runnable {
private final String param;
private final BiConsumer<Void, Throwable> callback;
public Runner(String param) {
this.param = param;
this.callback = this::callbackFunction;
}
Runner(String param, BiConsumer<Void, Throwable> callback) {
this.param = param;
this.callback = callback;
}
public void run() {
CompletableFuture.runAsync(task).whenComplete(callback);
}
private void callbackFunction(Void result, Throwable throwable) {
//some code when complete
}
}
测试将如下所示:
class RunnerTest {
@Test
void test() {
new Runner("param", (response, throwable) -> /* mocked behavior */).run();
}
}
TA贡献1942条经验 获得超3个赞
我的第一个倾向不是嘲笑这个:它看起来像是startAsyncMyClassImplementRunner 公共 API 的一部分,你应该一起测试这些部分。在像 MyClassImplementRunnerTest 这样的测试类中,将被测系统视为 MyClassImplementRunner 而不试图将其拆分是有意义的。否则,很容易忘记您正在测试的内容,包括什么是真实的,什么是模拟的。
如果存在MyClassImplementRunner 正在寻找的任何外部条件,您可以模拟该依赖项,这可能会导致您的 CompletableFuture 立即返回;但是,您只向我们展示了一个 String 参数。
也就是说,可能startAsync包含您想要在没有真正 MyClassImplementRunner 的情况下进行详尽测试的逻辑。在这种情况下,您可以创建用于测试的重载,可能具有有限的可见性或仅用于测试的注释,以指示不应在生产中调用它。
public static CompletableFuture<Void> startAsync(String param1) {
return startAsync(new MyClassImplementRunner(param1);
}
/** Package-private, for a test class in the same package. */
@VisibleForTesting static CompletableFuture<Void> startAsync(Runnable task) {
return CompletableFuture.runAsync(task).whenComplete(
(response, throwable) -> {
//some code when complete
});
}
通过将其拆分,您现在可以startAsync(new Runnable())在测试中运行以模拟立即成功的任务,并运行startAsync(() -> { throw new RuntimeException(); })以模拟立即失败的任务。这允许您startAsync独立于 MyClassImplementRunner进行测试。
为测试而重构或引入仅测试方法似乎并不明智,这是一个公平的评估:纯粹来说,MyClassImplementRunner应该完全按照消费者运行它的方式进行测试,而不是模拟。但是,如果您说在测试中使用与 MyClassImplementRunner 不同的 Runnable 运行更方便,则您可以控制代码,并且可以通过在代码中包含适当的灵活性(“测试接缝”)来为此做好准备你控制。事实上,如果startAsync是一个足够独立的方法,它可以带一个任意的Runnable,你可以选择把它分离出来作为一个单独的方法,单独测试。
添加回答
举报