Хочу поделиться опытом создания системы автоматизации функционального тестирования на языке на Kotlin.
Основой для создания / конфигурирования / запуска / контроля выполнения тестов — будет набирающий популярность молодой фреймворк Kotest (в прошлом Kotlin Test).
Проанализировав все популярные варианты для Kotlin — выяснилось, что есть всего два "нативных": Либо бесконечное кол-во из Java мира: Junit4/5, TestNG, Cucumber JVM или другие BDD фреймворки.
Выбора пал на Kotest с бОльшим кол-вом "лайков" на GitHub, чем у Spek.  Руководств по автоматизации тестирования на Kotlin, особенно в сочетании с Kotest — немного.
Думаю, что написать цикл статей про Kotest, а также про организацию проекта авто-тестов, сборку, запуск и сопутствующие технологии — хорошая идея. Все части руководства: Я являюсь QA Лидом на одном из проектов Мир Plat.Form (НСПК). Проект зародился около года назад и уже вырос до четырех команд, где трудится в общей сложности около 10 разработчиков в тестировании (SDET), без учета остальных участников в лице аналитиков, разработчиков и технологов.
Наша задача — автоматизировать функциональные тесты на уровне отдельных сервисов, интеграций между ними и E2E до попадания функционала в master — всего порядка 30 микро-сервисов. Взаимодействие между сервисами — Kafka, внешний API — REST, а также 2 фронтовых Web приложения.
Разработка самой системы и тестов ведется на языке Kotlin, а движок для тестов был выбран Kotest. В данной статье и в остальных публикациях серии я максимально подробно рассказываю о тестовом Движке и вспомогательных технологиях в формате Руководства/Tutorial. Сходу определимся по глобальным минусам и учтем, что проект быстро развивается. Те проблемы, что есть в актуальной на время написания статьи версии 4.2.5, уже могут быть исправлены в свежих релизах. Первым, очевидным, минусом является очень быстрое изменение минорных и мажорных версий.
Еще в марте 2020 фреймворк назывался KotlinTest, с версии 4.0.0 поменял название всех пакетов, потерял совместимость с плагином Idea и был переименован в Kotest, а также стал мульти-платформенным.
После обновления с версии 4.0.7 до 4.1 перестали работать расширения, написанные ранее, также были изменены названия базовых модулей и много чего еще, то есть принцип семантического версионирования нарушился между минорными версиями 4.0 и 4.1.
Это немыслимо для Java мира — это что-то из JS.
У инструмента пока небольшое комьюнити.
В пользу широкого функционала иногда приносится в жертву продуманный дизайн.
Не все предоставляемые стили написания тестов адекватно транслируются в отчет. На текущий момент не корректно отображаются репорты для data-driven и property-based тестов.
Шаблоны и фабрики тестов работают не как ожидается.
Встроенное расширение allure совсем никуда не годится (к примеру, оно пытается обрабатывать аннотации, которыми в принципе невозможно аннотировать тестовые DSL методы). Однако ни одного критического или блокирующего дефекта я не встретил. Многие разработчики на Kotlin не уделяют много внимания на выбор тестового фреймворка и продолжают использовать Junit4 или Junit5.
Для них тесты — это, как правило, класс, помеченный аннотацией @SpringBootTest, набор методов с аннотацией @Test, возможно методы before и beforeClass с соответствующими аннотациями. Для полноценных функциональных e2e тестов этого недостаточно.
Нужен инструмент предоставляющий возможность создавать понятные тесты на основе требований, удобную организацию проверок, тестовых данных и отчетности. Так вот Kotest позволяет: Kotest дает возможность выбора между несколькими вариантами DSL для формирования структуры тестов. Самый базовый и простой String Spec — идеально подойдет для написания unit-тестов с одним уровнем вложенности.
Для полноценных функциональных авто-тестов нужно что-то посложнее: по структуре схожее с Gherkin, но менее формализованное, особенно по ключевым словам.  После долгих экспериментов я остановился на стиле FreeSpec.
Используя Kotest я рекомендую продолжать писать тесты в BDD стиле, как в языке Gherkin (Cucumber).
FreeStyle не накладывает ограничений на именование тестов, ключевые слова и вложенность, поэтому эти вещи нужно контролировать на уровне code-style, best practice, обучения и Merge-Request`ов. В нашем подходе будет 5 базовых тестовых сущностей (или уровней) в рамках Kotest.
Важно определить это сейчас, потому что в дальнейшем оперировать я буду этими понятиями: Тестовый прогон — Execution (или Project)
Запуск определенного набора тестов Спецификация — Spec
Тестовый класс. В cucumber — это Feature Контейнер теста — Top Level Test
Сценарий верхнего уровня в Спецификации. В cucumber — это Scenario Шаг теста — Nested Test
Шаг в сценарии, который начинается с ключевого слова.
Ключевое слово обозначает этап: подготовка (Дано), воздействие (Когда), проверка ожидаемой реакции (Тогда).
В cucumber — это Step Вложенные Шаги — Nested Step
Это любая дополнительная информация о произведенных действиях, например аннотация @Step в Allure.
В рамках описания сценария эти шаги не несут нагрузки — они нужны для отчета, для отладки, для выяснения причин ошибки.
Kotest позволяет создавать любую вложенность, но в данном подходе ограничиваемся 4 - Шаг теста - Nested Test — дальнейшая вложенность воспринимается как шаги для отчета.  С точки зрения Форматирования теста и Review интерес представляют уровни 1 — 4. В Gherkin есть сущность Структура Сценария (Scenario Template) — это реализация Data Driven.
В Kotest уровень 3. Контейнер теста - Top Level Test, также может являться Структурой Сценария — то есть помножиться на наборы тестовых данных.  Допустим мы тестируем REST API сервиса и имеются требования.
Не известно, как будем отправлять запросы, как получать, десериализовать и проверять, но сейчас это не нужно.  Пишем скелет сценария: Очень похоже на Сценарии на Gherkin Во-первых, стоит обратить внимание, что здесь нет понятия тестовый класс, а есть спецификация (FreeSpec). И это не спроста.
Вспоминаем, что Kotlin DSL — это type-safe builder, а значит при запуске тесты сначала формируют дерево тестов / тестовых контейнеров / pre и after функций / вложенных шагов умноженных на наборы тестовых данных. Отмечу использование интерполяции строк в имени шага "Then response received and has $expectedCode code" Тест наследуется от класса FreeSpec, в свою очередь он реализует FreeSpecRootScope:  В FreeSpecRootScope для класса String переопределяется оператор -: Соответсвенно запись "Scenario. Single case" - { } вызывает функцию расширения String.minus, передает внутрь функциональный тип с контекстом FreeScope и добавляет в дерево тестов контейнер.
Напомню, что в Kotlin, если лямбда-аргумент является последним в функции, то для него круглые скобки можно опускать. В том же интерфейсе FreeSpecRootScope для класса String переопределяется оператор вызова invoke Запись "string" { } является вызовом функции расширения с аргументом функционального типа с контекстом TestContext.  Вот как будет выглядеть тест с реализацией: Поясню некоторые момент и мотивацию В Kotest уже есть довольно обширная библиотека Assertions and Matchers. Зависимость testImplementation "io.kotest:kotest-assertions-core:$kotestVersion" предоставляет набор Matcher-ов, SoftAssertion и Assertion для проверки Исключений.
Есть возможность расширять ее и добавлять свои комплексные Matcher-ы, а также использовать уже готовые расширения.  Немного расширим последний шаг и добавим в него побольше проверок: Существует большое количество вариантов обратных вызовов на события тестов.
На текущий момент (4.3.5) все встроенные события представлены в файле io.kotest.core.spec.CallbackAliasesKt в артефакте kotest-framework-api-jvm в виде typealias:  Существует 2 вида интерфейсов, которые реализуют обратные вызовы: Первый позволяет создать обратные вызов на событие теста и предоставляет immutable описание теста или результат (для after).
Второй позволяет вмешиваться в выполнение теста, что-то изменять, получать информацию о внутреннем состоянии движка, то есть небезопасен и подходит для разработки расширений, влияющих на функционал Фреймворка. Ограничимся событиями Listener — поверьте, этого более чем достаточно.
Этот интерфейс, в свою очередь, делится еще на 2 основных: Добавить свой callback можно множеством способов: Самый простой способ добавить callback для одного из типов доступных событий — это вызвать одноименный метод из FreeSpec, каждый из которых принимает функциональный тип соответствующего события: В коде выше все обратные вызовы определены в порядке выполнения до и после теста. Выполняется сразу после окончания работы всех тестов в Спецификации.
Имеет аргумент пару — класс спецификации KClass<out Spec> + отображение всех тестов и результатов Map<TestCase, TestResult> Kotest предоставляет возможность определить callback на события всего запуска тестов.
Их два: Определить можно с помощью реализации ProjectListener и добавления в список слушателей проекта в конфигурации проекта, в AbstractProjectConfig либо одиночке Project.
Также можно переопределить одноименные методы в AbstractProjectConfig — в большинстве случаев предпочтительный способ: В пакете io.kotest.data предоставлен набор классов и функций для организации Data Driven Testing Создадим простейший тест c Data Provider-ом: Выглядит довольно просто, а главное понятно (наверное) 2 итерации со своим набором тестовых данных.
В каждом наборе 2 значения. Функциональный блок, который выполнится 2 раза. Существует важное ограничение на имена в рамках контейнера — все имена шагов должны быть уникальными.
Поэтому в шагах добавлены уникальные индексы .
Можно обойтись без индекса и печатать uuid в каждом шаге — индекс используется только для упорядоченности в отчете. Во-первых, привожу ссылку на все примеры qa-kotest-articles/kotest-first. По итогу имеем полноценный фреймворк для запуска тестов. Широкий контроль жизненного цикла всего запуска, спецификации, каждого сценария и его шагов. Результаты запуска трансформируются в стандартные junit отчеты, все события публикуются в слушатели junit для корректного отображения в консоли Idea. Также имеется Idea плагин. Data Driven без всяких аннотаций или дополнительных классов. Все это дело похоже на Groovy Spoke, но только для Kotlin. Kotlin. Автоматизация тестирования (Часть 2). Kotest. Deep Diving Примеры кода Официальная документация Kotest Kotest GitHub Kotlin Lang Coroutines Tutorial Gradle Testing В планах написание следующих частей, которые покроют тему 'Kotlin. Автоматизация тестирования': Automation QA