Статьи

Юнит 5 Государство Союз

JUnit 5 разрабатывался около 14 месяцев, а прототипу почти год. Пришло время подвести итог тому, что произошло до сих пор, где находится проект и куда он идет.

Все технические данные основаны на текущей версии JUnit, этап 2 . Более подробную информацию вы найдете в официальном руководстве пользователя, с отдельными версиями milestone 2 и самой последней сборкой , или в моем блоге , где я написал серию о JUnit 5 , которую я обновляю всякий раз, когда выпускается новая веха.

Ранее в Battlestar JUnit

Почему Юнит 5? Почему не 4.13, который был в разработке с тех пор, вечно? Две с половиной причины …

До свидания, Юнит 4

Частично успех JUnit связан с его великолепной поддержкой инструментов, для которой создатели инструментов даже использовали рефлексию (вплоть до частных полей) для доступа к информации, которую API не раздает. Это связывало инструменты с деталями реализации, что, в свою очередь, мешало разработчикам JUnit развивать его — никому не нравятся взломанные проекты. Это важная причина того, почему прогресс JUnit в основном остановился.

Тогда есть бегуны и правила . Они были созданы, чтобы расширить JUnit и сделали хорошую работу в этом — хорошо, но не превосходно. Бегуны — это тяжеловесная концепция, в которой нужно управлять всем жизненным циклом испытаний. Но что еще хуже, вы можете использовать только одного бегуна за раз — очень строгое ограничение. Вот почему правила были введены в 4.7. Несмотря на то, что они намного проще и в основном сочетаемы, у них есть и другие недостатки, а именно то, что они ограничены определенными типами поведения, которые лучше всего суммировать, как до / после.

Последней и наименее была версия Java. В наши дни все хотят поиграть с лямбдами, и если бы JUnit 4 требовал Java 8, это было бы трудно продать.

Здравствуйте, JUnit 5

Так что в 2015 году команда сформировалась вокруг идеи полного переписывания. Вначале получивший название JUnit Lambda , проект провел успешную краудфандинговую кампанию и получил достаточно денег и спонсорскую поддержку работодателя, чтобы работать над ним в течение шести месяцев. Затем последовали семинары, прототип, альфа-версия и два этапа, а с начала этого года проект называется JUnit 5 . К настоящему времени средства израсходованы, и они разрабатываются, как и многие другие замечательные проекты с открытым исходным кодом: преданными своему делу людьми в свободное время в течение первого и последнего часов дня.

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

Как это выглядит?

Вместо того, чтобы вдаваться в подробности о новом API, я прошу вас представить полномасштабные тесты JUnit 4 со всеми махинациями, которые предлагает API. Вот как JUnit 5 отличается от этого:

  • Тестовые классы и методы могут быть видны в пакете.
  • Методы интерфейса по умолчанию могут быть методами тестирования.
  • @Test больше не принимает параметры. Ожидаемые исключения и тайм-ауты реализуются через утверждения.
  • Вы можете создавать тесты динамически во время выполнения с @TestFactory аннотированных методов @TestFactory (подробнее об этом позже).
  • Аннотации жизненного цикла получили новые названия:
    @Before стал @BeforeEach а @BeforeClass теперь @BeforeAll ; аналогично для @After...
  • @Ignore изменено на @Disabled .
  • В утверждениях сообщения об ошибках идут последними и могут создаваться лениво.
  • Новое утверждение assertAll выполняет данные проверки и всегда сообщает обо всех результатах, даже если некоторые утверждения не выполнены.
  • @Nested можно использовать для запуска тестов во внутренних классах.
  • @DisplayName присваивает @DisplayName удобочитаемые имена.
  • Пропуски и правила исчезли и заменены новым механизмом расширения, который легко интегрируется с нативным API (подробнее об этом позже).
  • Тесты могут иметь параметры, где экземпляры создаются расширениями (опять же, позже).

