Рубрики
Без рубрики

Аннотации JUnit 4 и 5 Каждый Разработчик Должен Знать

Краткое изложение аннотаций JUnit 4 и 5 с примерами Перед написанием этой статьи, только я… С тегами java, тестирование, качество кода, веб-разработчик.

До написания этой статьи я знал только несколько часто используемых аннотаций 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 testData() {
    return Arrays.asList(new Object[][] {
      {1, false}, {2, true}, {7, true}, {12, false}
    });
  }
  @Test
  public void test_isPrime() {
    PrimeNumberUtil util = new PrimeNumberUtil();
    assertSame(util.isPrime(number), expectedResult);
  }
}

Для параметризации теста 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 Collection someIntegers() {
    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 Collection argumentsSource() {
    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
  Stream dynamicTests() {
    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”