我目前正在使用线程锁定对我的异步方法进行单元测试,通常我会在我的异步组件中注入CountDownLatch
并让主线程等待它达到0.然而,这种方法看起来很丑陋,并且它不能很好地扩展,考虑当我为一个组件编写100多个测试时会发生什么,并且它们都必须等待工作线程做一些虚假的异步工作。
还有另一种方法吗?对于简单的搜索机制,请考虑以下示例:
Searcher.java
public class Searcher {
private SearcherListener listener;
public void search(String input) {
// Dispatch request to queue and notify listener when finished
}
}
SearcherListener.java
public interface SearcherListener {
public void searchFinished(String[] results);
}
如果不使用多个线程并阻止一个等待另一个线程,您将如何对search
方法进行单元测试?我从How to use Junit to test asynchronous processes中汲取灵感,但最重要的答案并未提供具体解决方案。
答案 0 :(得分:1)
为异步编写单元测试看起来不太好。
testMyAsyncMethod()
(主线程)必须阻塞,直到您准备好检查正确的行为。这是必要的,因为测试用例在方法结束时终止。所以没有办法,问题只是你如何阻止。
一种不会影响生产代码的简单方法是 使用while循环:asume AsyncManager是被测试的类:
ArrayList resultTarget = new ArrayList();
AsyncManager fixture = new AsyncManager(resultTarget);
fixture.startWork();
// now wait for result, and avoid endless waiting
int numIter = 10;
// correct testcase expects two events in resultTarget
int expected = 2;
while (numIter > 0 && resulTarget.size() < expected) {
Thread.sleep(100);
numIter--;
}
assertEquals(expected, resulTarget.size());
生产代码将在AsyncManager的构造函数中使用适当的目标或使用其他构造函数。出于测试目的,我们可以通过我们的测试目标。
您只会为自己的消息队列等固有的异步任务编写此代码。 对于其他代码,只有unitest执行计算任务的类的核心部分(一个特殊的算法等),你不需要让它在一个线程中运行。
但是对于您的搜索侦听器,显示的循环和等待原则是合适的。
public class SearchTest extends UnitTest implements SearchListener {
public void searchFinished() {
this.isSearchFinished = true;
}
public void testSearch1() {
// Todo setup your search listener, and register this class to receive
Searcher searcher = new Searcher();
searcher.setListener(this);
// Todo setup thread
searcherThread.search();
asserTrue(checkSearchResult("myExpectedResult1"));
}
private boolean checkSearchResult(String expected) {
boolean isOk = false;
int numIter = 10;
while (numIter > 0 && !this.isSearchFinished) {
Thread.sleep(100);
numIter--;
}
// todo somehow check that search was correct
isOk = .....
return isOk;
}
}
答案 1 :(得分:1)
另一种方法:
不要启动线程。就这样。
假设您有一个使用SearcherService
课程的Searcher
。
然后不要启动异步SearcherService,而只是调用searcher.search()
,这将阻塞直到搜索完成。
Searcher s = new Searcher();
s.search(); // blocks and returns when finished
// now somehow check the result
答案 2 :(得分:1)
创建一个类的同步版本,用于侦听自己的结果并使用search()
等待的内部锁存器并清除searchFinished()
。像这样:
public static class SynchronousSearcher implements SearcherListener {
private CountDownLatch latch = new CountDownLatch(1);
private String[] results;
private class WaitingSearcher extends Searcher {
@Override
public void search(String input) {
super.search(input);
try {
latch.await();
} catch (InterruptedException e) {
throw new RuntimeException(e);
}
}
}
public String[] search(String input) {
WaitingSearcher searcher = new WaitingSearcher();
searcher.listener = this;
searcher.search(input);
return results;
}
@Override
public void searchFinished(String[] results) {
this.results = results;
latch.countDown();
}
}
然后使用它,只需:
String[] results = new SynchronousSearcher().search("foo");
没有线程,没有等待循环,并且该方法在尽可能短的时间内返回。如果搜索在调用await()
之前立即返回也没关系 - 因为如果锁存器已经为零,await()
将立即返回。