Java 如何使SwingWorker代码可测试
考虑以下代码:Java 如何使SwingWorker代码可测试,java,unit-testing,swing,tdd,swingworker,Java,Unit Testing,Swing,Tdd,Swingworker,考虑以下代码: public void actionPerformed(ActionEvent e) { setEnabled(false); new SwingWorker<File, Void>() { private String location = url.getText(); @Override protected File doInBackground() throws Exception {
public void actionPerformed(ActionEvent e) {
setEnabled(false);
new SwingWorker<File, Void>() {
private String location = url.getText();
@Override
protected File doInBackground() throws Exception {
File file = new File("out.txt");
Writer writer = null;
try {
writer = new FileWriter(file);
creator.write(location, writer);
} finally {
if (writer != null) {
writer.close();
}
}
return file;
}
@Override
protected void done() {
setEnabled(true);
try {
File file = get();
JOptionPane.showMessageDialog(FileInputFrame.this,
"File has been retrieved and saved to:\n"
+ file.getAbsolutePath());
Desktop.getDesktop().open(file);
} catch (InterruptedException ex) {
logger.log(Level.INFO, "Thread interupted, process aborting.", ex);
Thread.currentThread().interrupt();
} catch (ExecutionException ex) {
Throwable cause = ex.getCause() == null ? ex : ex.getCause();
logger.log(Level.SEVERE, "An exception occurred that was "
+ "not supposed to happen.", cause);
JOptionPane.showMessageDialog(FileInputFrame.this, "Error: "
+ cause.getClass().getSimpleName() + " "
+ cause.getMessage(), "Error", JOptionPane.ERROR_MESSAGE);
} catch (IOException ex) {
logger.log(Level.INFO, "Unable to open file for viewing.", ex);
}
}
}.execute();
public void actionPerformed(ActionEvent e){
setEnabled(假);
新SwingWorker(){
私有字符串位置=url.getText();
@凌驾
受保护文件doInBackground()引发异常{
File File=新文件(“out.txt”);
Writer=null;
试一试{
writer=新文件编写器(文件);
creator.write(地点、作者);
}最后{
if(writer!=null){
writer.close();
}
}
返回文件;
}
@凌驾
受保护的void done(){
setEnabled(真);
试一试{
File=get();
JOptionPane.showMessageDialog(FileInputFrame.this,
“文件已检索并保存到:\n”
+getAbsolutePath());
Desktop.getDesktop().open(文件);
}捕获(中断异常例外){
logger.log(Level.INFO,“线程中断,进程中止”,例如);
Thread.currentThread().interrupt();
}捕获(ExecutionException ex){
可丢弃原因=ex.getCause()==null?ex:ex.getCause();
logger.log(Level.severy,“发生异常,该异常为”
+“不应该发生。”,原因);
JOptionPane.showMessageDialog(FileInputFrame.this,“错误:”
+原因。getClass().getSimpleName()+“”
+cause.getMessage(),“Error”,JOptionPane.Error\u MESSAGE);
}捕获(IOEX异常){
logger.log(Level.INFO,“无法打开文件进行查看”,例如);
}
}
}.execute();
url
是一个JTextField,“creator”是一个用于写入文件的注入接口(因此该部分正在测试中)。写入文件的位置是特意硬编码的,因为这只是一个示例。而java.util.logging的使用只是为了避免外部依赖
您将如何将其分块以使其可进行单元测试(包括在需要时放弃SwingWorker,但随后替换其功能,至少在这里使用)
在我看来,doInBackground基本上是好的。基本的机制是创建一个writer并关闭它,这几乎太简单了,无法测试,实际的工作正在测试中。但是,done方法存在问题,包括它与actionPerformed方法父类的耦合,以及协调启用d禁用按钮
然而,这一点并不明显。注入某种SwingWorkerFactory使捕获GUI字段变得更难维护(很难看出这将是一种设计改进)。JOpitonPane和桌面具有单例的所有“优点”,异常处理使得不可能轻松地包装get
那么,有什么好的解决方案来测试这段代码呢?IMHO,这对于匿名类来说很复杂。我的方法是将匿名类重构为如下内容:
public class FileWriterWorker extends SwingWorker<File, Void> {
private final String location;
private final Response target;
private final Object creator;
public FileWriterWorker(Object creator, String location, Response target) {
this.creator = creator;
this.location = location;
this.target = target;
}
@Override
protected File doInBackground() throws Exception {
File file = new File("out.txt");
Writer writer = null;
try {
writer = new FileWriter(file);
creator.write(location, writer);
}
finally {
if (writer != null) {
writer.close();
}
}
return file;
}
@Override
protected void done() {
try {
File file = get();
target.success(file);
}
catch (InterruptedException ex) {
target.failure(new BackgroundException(ex));
}
catch (ExecutionException ex) {
target.failure(new BackgroundException(ex));
}
}
public interface Response {
void success(File f);
void failure(BackgroundException ex);
}
public class BackgroundException extends Exception {
public BackgroundException(Throwable cause) {
super(cause);
}
}
}
public void actionPerformed(ActionEvent e) {
setEnabled(false);
Object creator;
new FileWriterWorker(creator, url.getText(), new FileWriterWorker.Response() {
@Override
public void failure(FileWriterWorker.BackgroundException ex) {
setEnabled(true);
Throwable bgCause = ex.getCause();
if (bgCause instanceof InterruptedException) {
logger.log(Level.INFO, "Thread interupted, process aborting.", bgCause);
Thread.currentThread().interrupt();
}
else if (cause instanceof ExecutionException) {
Throwable cause = bgCause.getCause() == null ? bgCause : bgCause.getCause();
logger.log(Level.SEVERE, "An exception occurred that was "
+ "not supposed to happen.", cause);
JOptionPane.showMessageDialog(FileInputFrame.this, "Error: "
+ cause.getClass().getSimpleName() + " "
+ cause.getMessage(), "Error", JOptionPane.ERROR_MESSAGE);
}
}
@Override
public void success(File f) {
setEnabled(true);
JOptionPane.showMessageDialog(FileInputFrame.this,
"File has been retrieved and saved to:\n"
+ file.getAbsolutePath());
try {
Desktop.getDesktop().open(file);
}
catch (IOException iOException) {
logger.log(Level.INFO, "Unable to open file for viewing.", ex);
}
}
}).execute();
}
此外,
FileWriterWorker.Response
的实例可以分配给一个变量,并独立于FileWriterWorker
简单的解决方案:一个简单的计时器是最好的;您打开计时器,启动执行的操作,在超时时必须启用bouton等等
下面是一个带有java.util.Timer的非常小的示例:
package goodies;
import java.util.Timer;
import java.util.TimerTask;
import javax.swing.JButton;
public class SWTest
{
static class WithButton
{
JButton button = new JButton();
class Worker extends javax.swing.SwingWorker<Void, Void>
{
@Override
protected Void doInBackground() throws Exception
{
synchronized (this)
{
wait(4000);
}
return null;
}
@Override
protected void done()
{
button.setEnabled(true);
}
}
void startWorker()
{
Worker work = new Worker();
work.execute();
}
}
public static void main(String[] args)
{
final WithButton with;
TimerTask verif;
with = new WithButton();
with.button.setEnabled(false);
Timer tim = new Timer();
verif = new java.util.TimerTask()
{
@Override
public void run()
{
if (!with.button.isEnabled())
System.out.println("BAD");
else
System.out.println("GOOD");
System.exit(0);
}};
tim.schedule(verif, 5000);
with.startWorker();
}
}
包装商品;
导入java.util.Timer;
导入java.util.TimerTask;
导入javax.swing.JButton;
公共类SWT测试
{
带按钮的静态类
{
JButton button=新JButton();
类Worker扩展了javax.swing.SwingWorker
{
@凌驾
受保护的Void doInBackground()引发异常
{
已同步(此)
{
等待(4000);
}
返回null;
}
@凌驾
受保护的void done()
{
按钮。setEnabled(真);
}
}
void startWorker()
{
工人工作=新工人();
work.execute();
}
}
公共静态void main(字符串[]args)
{
最终的按钮与;
时间任务验证;
with=新建WithButton();
带.button.setEnabled(假);
定时器tim=新定时器();
verif=new java.util.TimerTask()
{
@凌驾
公开募捐
{
如果(!with.button.isEnabled())
系统输出打印项次(“坏”);
其他的
系统输出打印号(“良好”);
系统出口(0);
}};
时间进度表(verif,5000);
with.startWorker();
}
}
假定的专家解决方案:Swing Worker是一个RunnableFuture,其中包含一个嵌入可调用的FutureTask,因此您可以使用自己的执行器启动它(RunnableFuture)要做到这一点,你需要一个有名字类的SwingWorker,而不是匿名的。有了你自己的executor和名字类,你可以测试你想要的一切,假定的专家说。当前的实现将线程问题、UI和文件编写结合在一起,正如你发现的那样,耦合使得测试单个组件变得很困难隔离 这是一个相当长的响应,但归根结底是将这三个关注点从当前实现中提取到具有定义接口的单独类中 排除应用程序逻辑 首先,将重点放在核心应用程序逻辑上,并将其转移到一个单独的类/接口中。接口允许更轻松地模拟和使用其他swing线程框架。这种分离意味着您可以完全独立于其他关注点来测试您的应用程序逻辑
interface FileWriter
{
void writeFile(File outputFile, String location, Creator creator)
throws IOException;
// you could also create your own exception type to avoid the checked exception.
// a request object allows all the params to be encapsulated in one object.
// this makes chaining services easier. See later.
void writeFile(FileWriteRequest writeRequest);
}
class FileWriteRequest
{
File outputFile;
String location;
Creator creator;
// constructor, getters etc..
}
class DefualtFileWriter implements FileWriter
{
// this is basically the code from doInBackground()
public File writeFile(File outputFile, String location, Creator creator)
throws IOException
{
Writer writer = null;
try {
writer = new FileWriter(outputFile);
creator.write(location, writer);
} finally {
if (writer != null) {
writer.close();
}
}
return file;
}
public void writeFile(FileWriterRequest request) {
writeFile(request.outputFile, request.location, request.creator);
}
}
独立用户界面
现在应用程序逻辑分离了,然后我们考虑成功与否
interface FileWriterHandler {
void done();
void handleFileWritten(File file);
void handleFileWriteError(Throwable t);
}
class FileWriterJOptionPaneOpenDesktopHandler implements FileWriterHandler
{
private JFrame owner;
private JComponent enableMe;
public void done() { enableMe.setEnabled(true); }
public void handleFileWritten(File file) {
try {
JOptionPane.showMessageDialog(owner,
"File has been retrieved and saved to:\n"
+ file.getAbsolutePath());
Desktop.getDesktop().open(file);
}
catch (IOException ex) {
handleDesktopOpenError(ex);
}
}
public void handleDesktopOpenError(IOException ex) {
logger.log(Level.INFO, "Unable to open file for viewing.", ex);
}
public void handleFileWriteError(Throwable t) {
if (t instanceof InterruptedException) {
logger.log(Level.INFO, "Thread interupted, process aborting.", ex);
// no point interrupting the EDT thread
}
else if (t instanceof ExecutionException) {
Throwable cause = ex.getCause() == null ? ex : ex.getCause();
handleGeneralError(cause);
}
else
handleGeneralError(t);
}
public void handleGeneralError(Throwable cause) {
logger.log(Level.SEVERE, "An exception occurred that was "
+ "not supposed to happen.", cause);
JOptionPane.showMessageDialog(owner, "Error: "
+ cause.getClass().getSimpleName() + " "
+ cause.getMessage(), "Error", JOptionPane.ERROR_MESSAGE);
}
}
interface FileWriterService
{
// rather than have separate parms for file writing, it is
void handleWriteRequest(FileWriteRequest request, FileWriter writer, FileWriterHandler handler);
}
class SwingWorkerFileWriterService
implements FileWriterService
{
void handleWriteRequest(FileWriteRequest request, FileWriter writer, FileWriterHandler handler) {
Worker worker = new Worker(request, fileWriter, fileWriterHandler);
worker.execute();
}
static class Worker extends SwingWorker<File,Void> {
// set in constructor
private FileWriter fileWriter;
private FileWriterHandler fileWriterHandler;
private FileWriterRequest fileWriterRequest;
protected File doInBackground() {
return fileWriter.writeFile(fileWriterRequest);
}
protected void done() {
fileWriterHandler.done();
try
{
File f = get();
fileWriterHandler.handleFileWritten(f);
}
catch (Exception ex)
{
// you could also specifically unwrap the ExecutorException here, since that
// is specific to the service implementation using SwingWorker/Executors.
fileWriterHandler.handleFileError(ex);
}
}
}
}