В общем, вот как может выглядеть тестовый класс:

 @DisplayName("Some example test cases") class ExampleTests { @BeforeAll static void initAll() { } @BeforeEach void init() { } @Test @DisplayName("Custom test name containing spaces") void succeedingTest() { } @Test void groupedAssertions() { // In a grouped assertion all assertions are executed, and any // failures will be reported together. assertAll("address", () -> assertEquals("John", address.getFirstName()), () -> assertEquals("User", address.getLastName()) ); } @Test @DisplayName("") @Disabled("for demonstration purposes") void skippedTest() { // not executed } @Test @CoolInstanceProvider void testWithParameters(MyCoolClass coolInstance) { // do something with 'coolInstance' } @TestFactory Collection<DynamicTest> dynamicTestsFromCollection() { return Arrays.asList( dynamicTest("1st dynamic test", () -> assertTrue(true)), dynamicTest("2nd dynamic test", () -> assertEquals(4, 2 * 2)) ); } @AfterEach void tearDown() { } @AfterAll static void tearDownAll() { } @DisplayName("Some nested test cases") class NestedTest { @Test @DisplayName("╯°□°)╯") void failingTest() { fail("a failing test"); } } } 

Что касается @Nested и @DisplayName , то вот как выглядит пример, написанный командой JUnit в IntelliJ 2016.2:

junit5-вложен-тесты

Неплохо, правда? Для более детального ознакомления с руководством пользователя или моим постом, охватывающим основы .

Как я могу использовать это?

Чтобы начать писать тесты, все что вам нужно сделать, это включить артефакт API
org.junit.jupiter:junit-jupiter-api:5.0.0-M2 в вашем проекте. К сожалению, выполнение тестов немного сложнее …

Иды

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

  • IntelliJ , начиная с 2016 года , имеет встроенную поддержку, которая работает достаточно хорошо.
  • Команда Eclipse начала работать над этим — посмотрите соответствующие проблемы в JUnit и в Eclipse .
  • NetBeans не имеет поддержки, и я даже не смог найти проблему .

Инструменты сборки

Как насчет поддержки встроенных инструментов? Команда JUnit внедрила провайдера Maven Surefire и плагин Gradle, но это всего лишь подтверждение концепции. Чтобы полностью интегрировать и протестировать их, команда хотела бы, чтобы соответствующие проекты взяли на себя эти кодовые базы.

откаты

Эта ситуация немного неудовлетворительная, но, к счастью, есть альтернативы. Один из них — просто обернуть все тесты JUnit 5 в набор тестов JUnit 4 и рассчитывать на поддержку JUnit 4:

 @RunWith(JUnitPlatform.class) @SelectPackages({ "my.test.package" }) public class JUnit5TestSuite { } 

См. Руководство пользователя для деталей настройки.

Другой альтернативой является консоль запуска. Опять же, обратитесь к руководству пользователя, чтобы начать, и ознакомьтесь с моим постом установки JUnit, если у вас возникнут проблемы. Когда вы закончите, вы можете запустить тесты следующим образом:

 junit-platform-console -p -p target/classes/:target/test-classes -a 

Юпитер

Космическое пространство

Теперь, когда вы знаете, как писать и запускать тесты JUnit 5, давайте пойдем дальше основ и взглянем на дополнительные темы.

Динамические тесты

Динамические тесты — это новая концепция в JUnit 5. Они позволяют легко создавать тесты во время выполнения, которые инструменты будут распознавать как полные тестовые случаи.

Пример объясняет это лучше всего. Предположим, у вас есть метод testPoint(Point p) который утверждает что-то в заданной точке, и вы хотите запустить этот тест для множества точек. Вы можете написать простой цикл:

 @Test void testPoints() { getTestPoints().forEach(this::testPoint); } 

Это имеет существенный недостаток: хотя концептуально это много тестов, для JUnit это всего лишь один. Первым среди нежелательных последствий является то, что, как только первый тест не проходит, все остальные даже не выполняются. Другое — инструменты, в частности IDE, сообщат об этом как об одном тесте, который тоже не идеален.

JUnit 5 дает нам выход. Тестовый код может быть DynamicTest экземпляр DynamicTest который создается во время выполнения. Методы, которые их возвращают, могут быть аннотированы новой аннотацией @TestFactory и JUnit будет вызывать их, вставлять созданные тесты в план тестирования и выполнять их.

 @TestFactory Stream<DynamicText> testingPoints() { return getTestPoints() .stream() .map(p -> dynamicTest("Testing " + p, () -> pointTest(p))); } 

Ухоженная. И если вы действительно хотите, вы можете использовать динамические тесты для написания тестов с лямбда-выражениями

Модель расширения

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

Точки расширения

Для большинства этапов жизненного цикла теста существует точка расширения:

  • Постпроцессор тестового экземпляра
  • BeforeAll Обратный звонок
  • Тест и условие выполнения контейнера
  • BeforeEach Callback
  • Разрешение параметра
  • Перед выполнением теста
  • После выполнения теста
  • Обработка исключений
  • AfterEach Callback
  • AfterAll Callback

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

 public interface BeforeEachCallback extends Extension { void beforeEach(TestExtensionContext context) throws Exception; } 

Я уже отмечал, что тесты могут иметь параметры. Поскольку JUnit не знает, как создать экземпляр MyCoolClass , расширения должны предоставить их. Вот эта точка расширения:

 public interface ParameterResolver extends Extension { boolean supports( ParameterContext parameterContext, ExtensionContext extensionContext) throws ParameterResolutionException; Object resolve( ParameterContext parameterContext, ExtensionContext extensionContext) throws ParameterResolutionException; } 

Для каждого параметра JUnit будет вызывать support для каждого зарегистрированного расширения, генерировать исключение, если не одно из них возвращает true , и в противном случае запрашивать одно расширение для разрешения параметра.

В качестве подтверждения концепции команда написала расширение Mockito, которое @Mock параметры, помеченные @Mock . Может использоваться следующим образом:

 void myCoolClassTest(@Mock MyCoolClass instance) { // ... } 

Написание расширений

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

Контекст расширения предоставляет доступ ко многим интересным вещам, например, к тестовому классу или методу в виде AnnotatedElement , поэтому расширение может отражать их. Тогда есть ExtensionStore . Поскольку JUnit не заинтересован в отслеживании экземпляров расширений, он не дает никаких гарантий относительно их жизненного цикла. Следовательно, классы расширения должны быть без сохранения состояния и могут использовать хранилище для сохранения информации. Другим интересным аспектом является TestReporter , который позволяет расширениям (и тестам в этом отношении) публиковать сообщения таким образом, чтобы их можно было легко найти с помощью внешних инструментов.

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

 class SimpleBenchmarkExtension implements BeforeTestExecutionCallback, AfterTestExecutionCallback { // we need a namespace to access the store and a key to persist information private static final Namespace NAMESPACE = Namespace .create("com", "sitepoint", "SimpleBenchmarkExtension"); private static final String LAUNCH_TIME_KEY = "LaunchTime"; @Override public void beforeTestExecution(TestExtensionContext context) { storeNowAsLaunchTime(context); } @Override public void afterTestExecution(TestExtensionContext context) { long launchTime = loadLaunchTime(context); long elapsedTime = currentTimeMillis() - launchTime; report(context, elapsedTime); } private static void storeNowAsLaunchTime(ExtensionContext context) { context.getStore(NAMESPACE).put(LAUNCH_TIME_KEY, currentTimeMillis()); } private static long loadLaunchTime(ExtensionContext context) { return context.getStore(NAMESPACE).get(LAUNCH_TIME_KEY, long.class); } private static void report(ExtensionContext context, long elapsedTime) { String message = String .format("Test '%s' took %d ms.", context.getDisplayName(), elapsedTime); // 'publishReportEntry' pipes key-value pairs into the test reporter context.publishReportEntry(singletonMap("Benchmark", message)); } } 

Регистрация расширений

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

 @Benchmark @Test void test() { // ... } 

Архитектура

Быстрое погружение в архитектуру, что действительно интересно. Чтобы разделить заботу тестировщиков, разработчиков расширений и инструментов, а также позволить другим инфраструктурам тестирования воспользоваться преимуществами великолепной поддержки инструментов JUnit (ну, пока она еще не готова, но будет), JUnit 5 разделен на три подпроекта:

  • JUnit Jupiter предоставляет API, о котором мы говорили ранее, и механизм, который обнаруживает и запускает тесты.
  • JUnit Vintage адаптирует JUnit 3/4 для работы в новой архитектуре JUnit 5, что означает, что он также предоставляет движок.
  • Платформа JUnit — это место, где происходит волшебство. Он предоставляет API для инструментов, чтобы инструктировать выполнение теста, и когда он вызывается, он находит механизмы тестирования (как два вышеупомянутых), передает инструкцию им и позволяет им делать свое дело.

С одной стороны, это облегчает запуск тестов JUnit 3, 4 и 5 в рамках одного механизма. Но разделение между платформой, которая содержит API движка, и реализациями движка также позволяет интегрировать все виды тестовых сред в механизм JUnit. Предполагая, что механизм TestNG будет существовать, вот как это получится:

JUnit-5-архитектура

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

Это может привести к новому поколению платформ тестирования!

Дорога отсюда

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

Текущие обсуждения

Обсуждается много очень интересных тем!

JUnit 4 Совместимость

Разве не было бы замечательно создавать адаптеры для существующих правил JUnit 4? Это значительно упростит миграцию тестов в API JUnit Jupiter (в отличие от простого запуска их в JUnit 5). Вопрос № 433 исследует именно это. Но код — не единственная хорошая вещь, которую может создать проект. Проблемы № 169 и № 292 направлены на обеспечение хорошего руководства по миграции с 4 до 5.

Модель расширения

Лично я думаю, что чего-то не хватает в динамических тестах. Было бы здорово, чтобы расширения взаимодействовали с этой функцией! Один из способов сделать это — точка расширения, в которую могут подключаться сторонние библиотеки для внедрения динамически созданных тестов. Затем они могли бы предложить интересные API для параметризации тестов, например. Обсуждение дешево. тем не менее, поэтому я создал проблему и прототипную реализацию для этой функции. Я надеюсь, что это будет рассмотрено во время разработки этапа 4.

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

В настоящее время также невозможно взаимодействовать с потоковой моделью Юпитера. Это проблема для расширений, которые хотят перенести тесты в другие потоки, например, чтобы убедиться, что тесты Swing или JavaFX выполняются в потоке диспетчеризации событий или в потоке приложения, соответственно. Есть несколько проблем, связанных с этим: # 16 , # 20 , # 157

Другие интересные темы:

  • точки расширения …
    • отредактировать план тестирования после обнаружения, # 276
    • изменить результаты теста, # 542
  • глобальный реестр расширений, # 448
  • растяжки на полях, № 497
  • API для управления программными расширениями, # 506

Сценарные тесты

Большой темой являются тесты сценариев, которые являются номенклатурой JUnit для тестов, которые упорядочены и работают в общем состоянии (отслеживается # 48 ). TestNG предлагает это уже и является важной причиной для людей отойти от JUnit. Ну, была причина.

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

другие

Вот пара других интересных идей:

  • механизм запуска для определения возможностей двигателя, # 90
  • проверки работоспособности тестового кода, написанного разработчиками, # 121
  • множество улучшений в консоли запуска, фильтр поиска
  • более гибкая модель потоков, например, для распараллеливания тестов, поисковый фильтр

Больше вех

Каков план на следующие месяцы? Когда-нибудь в течение следующих нескольких недель будет выпущен milestone 3, который фокусируется на совместимости с JUnit 4 и улучшенном API для инструментов. После этого запланированы еще два этапа: номер 4, посвященный параметризованным тестам, расширенные динамические тесты и документация, и номер 5, посвященный тестам сценариев, повторным тестам и выполнению тестов в пользовательском потоке.

Резюме

Мы быстро ознакомились с API, с которым собираемся написать тесты, и увидели, что он похож на JUnit 4, но содержит много вдумчивых дополнений. В первую очередь это встроенная поддержка вложенных тестов, удобочитаемое отображаемое имя и динамически создаваемые тесты. Модель расширения очень перспективна, и архитектура, разделяющая JUnit 5 на Jupiter, Vintage и Platform, просто великолепна, наделяя другие тестовые среды полной поддержкой инструментов, как только они внедряют движок.

С другой стороны, мы изучили историю создания JUnit 5 и потратили некоторое время на обсуждение текущего состояния поддержки инструментов, которого, за исключением IntelliJ, по-прежнему не хватает. Наконец, мы рассмотрели некоторые из текущих дискуссий и будущих событий, которые, как мне кажется, столь же захватывающие, как и многообещающие. Я не могу дождаться выхода релиза JUnit 5 GA — надеюсь, когда-нибудь в 2017 году.