我如何很好地单元测试异步方法?

时间:2014-02-21 22:57:50

标签: java unit-testing asynchronous junit

我目前正在使用线程锁定对我的异步方法进行单元测试,通常我会在我的异步组件中注入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中汲取灵感,但最重要的答案并未提供具体解决方案。

3 个答案:

答案 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()将立即返回。