Написание модульных тестов #
Подключение библиотеки Kotest #
Для написания тестов с помощью библиотеки Kotest необходимо добавить зависимости в проект. Внесите следующие доработки в файлы настроек системы сборки проекта.
- Удостовериться, что включена настройка для запуска JUnit-тестов в
build.gradle
:test { useJUnitPlatform() }
- Добавьте версию библиотеки Kotest в
gradle.properties
:kotestVersion=5.7.2
- Добавьте библиотеку для запуска Kotest-тестов в список зависимостей для запуска тестов в
build.gradle
:dependencies { testImplementation "io.kotest:kotest-runner-junit5:${kotestVersion}" }
- Добавьте библиотеку для написания тестовых утверждений в
build.gradle
:dependencies { testImplementation "io.kotest:kotest-assertions-core:${kotestVersion}" }
Написание тестов #
Библиотека Kotest поддерживает множество стилей тестирования. В рамках занятия будем рассматривать исключительно Fun Spec. Для написания тестов можете использовать любой стиль, который понравится.
Перед написанием новых тестов удалите тесты, которые были сгенерированы вместе с шаблоном приложения. Приведение их в рабочий порядок не является целью данного занятия.
В качестве примера опишем тест для класса треугольник, который был реализован на прошлом занятии:
package ru.yarsu.domain
data class Triangle(val sideA: Double, val sideB: Double, val sideC: Double) {
fun area(): Double { ... }
fun perimetr(): Double { ... }
}
В каталоге src/test/kotlin
создадим пакет ru.yarsu.doman
, в котором разместим тест для класса треугольник. Пока что создадим
package ru.yarsu.domain
import io.kotest.core.spec.style.FunSpec
import io.kotest.matchers.shouldBe
class TriangleTest : FunSpec({
test("test the length") {
"test".length.shouldBe(5)
}
})
Обратите внимание на способ написания теста. Класс теста наследуется от класса FunSpec
, которому в качестве аргумента передаётся лямбда-вырежение. Внутри данного выражения доступны методы для описания тестов, их расширенного контекста и так далее.
Запустите тесты с помощью команды test
системы сборки Gradle: ./gradlew test
(или через систему запуска команд IDEA gradle test
).
Удостоверьтесь, что тест не проходит. Исправьте его, чтобы он проходил.
Добавим тесты для проверки работы методов класса треугольник:
test("Should calculate perimeter") {
val triangle = Triangle(5.0, 4.0, 3.0)
triangle.perimeter().shouldBe(12.0.plusOrMinus(0.01))
}
test("Should calculate area") {
val triangle = Triangle(5.0, 4.0, 3.0)
triangle.area().shouldBe(6.0.plusOrMinus(0.01))
}
Изначальный тест, “test the length”, можно удалить.
Описание тестовых утверждений #
Библиотека Kotest предоставляет свой набор утверждений для удобного описания требований к работе тестируемого кода. В рамках конфигурации была добавлена библиотека Core Matchers, которая включает в себя большое количество тестовых утверждений для классов и данных из стандартной библиотеки языка Kotlin.
Особенности запуска тестов #
Библиотека Kotest по умолчанию создаёт класс теста один раз для всех тестов, которые описаны внутри него. В результате надо аккуратно относиться к состоянию, которое доступно всем тестам: запуск отдельных тестов не должен влиять на данное состояние. Если вы хотите получить поведение, аналогичное JUnit, то необходимо настроить режим изоляции.
Задание № 1. Написание тестов для класса Triangles #
Создайте класс для тестирования всех методов класса Triangles, который был разработан для хранения списка треугольников.
Тестирование http4k-приложений #
При разработке веб-приложения в рамках модульных тестов можно тестировать логику следующих компонентов9
- Обработчики HTTP-запросов.
- Фильтры.
- Классы предметной области.
- Отдельные компоненты.
Для обеспечения быстроты написания тестов с помощью Kotest разработчики библиотеки http4k предоставляют свои наборы тестовых утверждений.
- Краткое описание тестовых утверждений доступно на соответствующей странице.
- Список функций в API библиотеки.
Для поддержки данных выражений в шаблоне приложения уже добавлена библиотека
dependencies {
testImplementation "org.http4k:http4k-testing-kotest:${http4kVersion}"
}
Написание тестов для HTTP-обработчиков #
Разработаем тест для HTTP-обработчика корневого маршрута приложения-треугольника. Данный HTTP-обработчик должен перенаправлять на маршрут документа со списком треугольников.
Создадим соответствующий класс-тест:
package ru.yarsu.handlers
import io.kotest.core.spec.style.FunSpec
import io.kotest.matchers.and
import io.kotest.matchers.should
import org.http4k.core.Method
import org.http4k.core.Request
import org.http4k.core.Status
import org.http4k.kotest.haveHeader
import org.http4k.kotest.haveStatus
class RedirectToTrianglesHandlerTest : FunSpec({
})
Сформируем тест на проверку ключевой функциональности данного обработчика:
- Создадим обработчик HTTP-запроса.
- Создадим объект запроса.
- Вызовем созданный обработчик.
- Проверим свойства возвращённого объекта.
test("Should redirect to the /triangles route") {
val handler = redirectToTrianglesHandler()
val request = Request(Method.GET, "/")
val result = handler(request)
result.should(
haveStatus(Status.FOUND)
.and(haveHeader("Location", "/triangles"))
)
}
Задача № 2. Написание тестов HTTP-обработчиков #
Разработайте модульные тесты для следующих HTTP-обработчиков:
- Обработчик для отображения информации об одном треугольнике.
- Обработчик для отображения информации о списке треугольников.
Упрощение тестирования HTTP-обработчиков #
Обработчики HTTP-запросов, разработанные на прошлом занятии, в качестве зависимости имеют объект класса Triangles
. Для написания тестов необходимо правильным образом инициализировать данный класс, наполнить его данными. В текущей ситуации класс достаточно простой, но при усложнении приложения инициализация зависимостей будет усложнять логику тестов.
В качестве решения данной проблемы можно воспользоваться привязанными функции и свойствами. Рассмотрим следующий пример:
class Triangle(val sideA: Double, val sideB: Double, val sideC: Double) {
fun perimeter() = sideA + sideB + sideC
fun enlarge(coefficient: Double) = Triangle(
coefficient * sideA,
coefficient * sideB,
coefficient * sideC,
)
}
Проверим работу привязанной функции.
test("Should create bound function") {
val triangle = Triangle(3.0, 4.0, 5.0)
val enlargeFunction: (Double) -> Triangle = triangle::enlarge
val newTriangle = enlargeFunction(2.0)
newTriangle.perimeter().shouldBe(24.0.plusOrMinus(0.01))
}
Для функции enlarge
создадим ссылку, которая будет являться функциональным типом, сигнатура которого совпадает с сигнатурой оригинального метода.
Задача № 3. Упрощение тестирования HTTP-обработчиков #
Замените зависимость от объекта класса Triangles
в обработчиках на зависимость от следующих функциональных типов:
- Для HTTP-обработчика страницы информации об одном треугольнике:
(Int) -> Triangle
. - Для HTTP-обработчика страницы со списком треугольников:
() -> List<Triangle>
.
Обновите тесты для данных обработчиков. Вместо создания класса Triangles
предоставляйте лямбда-выражения, которые будут предоставлять HTTP-обработчикам необходимые данные.