Подтвердить что ты не робот

Как этот код 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 и Desktop имеют всю "доброту" синглтонов, а обработка исключений делает невозможным упрощение переноса.

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

4b9b3361

Ответ 1

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

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

Затем 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.

Ответ 2

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

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

Фактор логики приложения

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

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

Отделить Threading

Наконец, мы также можем отделить проблемы с потоками с помощью 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.

Ответ 3

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

Вот очень littel пример с 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 с классом имен, а не анонимным. По словам предполагаемого эксперта, с вашим собственным исполнителем и классом имен вы можете протестировать все, что хотите.