Статьи

JUnit в двух словах: Hello World

JUnit, кажется, самый популярный инструмент тестирования для разработчиков в мире Java. Поэтому неудивительно, что на эту тему написано несколько хороших книг . Но, зарабатывая на жизнь в качестве консультанта, я все еще часто встречаю программистов, которые в большинстве своем имеют смутное представление об инструменте и его правильном использовании.

Поэтому у меня возникла идея написать пару постов, в которых представлены основные приемы. Намерение состоит в том, чтобы обеспечить разумную отправную точку, но избегать пугающих информационных потоков, таких как тестовые шаблоны xUnit 1 . Вместо этого будут ссылки на глубокие статьи, главы книг или особые мнения для дальнейшего чтения, когда это будет уместно.

Несмотря на существование других статей на эту тему, подход, использованный в этом мини-сериале, может быть полезен, чтобы помочь одному или двум разработчикам погрузиться в мир тестирования JUnit — что стоило бы приложить усилия.

Зачем беспокоиться?

Написание качественного программного обеспечения — сложная задача. Что касается многих других сторонников гибких подходов, обширное предварительное планирование не сработало для меня. Но при всей этой методологии я испытал наибольший прогресс, когда мы стали последовательно использовать JUnit с TDD . И действительно, эмпирические исследования, кажется, подтверждают мое понимание того, что эта практика улучшает качество , как говорится в статье infoQ 2 .

Однако тестирование JUnit не так тривиально, как может показаться. Роковая ошибка, которую мы сделали вначале, заключалась в том, чтобы относиться к тестовым классам как к гражданам второго уровня. Постепенно мы поняли, что тест — это гораздо больше, чем простая машина проверки, и — если он не написан с осторожностью, — это может быть проблемой для технического обслуживания и развития 3 .

В настоящее время я склонен рассматривать тестовый пример как сопутствующую спецификацию тестируемого устройства. Совсем похоже на характеристики заготовки типа зубчатого колеса, которая сообщает QA, с какими показателями должен встретиться такой агрегат. Но из-за природы программного обеспечения никто, кроме разработчика, не способен писать такие низкоуровневые спецификации. Благодаря этому автоматизированные тесты становятся важным источником информации о предполагаемом поведении подразделения. И тот, который не устареет так же легко, как документация …

Начиная

Путешествие в тысячу миль начинается с одного шага
Лао Цзы

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

1
2
public class NumberRangeCounterTest {
}

Тестовый класс выражает намерение разработать модуль NumberRangeCounter , который Месарош будет обозначать как тестируемую систему (SUT). И следуя общему шаблону именования, имя устройства дополняется постфиксом Test .

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

Существуют различные способы включения JUnit. Если вы работаете с Eclipse Java IDE, библиотека уже включена. Его просто можно добавить к пути сборки проекта, которого будет достаточно на протяжении всего этого урока. Чтобы получить свою собственную копию, обратитесь к разделу «Загрузка и установка» , чтобы узнать, как интегрировать maven в « Использование JUnit», и, если вам понадобится комплект OSGi, вы найдете его при загрузке на орбите eclipse .

Обычно хорошей идеей будет начать с « счастливого пути» , который является «нормальным» путем выполнения и в идеале общим бизнес-сценарием. Для SUT NumberRangeCounter это может быть тест для проверки того, что счетчик возвращает последовательные числа при последующих вызовах метода, который все еще должен быть определен.

Исполняемый тест JUnit — это публичный нестатический метод, который аннотируется @Test и не принимает параметров. Обобщая всю эту информацию, следующим шагом может стать следующий метод заглушки 4 :

1
2
3
4
5
6
public class NumberRangeCounterTest {
   
  @Test
  public void subsequentNumber() {   
  }
}

Еще немного, но на самом деле JUnit достаточно для первого запуска теста. Тестовые прогоны JUnit могут быть запущены из командной строки или из определенного пользовательского интерфейса, но для целей данного руководства я предполагаю, что у вас есть доступная интеграция с IDE. В Eclipse результат будет выглядеть так: 5 :

greenbar

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

  1. Мы намереваемся написать модуль NumberRangeCounter который отвечает за доставку последовательной последовательности целочисленных значений. Чтобы проверить это, мы могли бы создать локальную переменную, которая принимает новый экземпляр такого счетчика.
  2. 1
    2
    3
    4
    @Test
      public void subsequentNumber() {   
        NumberRangeCounter counter = new NumberRangeCounter();
      }
  3. Поскольку первый тест должен утверждать, что числа, предоставляемые NumberRangeCounter являются последовательными целочисленными значениями, то есть 5, 6, 7 или тому подобное, SUT может использовать метод, обеспечивающий эти значения. Кроме того, этот метод может быть вызван дважды, чтобы обеспечить минимальный набор последующих значений.
  4. 1
    2
    3
    4
    5
    6
    7
    @Test
      public void subsequentNumber() {   
        NumberRangeCounter counter = new NumberRangeCounter();
     
        int first = counter.next();
        int second = counter.next();
      }

