Basic Java Tools for Building & Testing Apps
Скачать 4.76 Mb.
|
Наиболее важным является то, что мы больше не можем использовать аннотацию @ Test для указания ожиданий. Параметр expected в JUnit 4: @Test (expected = Exception.class) public void shouldRaiseAnException () throws Exception { //... } Теперь мы можем использовать метод assertThrows : public void shouldRaiseAnException () throws Exception { Assertions.assertThrows(Exception.class, () -> { //... }); } Атрибут timeout в JUnit 4: @Test (timeout = 1 ) public void shouldFailBecauseTimeout () throws InterruptedException { Thread.sleep( 10 ); } Теперь метод assertTimeout в JUnit 5: @Test public void shouldFailBecauseTimeout () throws InterruptedException { Assertions.assertTimeout(Duration.ofMillis( 1 ), () -> Thread.sleep( 10 )); } Другие аннотации, которые были изменены в JUnit 5: @ Before аннотация переименована в @ BeforeEach @ After аннотация переименована в @ AfterEach @ BeforeClass аннотация переименована в @ BeforeAll @ AfterClass аннотация переименована в @ AfterAll @ Ignore аннотация переименована в @ Disabled 3.2. Утверждения Теперь мы можем написать сообщения с утверждениями в лямбда-выражениях в JUnit 5, что позволяет при отложенной оценке пропускать сложное построение сообщения до тех пор, пока это не понадобится: @Test public void shouldFailBecauseTheNumbersAreNotEqual__lazyEvaluation () { Assertions.assertTrue( 2 == 3 , () -> "Numbers " + 2 + " and " + 3 + " are not equal!" ); } Мы также можем сгруппировать утверждения в JUnit 5: @Test public void shouldAssertAllTheGroup () { List 1 , 2 , 4 ); Assertions.assertAll( "List is not incremental" , () -> Assertions.assertEquals(list.get( 0 ).intValue(), 1 ), () -> Assertions.assertEquals(list.get( 1 ).intValue(), 2 ), () -> Assertions.assertEquals(list.get( 2 ).intValue(), 3 )); } 3.3. Предположения Новый класс Assumptions теперь находится в org.junit.jupiter.api.Assumptions . JUnit 5 полностью поддерживает существующие методы предположений в JUnit 4, а также добавляет набор новых методов, позволяющих запускать некоторые утверждения только в определенных сценариях: @Test public void whenEnvironmentIsWeb__thenUrlsShouldStartWithHttp () { assumingThat( "WEB" .equals(System.getenv( "ENV" )), () -> { assertTrue( "http" .startsWith(address)); }); } 3.4. Пометка и фильтрация В JUnit 4 мы могли группировать тесты, используя аннотацию @ Category . В JUnit 5 аннотация @ Category заменяется аннотацией @ Tag : @Tag ( "annotations" ) @Tag ( "junit5" ) @RunWith (JUnitPlatform.class) public class AnnotationTestExampleTest { /** ...** /} Мы можем включить/исключить определенные теги, используя maven-surefire-plugin : < build > < plugins > < plugin > < artifactId >maven-surefire-plugin artifactId > < configuration > < properties > < includeTags >junit5 includeTags > properties > configuration > plugin > plugins > build > 3.5. Новые аннотации для запуска тестов @ RunWith использовался для интеграции тестового контекста с другими платформами или для изменения общего потока выполнения в тестовых примерах в JUnit 4. С помощью JUnit 5 теперь мы можем использовать аннотацию @ ExtendWith для обеспечения аналогичной функциональности. В качестве примера, чтобы использовать функции Spring в JUnit 4: @RunWith (SpringJUnit4ClassRunner.class) @ContextConfiguration ( { "/app-config.xml" , "/test-data-access-config.xml" }) public class SpringExtensionTest { /** ...** /} Теперь в JUnit 5 это простое расширение: @ExtendWith (SpringExtension.class) @ContextConfiguration ( { "/app-config.xml" , "/test-data-access-config.xml" }) public class SpringExtensionTest { /** ...** /} 3.6. Новые аннотации правил испытаний В JUnit 4 аннотации @ Rule и @ ClassRule использовались для добавления специальных функций в тесты. В JUnit 5. мы можем воспроизвести ту же логику, используя аннотацию @ ExtendWith . Например, скажем, у нас есть пользовательское правило в JUnit 4 для записи трассировок журнала до и после теста: public class TraceUnitTestRule implements TestRule { @Override public Statement apply (Statement base, Description description) { return new Statement() { @Override public void evaluate () throws Throwable { //Before and after an evaluation tracing here } }; } } И мы реализуем это в тестовом наборе: @Rule public TraceUnitTestRule traceRuleTests = new TraceUnitTestRule(); В JUnit 5 мы можем написать то же самое гораздо более интуитивно понятным способом: public class TraceUnitExtension implements AfterEachCallback , BeforeEachCallback { @Override public void beforeEach (TestExtensionContext context) throws Exception { //... } @Override public void afterEach (TestExtensionContext context) throws Exception { //... } } Используя интерфейсы AfterEachCallback и BeforeEachCallback в JUnit 5, доступные в пакете org.junit.jupiter.api.extension, мы легко реализуем это правило в комплекте тестов: @RunWith (JUnitPlatform.class) @ExtendWith (TraceUnitExtension.class) public class RuleExampleTest { @Test public void whenTracingTests () { /** ...** / } } 3.7. Юнит 5 Винтаж JUnit Vintage помогает в миграции тестов JUnit, выполняя тесты JUnit 3 или JUnit 4 в контексте JUnit 5. Мы можем использовать его, импортировав JUnit Vintage Engine: < dependency > < groupId >org.junit.vintage groupId > < artifactId >junit-vintage-engine artifactId > < version >${junit5.vintage.version} version > < scope >test scope > dependency > 4. Заключение Как мы видели в этой статье, JUnit 5 - это модульная и современная версия платформы JUnit 4. Мы ввели основные различия между этими двумя версиями и намекнули, как перейти с одной на другую Руководство по расширению JUnit 5 Testing JUnit 5 FacebookTumblrPinterestPocketEvernoteTwitterLineEmailRedditDiggVKРесурс 1. Обзор В этой статье мы рассмотрим модель расширения в библиотеке тестирования JUnit 5. Как следует из названия, цель расширений Junit 5 состоит в том, чтобы расширить поведение тестовых классов или методов , и они могут быть повторно использованы для нескольких тестов. До 5 июня версия библиотеки JUnit 4 использовала два типа компонентов для расширения теста: исполнители тестов и правила. Для сравнения, JUnit 5 упрощает механизм расширения, вводя единую концепцию: API Extension . 2. Модель расширения JUnit 5 Расширения JUnit 5 относятся к определенному событию при выполнении теста, называемому точкой расширения. Когда достигается определенная фаза жизненного цикла, механизм JUnit вызывает зарегистрированные расширения. Можно использовать пять основных типов точек расширения: постобработка тестового экземпляра условное выполнение теста обратные вызовы жизненного цикла разрешение параметра Обработка исключений Мы рассмотрим каждый из них более подробно в следующих разделах. 3. Зависимости Maven Во-первых, давайте добавим зависимости проекта, которые нам понадобятся для наших примеров. Нам понадобится основная библиотека JUnit 5: junit-jupiter-engine : < dependency > < groupId >org.junit.jupiter groupId > < artifactId >junit-jupiter-engine artifactId > < version >5.1.0 version > < scope >test scope > dependency > Также давайте добавим две вспомогательные библиотеки для использования в наших примерах: < dependency > < groupId >org.apache.logging.log4j groupId > < artifactId >log4j-core artifactId > < version >2.8.2 version > dependency > < dependency > < groupId >com.h2database groupId > < artifactId >h2 artifactId > < version >1.4.196 version > dependency > Последние версии junit-jupiter-engine , https://search.maven.org/classic/#search % 7Cga% 7C1% 7Ca% 3A% 22h2% 22% 20AND% 20g% 3A% 22com.h2database% 22[h2]и https://search.maven.org/classic/#search%7Cga%7C1%7Ca%3A % 22log4j- core% 22% 20AND% 20g% 3A% 22org.apache.logging.log4j% 22[log4j-core]можно загрузить из Maven Central. 4. Создание расширений JUnit 5 Чтобы создать расширение JUnit 5, нам нужно определить класс, который реализует один или несколько интерфейсов, соответствующих точкам расширения JUnit 5. Все эти интерфейсы расширяют основной интерфейс Extension , который является только интерфейсом маркера. 4.1. TestInstancePostProcessor Extension Расширение этого типа выполняется после того, как был создан экземпляр теста. Интерфейс для реализации - TestInstancePostProcessor , у которого есть метод postProcessTestInstance () для переопределения. Типичным вариантом использования этого расширения является внедрение зависимостей в экземпляр. Например, давайте создадим расширение, которое создает экземпляр объекта logger , а затем вызывает метод setLogger () в тестовом экземпляре: public class LoggingExtension implements TestInstancePostProcessor { @Override public void postProcessTestInstance (Object testInstance, ExtensionContext context) throws Exception { Logger logger = LogManager.getLogger(testInstance.getClass()); testInstance.getClass() .getMethod( "setLogger" , Logger.class) .invoke(testInstance, logger); } } Как видно выше, метод postProcessTestInstance () обеспечивает доступ к экземпляру теста и вызывает метод setLogger () класса теста, используя механизм отражения. 4.2. Условное выполнение теста JUnit 5 предоставляет тип расширения, который может контролировать, следует ли запускать тест. Это определяется реализацией интерфейса ExecutionCondition . Давайте создадим класс EnvironmentExtension , который реализует этот интерфейс и переопределяет метод evaluateExecutionCondition () . Метод проверяет, равняется ли свойство, представляющее имя текущей среды, «qa» , и отключает тест в этом случае: public class EnvironmentExtension implements ExecutionCondition { @Override public ConditionEvaluationResult evaluateExecutionCondition ( ExtensionContext context) { Properties props = new Properties(); props.load(EnvironmentExtension.class .getResourceAsStream( "application.properties" )); String env = props.getProperty( "env" ); if ( "qa" .equalsIgnoreCase(env)) { return ConditionEvaluationResult .disabled( "Test disabled on QA environment" ); } return ConditionEvaluationResult.enabled( "Test enabled on QA environment" ); } } В результате тесты, которые регистрируют это расширение, не будут выполняться в среде «qa» . Если мы не хотим, чтобы условие было проверено, мы можем деактивировать его, установив ключ конфигурации junit.conditions.deactivate ** в шаблон, соответствующий условию. Это может быть достигнуто путем запуска JVM со свойством -Djunit.conditions.deactivate = или путем добавления параметра конфигурации в LauncherDiscoveryRequest : public class TestLauncher { public static void main (String[]args) { LauncherDiscoveryRequest request = LauncherDiscoveryRequestBuilder.request() .selectors(selectClass( "com.baeldung.EmployeesTest" )) .configurationParameter( "junit.conditions.deactivate" , "com.baeldung.extensions.** " ) .build(); TestPlan plan = LauncherFactory.create().discover(request); Launcher launcher = LauncherFactory.create(); SummaryGeneratingListener summaryGeneratingListener = new SummaryGeneratingListener(); launcher.execute( request, new TestExecutionListener[]{ summaryGeneratingListener }); System.out.println(summaryGeneratingListener.getSummary()); } } 4.3. Обратные вызовы жизненного цикла Этот набор расширений связан с событиями в жизненном цикле теста и может быть определен путем реализации следующих интерфейсов: BeforeAllCallback и AfterAllCallback - выполняется до и после все методы испытаний выполнены ** BeforeEachCallBack и AfterEachCallback - выполняется до и после каждого метода испытаний ** BeforeTestExecutionCallback и AfterTestExecutionCallback - выполняется непосредственно перед и сразу после метода испытаний Если тест также определяет методы его жизненного цикла, порядок выполнения такой: , BeforeAllCallback , BeforeAll , BeforeEach Callback , BeforeEach , BeforeTestExecutionCallback , Тестовое задание , AfterTestExecutionCallback , AfterEach , AfterEachCallback , В конце концов , AfterAllCallback В нашем примере давайте определим класс, который реализует некоторые из этих интерфейсов и управляет поведением теста, который обращается к базе данных с помощью JDBC. Сначала давайте создадим простую сущность Employee : public class Employee { private long id; private String firstName; //constructors, getters, setters } Нам также понадобится служебный класс, который создает Connection на основе файла .properties : public class JdbcConnectionUtil { private static Connection con; public static Connection getConnection () throws IOException, ClassNotFoundException, SQLException{ if (con == null ) { //create connection return con; } return con; } } Наконец, давайте добавим простой DAO на основе JDBC, который манипулирует записями Employee : public class EmployeeJdbcDao { private Connection con; public EmployeeJdbcDao (Connection con) { this .con = con; } public void createTable () throws SQLException { //create employees table } public void add (Employee emp) throws SQLException { //add employee record } public List () throws SQLException { //query all employee records } } Давайте создадим наше расширение, которое реализует некоторые интерфейсы жизненного цикла: ** public class EmployeeDatabaseSetupExtension implements BeforeAllCallback , AfterAllCallback , BeforeEachCallback , AfterEachCallback { //... } Каждый из этих интерфейсов содержит метод, который мы должны переопределить. Для интерфейса BeforeAllCallback мы переопределим метод beforeAll () и добавим логику для создания нашей таблицы employees перед выполнением любого тестового метода: private EmployeeJdbcDao employeeDao = new EmployeeJdbcDao(); @Override public void beforeAll (ExtensionContext context) throws SQLException { employeeDao.createTable(); } Далее мы будем использовать BeforeEachCallback и AfterEachCallback , чтобы обернуть каждый тестовый метод в транзакции. Цель этого - откатить любые изменения в базе данных, выполненные в методе test, чтобы следующий тест выполнялся на чистой базе данных. В методе beforeEach () мы создадим точку сохранения, которая будет использоваться для отката состояния базы данных: private Connection con = JdbcConnectionUtil.getConnection(); private Savepoint savepoint; @Override public void beforeEach (ExtensionContext context) throws SQLException { con.setAutoCommit( false ); savepoint = con.setSavepoint( "before" ); } Затем в методе afterEach () мы откатим изменения в базе данных, сделанные во время выполнения тестового метода: @Override public void afterEach (ExtensionContext context) throws SQLException { con.rollback(savepoint); } Чтобы закрыть соединение, мы будем использовать метод afterAll () , который выполняется после завершения всех тестов: @Override public void afterAll (ExtensionContext context) throws SQLException { if (con != null ) { con.close(); } } 4.4. Разрешение параметра Если конструктор или метод теста получает параметр, он должен быть разрешен во время выполнения ParameterResolver . Давайте определим наш собственный ParameterResolver , который разрешает параметры типа EmployeeJdbcDao : public class EmployeeDaoParameterResolver implements ParameterResolver { @Override public boolean supportsParameter (ParameterContext parameterContext, ExtensionContext extensionContext) throws ParameterResolutionException { return parameterContext.getParameter().getType() .equals(EmployeeJdbcDao.class); } @Override public Object resolveParameter (ParameterContext parameterContext, ExtensionContext extensionContext) throws ParameterResolutionException { return new EmployeeJdbcDao(); } } Наш распознаватель реализует интерфейс ParameterResolver и переопределяет методы supportsParameter () и resolveParameter () . Первый из них проверяет тип параметра, а второй определяет логику для получения экземпляра параметра. |