Как этот SwingWorker может кодировать быть сделанным тестируемым

Рассмотрите этот код:

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();

url JTextField, и 'создатель' является введенным интерфейсом для записи файла (так, чтобы часть находилась под тестом). Местоположение, где файл записан, трудно кодируется нарочно, потому что это предназначается как пример. И java.util.logging используется просто для предотвращения внешней зависимости.

Как Вы разделили бы это на блоки для создания этого тестируемым единицей (включая отказ от SwingWorker в случае необходимости, но затем замену его функциональности, по крайней мере, как используется здесь).

Путем я смотрю на него, doInBackground в основном в порядке. Фундаментальная механика создает устройство записи и закрывает его, который почти слишком прост протестировать, и реальная работа находится под тестом. Однако сделанный метод является проблематичной кавычкой, включая ее связь с actionPerformed методом родительский класс и координирование включения и отключение кнопки.

Однако разделение, которое не очевидно. Введение некоторого SwingWorkerFactory делает получение полей GUI намного тяжелее для поддержания (трудно видеть, как это было бы улучшение дизайна). JOpitonPane и Рабочий стол имеют все "совершенство" Одиночных элементов, и обработка исключений лишает возможности переносить получение легко.

Таким образом, каково было бы хорошее решение для подачи этого кода под тестом?

16
задан trashgod 21 June 2010 в 03:04
поделиться

3 ответа

ИМХО, для анонимного класса это сложно. Мой подход заключался бы в рефакторинге анонимного класса примерно так:

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);
        }
    }
}

Это позволяет проверять функциональность записи в файл независимо от графического интерфейса пользователя

Затем actionPerformed становится примерно таким:

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 .

10
ответ дан 30 November 2019 в 22:30
поделиться

Простое решение: a лучше всего подойдет простой таймер; вы запускаете свой таймер, вы запускаете свой actionPerformed, и по истечении тайм-аута бутон должен быть включен и так далее.

Вот очень маленький пример с 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();
    }
}

Предполагаемое экспертное решение: Swing Worker - это RunnableFuture, внутри него FutureTask, встроенная в вызываемый объект, поэтому вы можете использовать свой собственный исполнитель запустить его (RunableFuture). Для этого вам понадобится SwingWorker с именем class, а не anonymous. «С вашим собственным исполнителем и классом имен вы можете тестировать все, что захотите», - говорит предполагаемый эксперт.

-1
ответ дан 30 November 2019 в 22:30
поделиться

Текущая реализация объединяет проблемы потоковой обработки, пользовательского интерфейса и записи файлов - и, как вы обнаружили, связь затрудняет тестирование отдельных компонентов по отдельности.

Это довольно длинный ответ, но он сводится к выделению этих трех проблем из текущей реализации в отдельные классы с определенным интерфейсом.

Вынесите логику приложения за скобки

Для начала сконцентрируйтесь на основной логике приложения и переместите ее в отдельный класс / интерфейс. Интерфейс позволяет упростить имитацию и использование других фреймворков Swing-Threading. Разделение означает, что вы можете тестировать логику своего приложения совершенно независимо от других проблем.

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, поэтому хранение их за интерфейсом помогает избавиться от создаваемого ими беспорядка в коде. Это также позволяет вам использовать другую реализацию для реализации отдельных потоков пользовательского интерфейса / фонового изображения. Например, чтобы использовать Spin , вам нужно только предоставить новую реализацию FileWriterService.

9
ответ дан 30 November 2019 в 22:30
поделиться
Другие вопросы по тегам:

Похожие вопросы: