Kotlin assert that class

Class Assertions

Unless otherwise noted, a failed assertion will throw an AssertionFailedError or a subclass thereof.

Object Equality

Assertion methods comparing two objects for equality, such as the assertEquals(expected, actual) and assertNotEquals(unexpected, actual) variants, are only intended to test equality for an (un-)expected value and an actual value. They are not designed for testing whether a class correctly implements Object.equals(Object) . For example, assertEquals() might immediately return true when provided the same object for the expected and actual values, without calling equals(Object) at all. Tests that aim to verify the equals(Object) implementation should instead be written to explicitly verify the Object.equals(Object) contract by using assertTrue() or assertFalse() — for example, assertTrue(expected.equals(actual)) , assertTrue(actual.equals(expected)) , assertFalse(expected.equals(null)) , etc.

Kotlin Support

Additional Kotlin assertions can be found as top-level functions in the org.junit.jupiter.api package.

Preemptive Timeouts

The various assertTimeoutPreemptively() methods in this class execute the provided executable or supplier in a different thread than that of the calling code. This behavior can lead to undesirable side effects if the code that is executed within the executable or supplier relies on ThreadLocal storage.

One common example of this is the transactional testing support in the Spring Framework. Specifically, Spring’s testing support binds transaction state to the current thread (via a ThreadLocal ) before a test method is invoked. Consequently, if an executable or supplier provided to assertTimeoutPreemptively() invokes Spring-managed components that participate in transactions, any actions taken by those components will not be rolled back with the test-managed transaction. On the contrary, such actions will be committed to the persistent store (e.g., relational database) even though the test-managed transaction is rolled back.

Читайте также:  Php проверить вхождение подстроки строку

Similar side effects may be encountered with other frameworks that rely on ThreadLocal storage.

Extensibility

Although it is technically possible to extend this class, extension is strongly discouraged. The JUnit Team highly recommends that the methods defined in this class be used via static imports.

Источник

Тестирование с помощью JUnit 5 на Kotlin

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

Официальный user guide
Исходный код тестов из этой статьи: GitHub

Перед созданием первого теста укажем в pom.xml зависимость:

 org.junit.jupiter junit-jupiter-api 5.0.2 test 
import org.junit.jupiter.api.Test class HelloJunit5Test < @Test fun `First test`() < print("Hello, JUnit5!") >>

image

Перейдём к обзору основных фич JUnit 5 и различных технических нюансов.

Отображаемое название теста

В значении аннотации @DisplayName , как и в названии функции Kotlin, помимо удобочитаемого отображаемого названия теста можно указать спецсимволы и emoji:

import org.junit.jupiter.api.DisplayName import org.junit.jupiter.api.Test class HelloJunit5Test < @DisplayName("\uD83D\uDC4D") @Test fun `First test ╯°□°)╯`() < print("Hello, JUnit5!") >>

Как видно, значение аннотации имеет приоритет перед названием функции:

image

Аннотация применима и к классу:

@DisplayName("Override class name") class HelloJunit5Test 

image

Assertions

Assertion'ы находятся в классе org.junit.jupiter.Assertions и являются статическими методами.

Базовые assertion'ы

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

import org.junit.jupiter.api.Assertions.assertEquals import org.junit.jupiter.api.Test class HelloJunit5Test < @Test fun `Base assertions`() < assertEquals("a", "a") assertEquals(2, 1 + 1, "Optional message") assertEquals(2, 1 + 1, < "Assertion message " + "can be lazily evaluated" >) > >

Групповые assertion'ы

Для тестирования групповых assertion'ов предварительно создадим класс Person с двумя свойствами:

class Person(val firstName: String, val lastName: String)

Будут выполнены оба assertion'а:

import org.junit.jupiter.api.Assertions.assertAll import org.junit.jupiter.api.Assertions.assertEquals import org.junit.jupiter.api.Test import org.junit.jupiter.api.function.Executable class HelloJunit5Test < @Test fun `Grouped assertions`() < val person = Person("John", "Doe") assertAll("person", Executable < assertEquals("John", person.firstName) >, Executable < assertEquals("Doe", person.lastName) >) > >

Передача лямбд и ссылок на методы в проверках на true/false

 @Test fun `Test assertTrue with reference and lambda`() < val list = listOf("") assertTrue(list::isNotEmpty) assertTrue < !list.contains("a") >> 

Exceptions

