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);
}
我自己设计的两种模式。对其他意见感兴趣。
谷歌搜索了一段时间后,我仍然找不到我喜欢的答案。
我有一个 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.
@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);
}
我自己设计的两种模式。对其他意见感兴趣。