Java: 如何对 SwingWorker 进行单元测试

Java: How to unit test a SwingWorker

谷歌搜索了一段时间后,我仍然找不到我喜欢的答案。

我有一个 SwingWorker 执行 "long" 任务,与数据库对话并返回结果。我想为此编写一个单元测试,为我的数据库使用一个模拟对象。

我已经基于这个很棒的 post 在这里实现了我的 SwingWorker:

How can this SwingWorker code be made testable

但是,它实际上并没有单元测试逻辑。而且我还没有看到任何关于最佳实践的优秀文章,这让我相信我错过了一些基本的东西。它是否像 while 循环检查完成一样简单?或者有什么更复杂的东西我应该利用吗?或者最好的做法是简单地为被测逻辑构建一个同步访问点?

-- 编辑:我添加了 Swingworker 代码以查看我是如何实现它的。所以我的问题是:如何为此 class?

编写 effective 单元测试
public class MovieListLoaderWorker extends SwingWorker<ArrayList<String>, ArrayList<String>> {

//private Model model;
private MovieListDB db;
private Response target;
private boolean keepAlive = false;

public MovieListLoaderWorker(MovieListDB db, Response target) {
    this.db = db;
    this.target = target;
}

public MovieListLoaderWorker( boolean keepAlive, MovieListDB db, Response target) {
    this.db = db;
    this.target = target;
    this.keepAlive = keepAlive;
}

/**
 * @return List of movies (synchronous)
 */
public ArrayList<String> getMovieList() {
    ArrayList<String> movieNames = new ArrayList<String>(db.getMovieSet(true));     
    Collections.sort(movieNames);
    return movieNames;      
}

protected ArrayList<String> doInBackground() throws Exception {
    target.started();

    // if we want to keep the Thread alive, we spin and publish movie lists
    while (keepAlive) {
        publish(getMovieList());
        Thread.sleep(Config.MOVIE_LIST_REFRESH_RATE_MILLIS);
    }

    // only run once, and simply return the Movie list.
    return getMovieList();
}

@Override
protected void process(List<ArrayList<String>> list) {
    ArrayList<String> s = list.get(list.size() - 1);
    target.statusUpdate(s);
    super.process(list);        
}

@Override
protected void done() {
    try {
        ArrayList<String> movieNames = get();
        if (movieNames == null) {
            target.failure(new BackgroundException(new Exception("Unable to load movie names.")));
        }
        else {
            target.success(movieNames);
        }
    }
    catch (InterruptedException ex) {
        target.failure(new BackgroundException(ex));
    }
    catch (ExecutionException ex) {
        target.failure(new BackgroundException(ex));
    }
}

public interface Response {     
    void started();
    void statusUpdate(ArrayList<String> list);
    void success(ArrayList<String> movieList);
    void failure(BackgroundException ex);
}

public class BackgroundException extends Exception {
    public BackgroundException(Throwable cause) {
        super(cause);
    }
}
}

这里的职责你要搞清楚。

一部分是实际的 "Service",它必须与该数据库对话,并且您希望通过 SwingWorker 运行。

我会实现 DBService 完全 独立于线程上下文。 DBService class 与 SwingWorker 也没有 no 关系。它是一个"standalone"class,提供某种服务,完全可以单元测试,不用担心SwingWorker或线程。

然后您创建自己的小 SwingWorker,它采用 DBService 的实例并使用该对象来完成它的工作。

现在您可以将 DBService 的模拟实例提供给您的 SwingWorker;您可以使用单元测试来确保 "wiring" 是正确的。

换句话说:不要将全部功能推入 SwingWorker。相反,在这里分开关注;因为这也会导致 "testing needs".

的分离

所以这里作为我想出的如何对 SwingWorker 进行单元测试的实现,我将不胜感激任何反馈。如果你和我一样,在为这个问题找到合适的资源时遇到困难,我希望它能帮助你。

第一个例子是利用jodah.net的服务员功能。这将强制调用线程等待 SwingWorker returns。您可以对匿名 Response 对象中的 SwingWorker 的结果执行任何必需的断言,并直接进行异常测试。

//here's the Waiter import, among others that you'll need
import net.jodah.concurrentunit.Waiter;

@Test
public void testExecute_AsyncExample1() throws Throwable {

    final Waiter waiter = new Waiter();

    MovieListLoaderWorker mlw = new MovieListLoaderWorker(db, new Response() {

        @Override
        public void success(ArrayList<String> movieList) {
            waiter.assertNotNull(movieList);
            waiter.assertTrue(movieList instanceof ArrayList);
            waiter.assertEquals(3, movieList.size());
            waiter.resume();
        }

        @Override
        public void started() {                             
        }

        @Override
        public void failure(BackgroundException ex) {               
        }

        @Override
        public void statusUpdate(ArrayList<String> list) {              
        }
    });

    mlw.execute();

    waiter.await(1, TimeUnit.SECONDS);      
}

第二个例子相当简单,它利用了 SwingWorker.get() 阻塞直到 returns 的事实。当 get() returns.

时,SwingWorker 引发的异常将传递给调用者
@Test
public void testExecute_AsyncExample2() throws Throwable {

    MovieListLoaderWorker mlw = new MovieListLoaderWorker(db, response);    
    mlw.execute();

    mlw.get();  //blocking so we can verify

    verify(response).started();
    verify(db).getMovieSet(true);
}

我自己设计的两种模式。对其他意见感兴趣。