До написания этой статьи я знал только несколько часто используемых аннотаций JUnit 4, таких как
@RunWith @Test @Before @After @BeforeClass @AfterClass
Сколько раз вам приходилось комментировать тест? К моему удивлению, для этого есть аннотации.
@Ignore("Reason for ignoring") @Disabled("Reason for disabling")
Что ж, оказывается, есть несколько других аннотаций, особенно в JUnit 5, которые могли бы помочь писать лучшие и более эффективные тесты.
Чего ожидать?
В этой статье я рассмотрю следующие аннотации с примерами использования. Цель этой статьи – познакомить вас с аннотацией, в ней не будет вдаваться в подробности каждой аннотации.
* Все примеры из этой статьи также доступны на Github. Пожалуйста, ознакомьтесь со следующим хранилищем. *
рамеди/джунит-аннотации-примеры
Аннотации JUnit 4 и 5 с примерами
Целевая аудитория этой статьи – разработчики любого уровня.
Июнь 4
Следующие JUnit 4 аннотации будет охвачен
Июнь 5
Следующие JUnit 5 аннотации объясняются примерами
Зависимости JUnit
Все примеры в этой статье тестируются с использованием следующих зависимостей JUnit.
testImplementation 'org.junit.jupiter:junit-jupiter-api:5.3.1' testRuntimeOnly 'org.junit.jupiter:junit-jupiter-engine:5.3.1' testCompileOnly 'junit:junit:4.12' testRuntimeOnly 'org.junit.vintage:junit-vintage-engine:5.3.1'
Пожалуйста, ознакомьтесь с Github репозиторий для получения более подробной информации.
Давайте рассмотрим аннотации JUnit 4 одну за другой с кратким примером использования
Привет, Мир модульного тестирования
Аннотация @Test
используется для обозначения метода как теста.
public class BasicJUnit4Tests { @Test public void always_passing_test() { assertTrue("Always true", true); } }
Аннотации уровня класса и уровня теста
Аннотации, такие как @BeforeClass
и @AfterClass
являются аннотациями на уровне класса JUnit 4 .
public class BasicJUnit4Tests { @BeforeClass public static void setup() { // Setup resource needed by all tests. } @Before public void beforeEveryTest() { // This gets executed before each test. } @Test public void always_passing_test() { assertTrue("Always true", true); } @After public void afterEveryTest() { // This gets executed after every test. } @AfterClass public static void cleanup() { // Clean up resource after all are executed. } }
Аннотации @перед всеми
и @в конце концов
являются JUnit 5 эквиваленты и импортированы с использованием следующих инструкций.
// JUnit 5 import org.junit.jupiter.api.BeforeAll import org.junit.jupiter.api.AfterAll
Игнорирование теста против Предположение
Тест игнорируется с помощью @Ignore
аннотации, или утверждение может быть изменено на предположение, и JUnit Runner проигнорирует ошибочное предположение.
Предположения используются при работе с такими сценариями, как сервер и локальный часовой пояс. Когда предположение не выполняется, возникает исключение Нарушение предположения
, и JUnit runner проигнорирует его.
public class BasicJUnit4Tests { @Ignore("Ignored because of a good reason") @Test public void test_something() { assertTrue("Always fails", false); } }
Выполнение тестов в порядке
Как правило, хорошей практикой является написание модульных тестов, не зависящих от заказа.
@FixMethodOrder(MethodSorters.NAME_ASCENDING) public class FixedMethodOrderingTests { @Test public void first() {} @Test public void second() {} @Test public void third() {} }
В дополнение к сортировке в порядке возрастания имен тестов, Сортировщик методов
позволяет ПО УМОЛЧАНИЮ
и JVM
сортировка по уровням.
Добавление времени ожидания для тестов
Модульные тесты в основном будут выполняться быстро; однако могут быть случаи, когда модульный тест займет больше времени.
В JUnit 4 , @Тест
аннотация принимает тайм-аут
аргумент, как показано ниже
import org.junit.Ignore; import org.junit.Test; public class BasicJUnit4Tests { @Test(timeout = 1) public void timeout_test() throws InterruptedException { Thread.sleep(2); // Fails because it took longer than 1 second. } }
В JUnit 5 время ожидания происходит на уровне утверждения
import static java.time.Duration.ofMillis; import static org.junit.jupiter.api.Assertions.assertTimeout; import org.junit.jupiter.api.Test; public class BasicJUnit5Tests { @Test public void test_timeout() { // Test takes 2 ms, assertion timeout in 1 ms assertTimeout(ofMillis(1), () -> { Thread.sleep(2); }); } }
Иногда более целесообразно применять тайм-аут для всех тестов, который включает в себя @beforeEach/До
и @после каждого/После
также.
public class JUnitGlobalTimeoutRuleTests { @Rule public Timeout globalTimeout = new Timeout(2, TimeUnit.SECONDS); @Test public void timeout_test() throws InterruptedException { while(true); // Infinite loop } @Test public void timeout_test_pass() throws InterruptedException { Thread.sleep(1); } }
Использование правила с тестами JUnit
Я нахожу @Rule
очень полезно при написании модульных тестов. Правило применяется к следующему
- Время ожидания – показано выше
- Ожидаемое исключение
- Временная папка
- Сборщик ошибок
- Верификатор
Правило ожидаемого исключения
Это правило можно использовать для гарантии того, что тест выдаст ожидаемое исключение. В JUnit 4 мы можем сделать следующее
public class BasicJUnit4Tests { @Test(expected = NullPointerException.class) public void exception_test() { throw new IllegalArgumentException(); // Fail. Not NPE. } }
В JUnit 5 , однако, вышеуказанное может быть достигнуто с помощью утверждения, следующего
public class BasicJUnit5Tests { @Test public void test_expected_exception() { Assertions.assertThrows(NumberFormatException.class, () -> { Integer.parseInt("One"); // Throws NumberFormatException }); } }
Мы также можем определить Правило на уровне класса и повторно используйте его в тестах
public class JUnitRuleTests { @Rule public ExpectedException thrown = ExpectedException.none(); @Test public void expectedException_inMethodLevel() { thrown.expect(IllegalArgumentException.class); thrown.expectMessage("Cause of the error"); throw new IllegalArgumentException("Cause of the error"); } }
Правило временных папок
Это Правило обеспечивает создание и удаление файла и папки в течение жизненного цикла теста.
public class TemporaryFolderRuleTests { @Rule public TemporaryFolder temporaryFolder = new TemporaryFolder(); @Test public void testCreatingTemporaryFileFolder() throws IOException { File file = temporaryFolder.newFile("testFile.txt"); File folder = temporaryFolder.newFolder("testFolder"); String filePath = file.getAbsolutePath(); String folderPath = folder.getAbsolutePath(); File testFile = new File(filePath); File testFolder = new File(folderPath); assertTrue(testFile.exists()); assertTrue(testFolder.exists()); assertTrue(testFolder.isDirectory()); } }
Правило собирателя ошибок
Во время выполнения модульного теста, если существует много утверждений, и первое из них не выполняется, то последующие объявления пропускаются, как показано ниже.
@Test public void reportFirstFailedAssertion() { assertTrue(false); // Failed assertion. Report. Stop execution. assertFalse(true); // It's never executed. }
Было бы полезно, если бы мы могли получить список всех неудачных утверждений и исправить их сразу, а не по одному. Вот как Правило ErrorCollector может помочь в этом.
public class ErrorCollectorRuleTests { @Rule public ErrorCollector errorCollector = new ErrorCollector(); @Test public void reportAllFailedAssertions() { errorCollector.checkThat(true, is(false)); // Fail. Continue errorCollector.checkThat(false, is(false)); // Pass. Continue errorCollector.checkThat(2, equalTo("a")); // Fail. Report all } }
Существует также Верификатор Правило, что я не буду вдаваться в подробности, и вы можете прочитать об этом подробнее здесь .
Для получения дополнительной информации о @ClassRule
и разницу между ними, пожалуйста, смотрите в этом Stackoverflow посте.
Апартаменты JUnit
Наборы JUnit можно использовать для группировки тестовых классов и их совместного выполнения. Вот пример
public class TestSuiteA { @Test public void testSuiteA() {} } public class TestSuiteB { @Test public void testSuiteB() {} }
Предполагая, что существует много других тестовых классов, мы могли бы запустить оба или один из них, используя следующие аннотации
@RunWith(Suite.class) @Suite.SuiteClasses({TestSuiteA.class, TestSuiteB.class}) public class TestSuite { // Will run tests from TestSuiteA and TestSuiteB classes }
Вышесказанное приведет к следующему
Категории в JUnit 4
В JUnit 4 мы можем использовать Категории для включения и исключения группы тестов из выполнения. Мы можем создать столько категорий, сколько захотим, используя интерфейс маркера, как показано ниже
Интерфейс без реализации называется интерфейсом маркера.
public interface CategoryA {} public interface CategoryB {}
Теперь, когда у нас есть две категории, мы можем аннотировать каждый тест одним или несколькими типами категорий, как показано ниже
public class CategoriesTests { @Test public void test_categoryNone() { System.out.println("Test without any category"); assert(false); } @Category(CategoryA.class) @Test public void test1() { System.out.println("Runs when category A is selected."); assert(true); } @Category(CategoryB.class) @Test public void test2() { System.out.println("Runs when category B is included."); assert(false); } @Category({CategoryA.class, CategoryB.class}) @Test public void test3() { System.out.println("Runs when either of category is included."); assert(true); } }
Специальный бегун JUnit под названием Categories.class
используется для выполнения этих тестов
@RunWith(Categories.class) @IncludeCategory(CategoryA.class) @ExcludeCategory(CategoryB.class) @SuiteClasses({CategoriesTests.class}) public class CategroyTestSuite {}
Выше будет выполняться только тест тест 1
, однако, если мы удалим следующую запись, то будут выполнены оба тест1
и тест3
.
@ExcludeCategory(CategoryB.class)
Тесты пометки и фильтрации в JUnit 5
В дополнение к категориям в JUnit 4 , JUnit 5 вводит возможность помечать и фильтровать тесты. Давайте предположим, что у нас есть следующее
@Tag("development") public class UnitTests { @Tag("web-layer") public void login_controller_test() {} @Tag("web-layer") public void logout_controller_test() {} @Tag("db-layer") @Tag("dao") public void user_dao_tests() {} }
и
@Tag("qa") public class LoadTests { @Tag("auth") @Test public void login_test() {} @Tag("auth") @Test public void logout_test() {} @Tag("auth") @Test public void forgot_password_test() {} @Tag("report") @Test public void generate_monthly_report() {} }
Как показано выше, теги применяются как ко всему классу, так и к отдельным методам. Давайте выполним все тесты, помеченные как qa
в данном пакете.
@RunWith(JUnitPlatform.class) @SelectPackages("junit.exmaples.v2.tags") @IncludeTags("qa") public class JUnit5TagTests {}
Вышесказанное приведет к следующему результату
Как показано выше, только тестовый класс с кк
тег запущен. Давайте запустим оба qa
и разработка
помеченные тесты, но отфильтруйте дао
и отчет
помеченные тесты.
@RunWith(JUnitPlatform.class) @SelectPackages("junit.exmaples.v2.tags") @IncludeTags({"qa", "development"}) @ExcludeTags({"report", "dao"}) public class JUnit5TagTests {}
Как показано ниже, два теста, помеченные дао
и отчет
исключены.
Параметризация Модульных Тестов
JUnit позволяет параметризовать тест, который будет выполняться с разными аргументами, вместо того, чтобы копировать/вставлять тест несколько раз с разными аргументами или создавать пользовательские служебные методы.
@RunWith(Parameterized.class) public class JUnit4ParametrizedAnnotationTests { @Parameter(value = 0) public int number; @Parameter(value = 1) public boolean expectedResult; // Must be static and return collection. @Parameters(name = "{0} is a Prime? {1}") public static Collection
Для параметризации теста test_isPrime
нам нужно следующее
- Воспользуйтесь специализированным
Parametrized.class
Бегун Джунит - Объявите непубличный статический метод, который возвращает коллекцию с аннотацией
@Параметры
- Объявляйте каждый параметр с
@параметром
и атрибутом значения - Используйте
@Параметр
аннотированные поля в тесте
Вот как выглядит вывод нашего параметризованного test_isPrime
Выше приведено использование @параметра
инъекции, и мы также можем достичь того же результата с помощью конструктора, как показано ниже.
public class JUnit4ConstructorParametrized { private int number; private boolean expectedResult; public JUnit4ConstructorParametrized(int input, boolean result) { this.number = input; this.expectedResult = result; } ... }
В JUnit 5 @parameterizedtest
вводится со следующими источниками
- Источник
@valuesource
- Источник
@enumsource
- Источник
@methodsource
- Источник
@CSVSOURCE
и@CSV-файловый источник
Давайте подробно рассмотрим каждый из них.
Параметризованные тесты с источником значений
Аннотация @Valuesource
допускает следующие объявления
@ValueSource(strings = {"Hi", "How", "Are", "You?"}) @ValueSource(ints = {10, 20, 30}) @ValueSource(longs = {1L, 2L, 3L}) @ValueSource(doubles = {1.1, 1.2, 1.3})
Давайте используем одно из вышеперечисленных в тесте
@ParameterizedTest @ValueSource(strings = {"Hi", "How", "Are", "You?"}) public void testStrings(String arg) { assertTrue(arg.length() <= 4); }
Параметризованные тесты с источником перечисления
Аннотацию @enumsource
можно использовать следующими способами
@EnumSource(Level.class) @EnumSource(value = Level.class, names = { "MEDIUM", "HIGH"}) @EnumSource(value = Level.class, mode = Mode.INCLUDE, names = { "MEDIUM", "HIGH"})
Аналогично Источнику значения
, , мы можем использовать
Источник перечисления
@ParameterizedTest @EnumSource(value = Level.class, mode = Mode.EXCLUDE, names = { "MEDIUM", "HIGH"}) public void testEnums_exclude_Specific(Level level) { assertTrue(EnumSet.of(Level.MEDIUM, Level.HIGH).contains(level)); }
Параметризованные тесты с источником метода
Аннотация @Methodsource
принимает имя метода, предоставляющего входные данные. Метод, предоставляющий входные данные, может возвращать один параметр, или мы могли бы использовать Аргументы
, как показано ниже
public class JUnit5MethodArgumentParametrizedTests { @ParameterizedTest @MethodSource("someIntegers") public void test_MethodSource(Integer s) { assertTrue(s <= 3); } static CollectionsomeIntegers() { return Arrays.asList(1,2,3); } }
Ниже приведен пример Аргументов
и его также можно использовать для возврата POJO
public class JUnit5MethodArgumentParametrizedTests { @ParameterizedTest @MethodSource("argumentsSource") public void test_MethodSource_withMoreArgs(String month, Integer number) { switch(number) { case 1: assertEquals("Jan", month); break; case 2: assertEquals("Feb", month); break; case 3: assertEquals("Mar", month); break; default: assertFalse(true); } } static CollectionargumentsSource() { return Arrays.asList( Arguments.of("Jan", 1), Arguments.of("Feb", 2), Arguments.of("Mar", 3), Arguments.of("Apr", 4)); // Fail. } }
Параметризованные тесты с помощью CSV Источники
Когда дело доходит до выполнения теста с содержимым CSV, Блок 5 предоставляет два разных типа источников для параметризованного тестового случая
- A
CsvSource
– значения, разделенные запятыми - A
CsvFileSource
– ссылка на файл CSV
Вот пример источника Csv
@ParameterizedTest @CsvSource(delimiter=',', value= {"1,'A'","2,'B'"}) public void test_CSVSource_commaDelimited(int i, String s) { assertTrue(i < 3); assertTrue(Arrays.asList("A", "B").contains(s)); }
Предполагая, что у нас есть следующие записи в файле sample.csv
в файле src/test/ресурсы
Name, Age Josh, 22 James, 19 Jonas, 55
Исходный файл Csv
будет выглядеть следующим образом
@ParameterizedTest @CsvFileSource(resources = "/sample.csv", numLinesToSkip = 1, delimiter = ',', encoding = "UTF-8") public void test_CSVFileSource(String name, Integer age) { assertTrue(Arrays.asList("James", "Josh").contains(name)); assertTrue(age < 50); }
В результате 2 успешных тестовых запуска и один сбой из-за последней записи в образец.csv
это не соответствует утверждению.
Вы также можете создать пользовательские конвертеры, которые преобразуют CSV-файл в объект. Смотрите здесь для получения более подробной информации.
Теория в JUnit4
Аннотация @Теория
и Бегун Теории являются экспериментальными особенностями. По сравнению с Параметризованными Тестами, a Теория передает все комбинации данных в тест, как показано ниже.
@RunWith(Theories.class) public class JUnit4TheoriesTests { @DataPoint public static String java = "Java"; @DataPoint public static String node = "node"; @Theory public void test_theory(String a) { System.out.println(a); } @Theory public void test_theory_combos(String a, String b) { System.out.println(a + " - " + b); } }
Когда приведенный выше тестовый класс будет выполнен, тест test_theory
выведет 21 комбинацию
Java node
Однако тест test_theory_combos
выведет все комбинации двух точек данных. Другими словами, 22 комбинации.
Java - Java Java - node node - Java node - node
Если у нас есть следующая точка данных oss
затем test_theory_one
тест сгенерирует 23 комбинации (2 числа аргументов ^ 3 точки данных). Испытание test_theory_two
создаст 33 комбинации.
@DataPoints public static String[] oss = new String[] {"Linux", "macOS", "Windows"}; @Theory public void test_theory_one(String a, String b) { System.out.println(a + " - " + b); } @Theory public void test_theory_two(String a, String b, String c) { System.out.println(a + " <-> " + b + "<->" + c); }
Ниже приведена допустимая точка данных
@DataPoints public static Integer[] numbers() { return new Integer[] {1, 2, 3}; }
Имя отображения теста JUnit 5
В JUnit 5 появилась аннотация @DisplayName
, которая используется для присвоения отображаемого имени отдельному тесту или тестовому классу, как показано ниже.
@Test @DisplayName("Test If Given Number is Prime") public void is_prime_number_test() {}
и это будет отображаться следующим образом в консоли
Повторение теста JUnit
Если возникнет необходимость повторить модульный тест X раз, JUnit 5 предоставляет @repeatedtest
аннотацию.
@RepeatedTest(2) public void test_executed_twice() { System.out.println("RepeatedTest"); // Prints twice }
@Repeatedtest
поставляется вместе с текущим повторением
, общим повторением
переменными, а также TestInfo.java
и RepetitionInfo.java
Объекты.
@RepeatedTest(value = 3, name = "{displayName} executed {currentRepetition} of {totalRepetitions}") @DisplayName("Repeated 3 Times Test") public void repeated_three_times(TestInfo info) { assertTrue("display name matches", info.getDisplayName().contains("Repeated 3 Times Test")); }
Мы также могли бы использовать RepetitionInfo.java
чтобы узнать текущее и общее количество повторений.
Выполнение модульных тестов внутреннего класса с использованием JUnit 5 Вложенный
Я был удивлен, узнав, что JUnit Runner не сканирует внутренние классы на предмет тестов.
public class JUnit5NestedAnnotationTests { @Test public void test_outer_class() { assertTrue(true); } class JUnit5NestedAnnotationTestsNested { @Test public void test_inner_class() { assertFalse(true); // Never executed. } } }
При запуске вышеупомянутого тестового класса он будет выполнять только test_outer_class
и сообщать об успехе , однако при пометке внутреннего класса аннотацией @Nested
выполняются оба теста.
Блок 5 Жизненный цикл тестирования с аннотацией экземпляра теста
Перед вызовом каждого метода @Test
JUnit Runner создает новый экземпляр класса. Это поведение можно изменить с помощью @testInstance
@TestInstance(LifeCycle.PER_CLASS) @TestInstance(LifeCycle.PER_METHOD)
Для получения дополнительной информации о @testInstance (жизненный цикл. ЗА КЛАСС)
пожалуйста, ознакомьтесь с документацией .
Динамические тесты с использованием аннотации фабрики тестов JUnit 5
Тесты JUnit, помеченные @Test
, являются статическими тестами, поскольку они указаны во время компиляции. С другой стороны, Динамические тесты
генерируются во время выполнения. Вот пример Динамических тестов
с использованием класса Простое число Util
.
public class Junit5DynamicTests { @TestFactory StreamdynamicTests() { PrimeNumberUtil util = new PrimeNumberUtil(); return IntStream.of(3, 7 , 11, 13, 15, 17) .mapToObj(num -> DynamicTest.dynamicTest("Is " + num + " Prime?", () -> assertTrue(util.isPrime(number)))); } }
Для получения дополнительной информации о динамических тестах см. Это сообщение в блоге .
Условное выполнение тестов JUnit 5
JUnit 5 ввел следующие примечания, позволяющие условно выполнять тесты.
@EnabledOnOs, @DisabledOnOs, @EnabledOnJre, @DisabledOnJre, @EnabledForJreRange, @DisabledForJreRange, @EnabledIfSystemProperty, @EnabledIfEnvironmentVariable, @DisabledIfEnvironmentVariable, @EnableIf, @DisableIf
Вот пример использования @enabledonos
и @@Отключен
public class JUnit5ConditionalTests { @Test @DisabledOnOs({OS.WINDOWS, OS.OTHER}) public void test_disabled_on_windows() { assertTrue(true); } @Test @EnabledOnOs({OS.MAC, OS.LINUX}) public void test_enabled_on_unix() { assertTrue(true); } @Test @DisabledOnOs(OS.MAC) public void test_disabled_on_mac() { assertFalse(false); } }
Я использую MacBook, и результат выглядит следующим образом
Для получения примеров других аннотаций, пожалуйста, ознакомьтесь с этими тестами .
Спасибо, что читаете вместе с нами. Пожалуйста, поделитесь своими мыслями, предложениями и отзывами в комментариях.
Пожалуйста, не стесняйтесь следовать за мной дальше dev.to для получения дополнительных статей, Twitter и присоединяйтесь к моей профессиональной сети в LinkedIn .
Наконец, я также написал следующие статьи, которые могут вам пригодиться.
Руководство о том, как начать вносить свой вклад в открытый исходный код
Почему вы должны дважды подумать о том, чтобы внести свой вклад в открытый исходный код
Рафиулла Хамеди ・ 22 августа 19 ・ 7 мин читать
Список ключевых привычек, которые, на мой взгляд, помогли бы вам стать лучшим разработчиком
Ключевые привычки и вещи, которые я хотел бы знать раньше как разработчик
Рафиулла Хамеди ・ 6 августа 19 ・ 9 мин читать
Окончательно, краткое изложение лучших практик кодирования для Java
Краткое изложение лучших практик программирования на Java | от Рафиуллы Хамеди | Medium
Рафиулла Хамеди ・ 18 августа 2020 г. ・ 14 мин Среда чтения
Оригинал: “https://dev.to/rhamedy/junit-4-5-annotations-every-developer-should-know-5f6c”