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

Java:如何对SwingWorker进行单元测试,java,junit,mockito,swingworker,Java,Junit,Mockito,Swingworker,在谷歌搜索了一段时间后,我仍然无法找到一个我喜欢的答案 我有一个SwingWorker执行“长”任务,与db交谈并返回结果。我想为此编写一个单元测试,为我的db编写一个模拟对象 我已经基于以下这篇文章实现了我的SwingWorker: 但是,它实际上没有单元测试逻辑。我还没有看到任何关于最佳实践的优秀文章,这让我相信我遗漏了一些基本的东西。它是否和while循环检查done一样简单?还是我应该利用更复杂的东西?或者最佳实践仅仅是构建一个到被测逻辑的同步访问点 --编辑:我添加了Swingwor

在谷歌搜索了一段时间后,我仍然无法找到一个我喜欢的答案

我有一个SwingWorker执行“长”任务,与db交谈并返回结果。我想为此编写一个单元测试,为我的db编写一个模拟对象

我已经基于以下这篇文章实现了我的SwingWorker:

但是,它实际上没有单元测试逻辑。我还没有看到任何关于最佳实践的优秀文章,这让我相信我遗漏了一些基本的东西。它是否和while循环检查done一样简单?还是我应该利用更复杂的东西?或者最佳实践仅仅是构建一个到被测逻辑的同步访问点

--编辑:我添加了Swingworker代码,以查看我是如何实现它的。所以我的问题是:如何为这个类编写有效的单元测试

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);
    }
}
}
公共类MovieListLoaderWorker扩展SwingWorker{
//私有模型;
私人电影数据库;
私人响应目标;
私有布尔值keepAlive=false;
公共MovieListLoaderWorker(MovieListDB,响应目标){
这个.db=db;
this.target=目标;
}
公共MovieListLoaderWorker(布尔keepAlive、MovieListDB、响应目标){
这个.db=db;
this.target=目标;
this.keepAlive=keepAlive;
}
/**
*@返回电影列表(同步)
*/
公共阵列列表getMovieList(){
ArrayList movieNames=新的ArrayList(db.getMovieSet(true));
集合。排序(movieNames);
归还电影;
}
受保护的ArrayList doInBackground()引发异常{
target.started();
//如果我们想保持线程的活力,我们就旋转并发布电影列表
while(keepAlive){
发布(getMovieList());
sleep(Config.MOVIE\u LIST\u REFRESH\u RATE\u MILLIS);
}
//只运行一次,只需返回电影列表。
返回getMovieList();
}
@凌驾
受保护的无效进程(列表){
ArrayList s=list.get(list.size()-1);
目标。状态更新;
超级进程(列表);
}
@凌驾
受保护的void done(){
试一试{
ArrayList movieNames=get();
if(movieNames==null){
失败(新背景异常(新异常(“无法加载电影名称”));
}
否则{
目标成功(movieNames);
}
}
捕获(中断异常例外){
失败(新背景异常(ex));
}
捕获(ExecutionException ex){
失败(新背景异常(ex));
}
}
公共接口响应{
void start();
作废状态更新(ArrayList列表);
无效的成功(ArrayList movieList);
无效失效(背景异常ex);
}
公共类BackgroundException扩展了异常{
公共背景例外(可丢弃原因){
超级(原因);
}
}
}

您必须明确此处的职责

其中一部分是实际的“服务”,它必须与数据库通信,并且您希望通过SwingWorker运行

我将实现完全独立于线程上下文的DBService。DBService类也与SwingWorker没有任何关系。它是一个提供特定服务的“独立”类,可以完全进行单元测试,而不必担心SwingWorker或线程

然后您创建自己的小SwingWorker,它获取该DBService的一个实例,并使用该对象执行其工作

现在,您可以向SwingWorker提供一个模拟的DBService实例;您可以使用单元测试来确保“接线”是正确的


换句话说:不要将全部功能推送到SwingWorker中。相反,这里有单独的关注点;因为这也导致了“测试需求”的分离。

因此,作为如何对SwingWorker进行单元测试的一个实现,我非常感谢您的反馈。如果你和我一样,在这个问题上找不到合适的资源,我希望它能帮助你

第一个例子是利用jodah.net的服务员功能。这将迫使调用线程等待SwingWorker返回。您可以在匿名响应对象中对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);      
}

我为自己设计的两种模式。对其他观点感兴趣。

避免单元测试任何与线程相关的内容。分离出您的核心逻辑,并直接使用synchronhous的单元测试对其进行测试。作为集成级测试的一部分,测试线程。这是一个好主意。我已经将我的核心逻辑转移到了一个可以同步测试的公共方法。然而,我最初的问题仍然是集成测试。测试上述类的线程方面的最佳方法是什么?我添加了我想为其编写单元测试的类。MovieListDB是我的DB服务的接口。我这里只有我的手机键盘。。。即使有一个真正的键盘:记住我们帮助解决问题;这并不意味着我们为你做所有的工作。我可以看看你的代码,并提出一些具体的建议;但在下周之前可能不会。如果你对这种支持感兴趣,请在下周一左右给我留言。
@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);
}