Более прозрачная по сравнению с JUnit 4 работа с исключениями:

 @Test fun `Test exception`() < val exception: Exception = assertThrows(IllegalArgumentException::class.java, < throw IllegalArgumentException("exception message") >) assertEquals("exception message", exception.message) >

Проверка времени выполнения тестов

Как и в остальных примерах, всё делается просто:

 @Test fun `Timeout not exceeded`() < // Тест упадёт после выполнения лямбда-выражения, если оно превысит 1000 мс assertTimeout(ofMillis(1000)) < print("Выполняется операция, которая займёт не больше 1 секунды") Thread.sleep(3) >>

При этом лямбда-выражение выполняется полностью, даже когда время выполнения уже превысило допустимое. Для того, чтобы тест падал сразу после истечения отведённого времени, нужно использовать метод assertTimeoutPreemptively :

 @Test fun `Timeout not exceeded with preemptively exit`() < // Тест упадёт, как только время выполнения превысит 1000 мс assertTimeoutPreemptively(ofMillis(1000)) < print("Выполняется операция, которая займёт не больше 1 секунды") Thread.sleep(3) >>

Внешние assertion-библиотеки

Некоторые библиотеки предоставляют более мощные и выразительные средства использования assertion'ов, чем JUnit. В частности, Hamcrest, помимо прочих, предоставляет множество возможностей для проверки массивов и коллекций. Несколько примеров:

import org.hamcrest.MatcherAssert.assertThat import org.hamcrest.Matchers.containsInAnyOrder import org.hamcrest.Matchers.greaterThanOrEqualTo import org.hamcrest.Matchers.hasItem import org.hamcrest.Matchers.notNullValue import org.junit.jupiter.api.Test class HamcrestExample < @Test fun `Some examples`() < val list = listOf("s1", "s2", "s3") assertThat(list, containsInAnyOrder("s3", "s1", "s2")) assertThat(list, hasItem("s1")) assertThat(list.size, greaterThanOrEqualTo(3)) assertThat(list[0], notNullValue()) >>

Assumptions

Assumption'ы предоставляют возможность выполнения тестов только в случае выполнения определённых условий:

import org.junit.jupiter.api.Assumptions.assumeTrue import org.junit.jupiter.api.Test class AssumptionTest < @Test fun `Test Java 8 installed`() < assumeTrue(System.getProperty("java.version").startsWith("1.8")) print("Not too old version") >@Test fun `Test Java 7 installed`() < assumeTrue(System.getProperty("java.version").startsWith("1.7")) < "Assumption doesn't hold" >print("Need to update") > >

При этом тест с невыполнившимся assumption'ом не падает, а прерывается:

image

Data driven тестирование

Одной из главных фич JUnit 5 является поддержка data driven тестирования.

Test factory

Перед генерацией тестов для большей наглядности сделаем класс Person data-классом, что, помимо прочего, переопределит метод toString() , и добавим свойства birthDate и age :

import java.time.LocalDate import java.time.Period data class Person(val firstName: String, val lastName: String, val birthDate: LocalDate?)

Следующий пример сгенерирует пачку тестов для проверки того, что возраст каждого человека не меньше заданного:

import org.junit.jupiter.api.Assertions.assertTrue import org.junit.jupiter.api.DynamicTest import org.junit.jupiter.api.DynamicTest.dynamicTest import org.junit.jupiter.api.TestFactory import java.time.LocalDate class TestFactoryExample < @TestFactory fun `Run multiple tests`(): Collection< val persons = listOf( Person("John", "Doe", LocalDate.of(1969, 5, 20)), Person("Jane", "Smith", LocalDate.of(1997, 11, 21)), Person("Ivan", "Ivanov", LocalDate.of(1994, 2, 12)) ) val minAgeFilter = 18 return persons.map < dynamicTest("Check person $it on age greater or equals $minAgeFilter") < assertTrue(it.age >= minAgeFilter) > >.toList() > >

image

Помимо коллекций DynamicTest , в методе, аннотированном @TestFactory , можно возвращать Stream , Iterable , Iterator .

Жизненный цикл выполнения динамических тестов отличается от @Test методов тем, что метод, аннотированный @BeforeEach выполнится только для @TestFactory метода, а не для каждого динамического теста. Например, при выполнении следующего кода функция Reset some var будет вызвана только один раз, в чём можно убедиться, используя переменную someVar :

 private var someVar: Int? = null @BeforeEach fun `Reset some var`() < someVar = 0 >@TestFactory fun `Test factory`(): Collection  < val ints = 0..5 return ints.map < dynamicTest("Test №$it incrementing some var") < someVar = someVar?.inc() print(someVar) >>.toList() >

