Рефакторинг для тестирования - PullRequest
1 голос
/ 18 февраля 2011

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


public class ConcreteThread extends OtherThread {
  private DAOfirst firstDAO;
  private DAOsecond secondDAO;
  private TransformService transformService;
  private NetworkService networkService;

  public ConcreteThread(DAOfirst first, DAOsecond second, TransformService service1, 
       NetworkService service2) {
    firstDAO = first;
    secondDAO = second;
    transformService = service1;
    networkService = service2;
  }

  public Future go() {
    Results r1 = firstDAO.getResults();
    MyCallable c1 = new MyCallable(r1);
    return super.getThreadPool().submit(c1);
  }

  private class MyCallable implements Callable {   
    private Results result; 
    private Long count;
    private MyCallable(Results r) {
      this.result = r;
      this.count = new Long(0);
    }

    public Long call() {
      Singleton transactions = Singleton.getInstance();
      try {
        transactions.begin();
        while(result != null) {
          Transformed t = transformService.transform(r1);
          networkService.sendSomewhere(t);
          count = count += result.size();
          secondDao.persist(result);
          result = firstDao.getNext(result);
        }
      }
      catch (Exception e) {
        e.printStackTrace();
      }
      finally {
         transactions.end(); 
      }
    }
  }
 

Ни один из этих классов (внутренний или внешний) не имеет модульных тестов, и оказывается, что во внутреннем классе, MyCallable, есть ошибка. В упрощенной версии кода, который я дал вам выше, ошибки нет.

Итак, давайте предположим, что вы решили исправить ошибку и внедрили некоторые модульные тесты для MyCallable. У меня вопрос такой; Как именно вы будете писать модульные тесты для внутреннего класса MyCallable?

Мое собственное решение было для первого рефакторинга MyCallable и ConcreteThread. MyCallable был сделан открытым классом в своем собственном файле, а ConcreteThread теперь передает DAOs, Services и Singleton в качестве аргументов конструктора для MyCallable, а не полагается на доступ внутреннего класса к своим закрытым переменным.

Затем я активно использовал EasyMock в модульных тестах, чтобы смоделировать эти зависимости и убедиться, что они вызывались так, как я ожидал.

Следствием всего этого является то, что код для MyCallable несколько больше, чем был. Поскольку он больше не имеет доступа к закрытым переменным в ConcreteThread, ConcreteThread должен передавать их в качестве аргументов в конструкторе, а MyCallable устанавливает их как частные переменные.

Вы думаете, что это был неправильный подход? Что, возможно, выполнив такой рефакторинг, я нарушил инкапсуляцию и добавил ненужный шаблон к базе кода? Вы бы вместо этого использовали отражение в тестах?

Ответы [ 2 ]

3 голосов
/ 18 февраля 2011

Следствием всего этого является то, что код для MyCallable несколько больше, чем был. Поскольку он больше не имеет доступа к закрытым переменным в ConcreteThread, ConcreteThread должен передавать их в качестве аргументов в конструкторе, а MyCallable устанавливает их в качестве закрытых переменных.

Это хорошее следствие, MyCallable больше не зависит от изменений в ConcreteThread.

Я думаю, что вопрос и ответ довольно субъективны, но я думаю, что вы следовали принципу SOLID в рефакторинге (что хорошо).

И если вы можете, сделайте пакет MyCallable защищенным, а не публичным:)

1 голос
/ 18 февраля 2011

Для меня это выглядит так: Внутренний класс - это деталь реализации внешнего класса.

Итак, я задаю этот вопрос. Можете ли вы продемонстрировать ошибку, написав провальный модульный тест для ConcreteThread.Go ()? Что должно быть иначе, как только вы сделаете изменение для внутреннего класса - что будет внешне видимым изменением? Как только вы это поймете - вы уже в пути.

...