Java 创建等待所有Scheduler.io()完成的Mockito测试用例
我有一个bug,它只有在并行运行时才会显现出来,例如,在Java 创建等待所有Scheduler.io()完成的Mockito测试用例,java,mockito,rx-java,Java,Mockito,Rx Java,我有一个bug,它只有在并行运行时才会显现出来,例如,在Schedulers.io()上。修复方法很简单-将flatMapCompletable替换为concatmappletable,以明确强制顺序执行 但是,我的bug复制单元测试调用verify()太早了,因为它没有等待完成 重要提示:被测函数返回void,因此我无法直接附加到我正在测试的可观察对象。我必须启动该函数,然后等待所有线程完成,然后检查结果 如果我用Schedulers.trampoline()替换Schedulers.io()
Schedulers.io()
上。修复方法很简单-将flatMapCompletable
替换为concatmappletable
,以明确强制顺序执行
但是,我的bug复制单元测试调用verify()
太早了,因为它没有等待完成
重要提示:被测函数返回void
,因此我无法直接附加到我正在测试的可观察对象。我必须启动该函数,然后等待所有线程完成,然后检查结果
如果我用Schedulers.trampoline()
替换Schedulers.io()
,测试将一直执行到最后,但它无法再现问题,因为代码在同一线程上执行,因此不会导致特定于并行性的错误
放置睡眠(3000)代码>在验证之前
修复了测试,但这是一个丑陋的解决方案
在我的测试中调用verify
之前,如何正确地等待所有Schedulers.io()
线程完成它们的工作
下面是一个被测试代码的简化示例,注释掉了修复程序,因此测试失败,测试方法也失败了。在这个简化版本中,它实际上对Mockito没有任何作用:
List<Integer> responseCompletions = new ArrayList<>();
private void methodUnderTest()
{
Completable longCompletable = Completable.fromRunnable(() -> {
System.out.println("Was requested for id 1");
try {
sleep(2000);
} catch (InterruptedException e) { }
System.out.println("Returning result for id 1 after long sleep");
responseCompletions.add(1);
})
// must run on io() ! not main thread! otherwise test succeeds when it should actually fail
.subscribeOn(Schedulers.io()).observeOn(Schedulers.io());
Completable shortCompletable = Completable.fromRunnable(() -> {
System.out.println("Was requested for id 2");
try {
sleep(100);
} catch (InterruptedException e) { }
System.out.println("Returning result for id 2 after short sleep");
responseCompletions.add(2);
})
// must run on io() ! not main thread! otherwise test succeeds when it should actually fail
.subscribeOn(Schedulers.io()).observeOn(Schedulers.io());
List<String> list = Arrays.asList("1", "2");
Observable.fromIterable(list)
//.concatMapCompletable(item -> { // <- this is the fix for the code under test
.flatMapCompletable(item -> {
System.out.println("Making completable for " + item);
if (item == "1")
return longCompletable;
else if (item == "2")
return shortCompletable;
return Completable.complete();
})
.subscribeOn(Schedulers.io())
.observeOn(Schedulers.io())
.subscribe(() -> {
System.out.println("list done");
},
throwable -> {});
}
@Test
public void cleanedUpCompletablesSequenceExperiments() throws Exception {
methodUnderTest();
// would like to get rid of this ugly sleep
// - there should be a better way to wait for waiting for it to complete?
sleep(3000);
assertEquals (1, (int)(responseCompletions.get(0)));
assertEquals (2, (int)(responseCompletions.get(1)));
}
List responseCompletions=new ArrayList();
私有void methodUnderTest()
{
Completable longCompletable=Completable.fromRunnable(()->{
System.out.println(“请求id为1”);
试一试{
睡眠(2000年);
}捕获(中断异常e){}
System.out.println(“长时间睡眠后返回id 1的结果”);
响应完成。添加(1);
})
//必须在io()上运行!不是主线程!否则测试将在实际失败时成功
.subscribeOn(Schedulers.io()).observeOn(Schedulers.io());
Completable shortCompletable=Completable.fromRunnable(()->{
System.out.println(“请求id为2”);
试一试{
睡眠(100);
}捕获(中断异常e){}
System.out.println(“短暂睡眠后返回id 2的结果”);
响应完成。添加(2);
})
//必须在io()上运行!不是主线程!否则测试将在实际失败时成功
.subscribeOn(Schedulers.io()).observeOn(Schedulers.io());
List=Arrays.asList(“1”、“2”);
可观察的。从可观察的(列表)
//.concatMapCompletable(项目->{//{
System.out.println(“使“+项可完成”);
如果(项目==“1”)
返回可完成;
否则,如果(项目==“2”)
返回短可完成;
返回Completable.complete();
})
.subscribeOn(Schedulers.io())
.observeOn(Schedulers.io())
.订阅(()->{
System.out.println(“列表完成”);
},
可丢弃->{});
}
@试验
public void cleanedUpCompletablesSequenceExperiments()引发异常{
方法测试();
//想摆脱这种难看的睡眠吗
//-应该有更好的方法等待它完成吗?
睡眠(3000);
assertEquals(1,(int)(responseCompletions.get(0));
assertEquals(2,(int)(responseCompletions.get(1));
}
您可以在方法测试()中使用:
在其开头添加:
private void methodUnderTest()引发InterruptedException{
最终CountDownLatch completables=新的CountDownLatch(2);
在Completable
s末尾添加:
响应完成。添加(1);
completables.countDown();
响应完成。添加(2);
completables.countDown();
在方法末尾添加:
throwable->{});
completables.wait();
这修复了删除睡眠(3000)时的索引自动边界异常
;
,但测试仍然失败:
java.lang.AssertionError:应为:但为:
连同:
assertTrue(“List responseCompletions不包含1.”,responseCompletions.contains(1));
assertTrue(“List responseCompletions不包含2.”,responseCompletions.contains(2));
正如我在对你的问题的评论中提到的,测试成功了。你能编辑你的问题以包括单元测试的某种MCVE以及你如何设置模拟和RX订阅吗?@Progman实际的代码有点复杂,但我设法创建了一个经过清理的简化版本,它甚至不再使用模拟了问题是相同的-丑陋的睡眠等待io()
线程完成其工作。@JustAMartin使用subscribe()是一个选项吗
重载方法,该方法在可观察对象完成时接受一个操作
对象?这样,您将知道它已完成并可以对其作出反应。这实际上更多的是API设计问题-您正在尝试测试一个异步并返回void的函数-客户端无法判断它何时完成。您应该重构函数,使其返回一个可完成的
。这样,您的测试就可以订阅该函数,并在函数完成时得到通知。@GeroldBrosersRestoresmonica测试中的代码最初使用flatMapCompletable
和Schedulers.io()
,这使得它可以并行执行Completable。对应于测试中的业务逻辑案例,这段代码不应该并行执行,因此它应该使用concatMapCompletable
。这就是为什么我首先创建了一个使用原始错误并行代码的测试,但失败了,然后