Текущая реализация связывает воедино проблемы с потоками, пользовательским интерфейсом и записью файлов - и, как вы обнаружили, из-за объединения сложно тестировать отдельные компоненты по отдельности.
Это довольно длинный ответ, но онсводится к тому, чтобы вытащить эти три проблемы из текущей реализации в отдельные классы с определенным интерфейсом.
Факторизация логики приложения
Для начала сосредоточимся на ядреприложите логику и перенесите ее в отдельный класс / интерфейс.Интерфейс позволяет легче насмехаться и использовать другие фреймворки с многопоточностью.Разделение означает, что вы можете проверить свою логику приложения полностью независимо от других проблем.
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);
}
}
Отделить пользовательский интерфейс
Теперь, когда логика приложения отделена, мы вычисляемУспех и обработка ошибок.Это означает, что пользовательский интерфейс может быть протестирован без фактической записи файла.В частности, обработка ошибок может быть протестирована без необходимости провоцирования этих ошибок.Здесь ошибки довольно просты, но часто некоторые ошибки могут быть очень трудно спровоцировать.Отделяя обработку ошибок, существует также возможность повторного использования или замены способа обработки ошибок.Например, используя JXErrorPane позже.
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);
}
}
Отделение потоков
Наконец, мы также можем отделить проблемы потоков с помощью FileWriterService.Использование описанного выше FileWriteRequest упрощает кодирование.
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);
}
}
}
}
Каждая часть системы может тестироваться отдельно - логика приложения, представление (успешность и обработка ошибок) и реализация потоков также являются отдельной задачей.
Это может показаться большим количеством интерфейсов, но реализация в основном вырезана и вставлена из вашего исходного кода.Интерфейсы обеспечивают разделение, необходимое для того, чтобы сделать эти классы тестируемыми.
Я не большой поклонник SwingWorker, поэтому сохранение их за интерфейсом помогает избежать беспорядка, который они создают в коде.Это также позволяет вам использовать другую реализацию для реализации отдельных UI / фоновых потоков.Например, чтобы использовать Spin , вам нужно только предоставить новую реализацию FileWriterService.