Пока выглядит разумно, но как мы можем гарантировать, что тестовый запуск будет обозначен как провал, если значение second не является действительным преемником first ? Для этой цели JUnit предлагает класс org.junit.Assert , который предоставляет набор статических методов, помогающих разработчикам писать так называемые самопроверяющиеся тесты.

Методы с префиксом assert предназначены для проверки определенного условия, выдающего AssertionError с отрицательным результатом. Такие ошибки обнаруживаются средой выполнения JUnit и помечают тест как неудачный в итоговом отчете.

Обновление 2014/08/13: использование org.junit.Assert это только одна возможность. JUnit также включает в себя библиотеку соответствия Hamcrest , которую многие считают лучшим решением в отношении чистого кода. Лично мне больше всего нравится синтаксис сторонней библиотеки AssertJ .

Я думаю, что Assert может быть более интуитивно понятным для новичков, поэтому я выбрал его для этой статьи «Привет, мир». Из-за комментариев к этому решению я понял, что должен упомянуть эти другие возможности, по крайней мере, на данном этапе. Я подробно остановлюсь на использовании Hamcrest и AssertJ в следующем посте.

Чтобы утверждать, что два значения или объекты равны, можно использовать Assert#assertEquals . Поскольку для вызовов методов утверждения очень часто используется статический импорт, subsequentNumber тест Number может быть выполнен следующим образом:

1
2
3
4
5
6
7
8
9
@Test
  public void subsequentNumber() {   
    NumberRangeCounter counter = new NumberRangeCounter();
 
    int first = counter.next();
    int second = counter.next();
 
    assertEquals( first + 1, second );
  }

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

Хотя это руководство посвящено JUnit, а не TDD, я решил использовать последний подход, чтобы подчеркнуть спецификационный характер, который могут иметь чистые тестовые примеры JUnit. Такой подход смещает фокус работы с внутренней части устройства в сторону его использования и требований низкого уровня.

Если вы хотите узнать больше о TDD, в частности о мантре Red / Green / Refactor, используемой для реализации единого модуля, книги « Управляемая тестами на примере Кента Бека» или « Управляемая тестами » Лассе Коскела могут быть хорошими вечерними чтениями.

В следующем фрагменте показано, как будет выглядеть заглушка NumberRangeCounter :

1
2
3
4
5
6
public class NumberRangeCounter {
 
  public int next() {
    return 0;
  }
}

Повторное выполнение теста теперь приводит к появлению красной полосы из-за недостаточной реализации NumberRangeCounter#next() . Это позволяет гарантировать, что спецификация не была случайно встречена бесполезной проверкой или подобным:

redbar

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

AssertionError предоставляет пояснительное сообщение, которое отображается в первой строке трассировки сбоев. Принимая во внимание, что тест с ошибкой может указывать на произвольную ошибку программирования, в результате чего Exception будет выброшено за пределы утверждений теста.

Обратите внимание, что JUnit следует принципу « все или ничего» . Это означает, что если тестовый запуск включает более одного теста, как это обычно бывает, провал одного теста помечает все выполнение как проваленное красной полосой.

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

Вывод

В предыдущих разделах объяснялись самые основы теста JUnit — как он написан, выполнен и оценен. При этом я высоко оценил тот факт, что такие тесты должны разрабатываться с максимально возможными стандартами кодирования, которые только можно придумать. Надеемся, что данный пример был достаточно сбалансирован, чтобы дать понятное введение, не будучи тривиальным. Предложения по улучшению, конечно, высоко ценятся.

Следующий JUnit в посте Nutshell продолжит пример и охватит общую концепцию тестового примера и его четырехфазную тестовую структуру, так что следите за обновлениями.

  1. Не поймите меня неправильно — книга мне очень нравится, но подход общего назначения, вероятно, не лучший способ для начала: тестовые шаблоны xUnit, Джерард Месарос , 2007
  2. Другие исследования перечислены по адресу http://biblio.gdinwiddie.com/biblio/StudiesOfTestDrivenDevelopment, а сравнительный анализ эмпирических исследований можно найти по адресу https://tuhat.halvi.helsinki.fi/portal/files/29553974/2014_01_swqd_author_version.pdf.
  3. См. Также: Сохранение тестов в чистоте, чистый код, глава 9, Роберт С. Мартин, 2009 г.
  4. Существуют различные мнения о том, как назвать метод испытаний. Я записал некоторые соображения по этой теме в « Правильный выбор имен тестов JUnit».
  5. Для получения дополнительной информации о том, как работать с JUnit в Eclipse, вы можете прочитать мой пост Эффективная работа с JUnit в Eclipse.
Ссылка: JUnit в двух словах: Hello World от нашего партнера по JCG Фрэнка Аппеля в блоге Code Affine .