image

Параметризованные тесты

Параметризованные тесты, как и динамические, позволяют создавать набор тестов на основе одного метода, но делают это отличным от @TestFactory образом. Для иллюстрации работы этого способа предварительно добавим в pom.xml зависимость:

  org.junit.jupiter junit-jupiter-params 5.0.2 test  

Код теста, проверяющего, что поступающие на вход даты уже в прошлом:

class ParameterizedTestExample < @ParameterizedTest @ValueSource(strings = ["2002-01-23", "1956-03-14", "1503-07-19"]) fun `Check date in past`(date: LocalDate) < assertTrue(date.isBefore(LocalDate.now())) >>

Значениями аннотации @ValueSource могут быть массивы int , long , double и String . В случае массива строк, как видно из примера выше, будет использовано неявное преобразование к типу входного параметра, если оно возможно. @ValueSource позволяет передавать только один входной параметр для каждого вызова теста.

@EnumSource позволяет тестовому методу принимать константы перечислений:

 @ParameterizedTest @EnumSource(TimeUnit::class) fun `Test enum`(timeUnit: TimeUnit)

Можно оставить или исключить определённые константы:

 @ParameterizedTest @EnumSource(TimeUnit::class, mode = EnumSource.Mode.EXCLUDE, names = ["SECONDS", "MINUTES"]) fun `Test enum without days and milliseconds`(timeUnit: TimeUnit)

image

Есть возможность указать метод, который будет использован как источник данных:

 @ParameterizedTest @MethodSource("intProvider") fun `Test with custom arguments provider`(argument: Int) < assertNotNull(argument) >companion object < @JvmStatic fun intProvider(): Stream= Stream.of(0, 42, 9000) >

В java-коде этот метод должен быть статическим, в Kotlin это достигается его объявлянием в объекте-компаньоне и аннотированием @JvmStatic . Чтобы использовать не статический метод, нужно изменить жизненный цикл экземпляра теста, точнее, создавать один инстанс теста на класс, вместо одного инстанса на метод, как делается по умолчанию:

@TestInstance(TestInstance.Lifecycle.PER_CLASS) class ParameterizedTestExample < @ParameterizedTest @MethodSource("intProvider") fun `Test with custom arguments provider`(argument: Int) < assertNotNull(argument) >fun intProvider(): Stream = Stream.of(0, 42, 9000) >

Повторяемые тесты

Число повторений теста указывается следующим образом:

 @RepeatedTest(10) fun `Повторяемый тест`()

image

Есть возможность настроить выводимое название теста:

 @RepeatedTest(10, name = "  из ") fun `Повторяемый тест`()

image

Доступ к информации о текущем тесте и о группе повторяемых тестов можно получить через соответствующие объекты:

 @RepeatedTest(5) fun `Repeated test with repetition info and test info`(repetitionInfo: RepetitionInfo, testInfo: TestInfo)

Вложенные тесты

JUnit 5 позволяет писать вложенные тесты для большей наглядности и выделения взаимосвязей между ними. Создадим пример, используя класс Person и собственный провайдер аргументов для тестов, возвращающий стрим объектов Person :

class NestedTestExample < @Nested inner class `Check age of person` < @ParameterizedTest @ArgumentsSource(PersonProvider::class) fun `Check age greater or equals 18`(person: Person) < assertTrue(person.age >= 18) > @ParameterizedTest @ArgumentsSource(PersonProvider::class) fun `Check birth date is after 1950`(person: Person) < assertTrue(LocalDate.of(1950, 12, 31).isBefore(person.birthDate)) >> @Nested inner class `Check name of person` < @ParameterizedTest @ArgumentsSource(PersonProvider::class) fun `Check first name length is 4`(person: Person) < assertEquals(4, person.firstName.length) >> internal class PersonProvider : ArgumentsProvider < override fun provideArguments(context: ExtensionContext): Stream= Stream.of( Person("John", "Doe", LocalDate.of(1969, 5, 20)), Person("Jane", "Smith", LocalDate.of(1997, 11, 21)), Person("Ivan", "Ivanov", LocalDate.of(1994, 2, 12)) ).map < Arguments.of(it) >> >

Результат будет довольно наглядным:

image

Заключение

JUnit 5 довольно прост в использовании и предоставляет множество удобных возможностей для написания тестов. Data driven тестирование с использованием Kotlin обеспечивает удобство в разработке и лаконичность кода.

Источник

Оцените статью