Статьи

Больше единиц с MoreUnit

Чуть больше года назад я написал пост о работе с JUnit в Eclipse . Один из комментаторов рекомендовал MoreUnit , чтобы еще больше повысить эффективность тестирования. Попробовав это, я был в восторге, и моя вегетативная нервная система немедленно запомнила сочетания клавиш плагина…

Кроме того, после использования MoreUnit некоторое время я обнаружил небольшое, но заметное изменение в одной из моих рабочих процедур. Точнее, кажется, что я имею тенденцию извлекать больше классов, охватываемых собственными юнит-тестами во время рефакторинга 1, чем я делал раньше. Поскольку поддержание сплоченности приводит ко многим маленьким классам 2, это, вероятно, хорошо.

Что ж, можно сказать «MoreUnit, название говорит само за себя», но так как я не ожидал, что это будет означать так, я решил, что это может быть хорошей темой для поста. Итак, давайте посмотрим на следующий пример, чтобы начать:

01
02
03
04
05
06
07
08
09
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
class ActionItem extends MouseAdapter  {
 
  private Label control;
  private Runnable action;
  private boolean mouseDown;
 
  [...]
 
  ActionItem( Composite parent, Runnable action ) {
    this.control = new Label( parent, SWT.NONE );
    this.control.addMouseListener( this );
    this.action = action;
  }
 
  @Override
  public void mouseDown( MouseEvent event ) {
    markMouseDown();
  }
 
  @Override
  public void mouseUp( MouseEvent event ) {
    handleMouseUp( event );
  }
 
  private void markMouseDown() {
    mouseDown = true;
  }
 
  private void handleMouseUp( MouseEvent event ) {
    if( mouseDown && inRange( event ) ) {
      action.run();
    }
    mouseDown = false;
  }
 
  private static boolean inRange( MouseEvent event ) {
    Point size = ( ( Control )event.widget).getSize();
    return    event.x >= 0 && event.x <= size.x
           && event.y >= 0 && event.y <= size.y;
  }
 
  [...]
}

Этот класс является выдержкой из пользовательского элемента управления UI 3 и показывает, как такой элемент управления может реализовывать поведение щелчка мыши. Мы исходим из того, что у нас есть соответствующий тестовый блок с достаточным охватом. Также можно предположить, что несколько этапов структурного рефакторинга уже были выполнены до того, как была достигнута договоренность с фрагментом кода выше.

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

01
02
03
04
05
06
07
08
09
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
class ActionItem extends MouseAdapter  {
 
  private Label control;
  private ClickHandler clickHandler;
 
  [...]
 
  ActionItem( Composite parent, Runnable action ) {
    this.control = new Label( parent, SWT.NONE );
    this.clickHandler = new ClickHandler( action );
    this.control.addMouseListener( this );
  }
 
  @Override
  public void mouseDown( MouseEvent event ) {
    clickHandler.markMouseDown();
  }
 
  @Override
  public void mouseUp( MouseEvent event ) {
    clickHandler.handleMouseUp( event );
  }
 
  static class ClickHandler {
 
    private Runnable action;
    private boolean mouseDown;
 
    ClickHandler( Runnable action ) {
      this.action = action;
    }
 
    private void markMouseDown() { [...] }
    private void handleMouseUp( MouseEvent event ) { [...] }
    private static boolean inRange( MouseEvent event ) { [...] }
  }
 
  [...]
 
}

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

Однако теперь существует класс, служащий неким «подкомпонентом», который косвенно охватывается тестовым примером «основного» компонента. Я сделал замечание, что такие классы часто развивают собственную жизнь. Это означает, что их функциональность расширяется, или они используются другими классами или и тем, и другим. В какой-то момент времени, вероятно, становится довольно сложно добавлять значимые и / или понятные тесты. Это потому, что самому тестовому классу не хватает сцепления, которое было введено в тестируемую единицу путем разделения его на две части.

По этой причине я обычно сразу создаю конкретный тестовый пример для извлеченного класса. Тем более, что с доступным MoreUnit это может быть достигнуто довольно интуитивно, фактически обеспечивая новый тестовый набор автоматически базовым набором заглушек метода тестирования. Продолжая наш пример, мы откроем извлеченный ClickHandler в редакторе, нажмите ‘ctrl + j’ 5 и выберите в мастере последующих действий, для какого метода мы хотели бы создать заглушку:

Новый тест

Нажатие «Готово» создает тестовый пример, правильно расположенный в соответствующей папке исходного кода вашего проекта 6, который выглядит — в зависимости от ваших настроек MoreUnit — примерно так:

01
02
03
04
05
06
07
08
09
10
11
12
public class ClickHandlerTest {
 
  @Test
  public void testMarkMouseDown() {
    fail( "Not yet implemented" );
  }
 
  @Test
  public void testHandleMouseUp() {
    fail( "Not yet implemented" );
  }
}

Заглушки могут служить отправной точкой для наполнения тестового примера с нуля. Но ActionItemTest в ActionItemTest уже есть полный охват, предоставляющий набор тестов. Так что еще одна возможность — переместить содержимое тестов, связанных с щелчком мыши, во вновь созданный ClickHandlerTest . Хотя последний теперь проводит тщательное тестирование нового модуля, необходимо позаботиться о том, чтобы в ActionItemTest оставалось достаточно тестов, обеспечивающих надлежащую интеграцию ClickHandler 7 .

Вывод

Работая так в течение некоторого времени, у меня сложилось впечатление, что ремонтопригодность и дальнейшая разработка моего кода (включая тестовые примеры) постепенно улучшаются. Конечно, можно сказать, что MoreUnit не должен быть решающим фактором для принятия решения о том, стоит ли переносить определенную ответственность в свой класс. Но мне кажется, что это облегчает решение сделать это, помогая преодолеть свое слабое «я», имея соответствующий тестовый набор для нового класса всего в нескольких щелчках мыши.

  1. Смотрите мантру красный / зеленый / рефакторинг Test Driven Development
  2. Роберт К. Мартин, Чистый код, Глава 10: Классы
  3. Хотя пример был вдохновлен моей текущей работой, я сократил ее до минимума, необходимого для этого поста.
  4. Дальнейшим шагом рефакторинга может быть изменение ClickHandler таким образом, чтобы он расширял MouseAdapter или реализовывал MouseListener. После этого сам обработчик щелчка может быть зарегистрирован как слушатель мыши на ярлыке, заменяющем элемент действия. Это полностью исключило бы делегирование методов-обработчиков мыши в ActionItem . Однако я пропускаю этот шаг здесь, чтобы сохранить объем сообщения
  5. Этот ярлык обычно используется для переключения между тестируемыми устройствами и их соответствующими тестовыми случаями. Как только вы привыкнете к этому довольно быстро, он как-то служит еще более «отсутствующим тестовым напоминанием», чем отдельные декораторы…
  6. В системах на основе OSGi это также может быть тестовым фрагментом
  7. Обратите внимание, что процесс извлечения упрощает настройку тестов функциональных тестов щелчком мыши. Извлеченный класс не имеет зависимости от производного виджета SWT в конструкторе, что позволяет предоставлять, например, тестовый набор, основанный на имитациях. Последнее невозможно без суеты для ActionItem и поэтому для создания, инициализации и удаления экземпляров Display / Shell например, методы setup / teardown.

Ссылка: Больше юнитов с MoreUnit от нашего партнера JCG Фрэнка Аппеля в блоге Code Affine .