интеграционные тесты java что это

Интеграционные тесты для Java с помощью TestContainers. Меньше безумия, больше порядка, и всё это автоматически

На Хабре совсем нет информации про TestContainers. На момент написания этой статьи, в поисковой выдаче есть анонсы наших же конференций, и всё. Между тем, в проекте на GitHub у них уже более 700 коммитов, 54 контрибьютора и 5 лет истории. Похоже, все эти пять лет проект тщательно скрывался спецслужбами и НЛО. Настало время выйти из тени на свет.

интеграционные тесты java что это. интеграционные тесты java что это фото. картинка интеграционные тесты java что это. смотреть фото интеграционные тесты java что это. смотреть картинку интеграционные тесты java что это.

Чукча — читатель, а не писатель. Поэтому, вместо написания своего текста, я попросил разрешения на перевод соответствующей статьи из блога RebelLabs.

Итак, здесь мы поделимся парой слов о наимоднейшей Java-библиотеке для интеграционного тестирования — TestContainers. Кроме этого, будет немного о том, почему интеграционное тестирование настолько важно для ZeroTurnaround и их требования к интеграционным тестам. И конечно, будет полнофункциональный пример интеграционного теста для Java-агента. Если кто-то никогда в глаза не видел код Java-агента, то сейчас самое время. Добро пожаловать под кат!

Интеграционное тестирование в компании ZeroTurnaround

Продукты компании ZeroTurnaround интегрируются с большой частью экосистемы Java. В том числе, JRebel и XRebel основаны на технологии Java-агентов и интегрируются с Java-приложениями, фреймворками, серверами приложений и так далее.

С помощью Java-агента можно инструментировать Java-код так, чтобы добавить нужную тебе дополнительную функциональность. Чтобы протестировать, как приложение ведет себя после применения патча, необходимо запустить его через преднастроенный Java-агент. Как только приложение запустилось и заработало, для воспроизведения желаемого поведения можно послать ему HTTP-запрос.

Чтобы запускать такие тесты на больших масштабах, требуется иметь автоматизированную систему, которая сможет запускать и останавливать среду исполнения, включая сервер приложений или любые другие внешние зависимости, от которых зависит приложение. Должна иметься возможность запускать примерно одни и те же вещи и в среде непрерывной интеграции, и на компьютере разработчика.

В результате получается куча тестов, они работают совсем не быстро, и поэтому мы точно захотим запускать их параллельно. Это автоматом означает, что тесты должны быть изолированы, чтобы не случилось конфликтов по ресурсам. Например, если мы запускаем на одном хосте несколько экземпляров Tomcat, хотелось бы избежать конфликтов использования портов.

В таком интеграционном тестировании нам помогает небольшая красивая библиотека TestContainers. Она не просто подошла по озвученным выше требованиям — после её внедрения мы получили внушительный рост производительности.

TestContainers

Официальная документация TestContainers говорит следующее:

«TestContainers — это Java-библиотека, которая поддерживает тесты JUnit и предоставляет легкие, временные экземпляры основных баз данных, веб-браузеров для Selenium или чего угодно еще, что можно запускать в Docker-контейнере».

TestContainers предоставляет API для автоматизации настройки окружения. Оно запускает нужные Docker-контейнеры ровно на время работы наших тестов и гасит их сразу же, как тесты завершатся. Дальше мы посмотрим на несколько демок, основанных на официальных примерах, лежащих в их репозитории на GitHub.

GenericContainer

При использовании TestContainers, очень часто используется класс GenericContainer :

Его конструктор принимает в качестве параметра строку, в которой указывается Docker-образ, который мы в дальнейшем будем использовать. В ходе запуска, TestContainers автоматически загружает соответствующий образ (если он не был загружен ранее).

Контейнеры под задачу

Наследники GenericContainer — это специализированные под задачу контейнеры. Для тестирования уровня доступа к данным, из коробки имеются контейнеризованные образы MySQL, PostgreSQL и Oracle.

Всего лишь этой одной строчкой можно получить экземпляр контейнера, который останется с нами на протяжении теста. На машину, где будут запускаться тесты, не нужно вручную устанавливать базу данных. Это дает особенно большой выигрыш, если хочется провести тест на нескольких версиях одной и той же базы данных.

Свои собственные контейнеры

Дальше мы увидим, что вручную определенные контейнеры помогают в структуризации окружения для тестирования Java-агентов.

Тестирование Java-агента с помощью TestContainers

Для демонстрации идеи воспользуемся примером, любезно предоставленным Сергеем @bsideup Егоровым, сомантейнером проекта TestContainers.

Демонстрационное приложение

Давайте начнем с тестового приложения. Нам понадобится веб-приложение, отвечающее на HTTP GET-запросы. Жирных фреймворков не требуется — поэтому почему бы не взять SparkJava? Чтобы добавить веселья, сразу начнем кодить на Groovy! Вот это приложение мы будем тестировать:

Это простой скрипт на Groovy, использующий Grape для загрузки зависимости на SparkJava, и определяющий один HTTP-эндпоинт, отвечающий сообщением “Hello!”.

Java-агент

Агент, который мы собрались проверять, патчит сервер Jetty и добавляет ему дополнительный заголовок в HTTP-ответ.

Собственно, тест!

Тест будет довольно простым: нужно сделать запрос к нашему приложению и проверить ответ на наличие особого заголовка, который Java-агент, теоретически, должен бы туда добавить. Если заголовок найден и значение заголовка совпадает с ожидаемым значением — тест успешно пройден. Взглянем на код:

Можно запустить его прямо из IDE, или из командной строки, или даже в среде непрерывной интеграции. TestContainers помогают нам запустить приложение так, что агент оказывается в изолированном окружении, в Docker-контейнере.

Чтобы запустить приложение, нужен Docker-образ с поддержкой Groovy. Чтобы сделать себе удобно, мы завели Docker-образ zeroturnaround/groovy, он лежит на Docker Hub. Вот как его можно использовать, наследуясь от GenericContainer :

Посмотрите, как API предоставляет нам методы для получения IP-адреса контейнера, а также связанного порта (который в реальности рандомизован). В смысле, порт будет разный каждый раз, когда запускается тест. Поэтому, если запустить все тесты одновременно, не будет конфликтов между портами, и тесты не посыпятся.

Теперь у нас имеется специальный класс GroovyTestApp для простого запуска скриптов на Groovy, в нашем случае — для тестирования демонстрационного приложения:

Запускаем тесты, смотрим на выхлоп:

Тест этот не очень быстр. Какое-то время уходит на скачивание Grapes — но только самый первый раз. Тем не менее, это полноценный интеграционный тест, который запускает Docker-контейнер, приложение с использованием HTTP-стека, и делает HTTP-запросы. Кроме этого, приложение запускается в изоляции, и сделать это действительно просто. И всё это — благодаря TestContainers!

Заключение

«Работает на моем компьютере» — популярное оправдание, но оно больше не должно быть оправданием вообще. По мере того, как технология контейнеризации становится доступна всё большему количеству разработчиков, появляется возможность делать всё более детерминированные тесты.

TestContainers уменьшают количество безумия в интеграционных тестах приложений на Java. Эту библиотеку очень просто интегрировать в существующие тесты. Больше не нужно вручную управлять внешними зависимостями, и это — огромная победа, особенно в среде непрерывной интеграции.

Если вам понравилось то, что вы сейчас прочитали, очень советуем посмотреть на запись с конференции GeekOut Java, где Richard North, изначальный автор проекта, дает вводную информацию о TestContainers, включая планы по развитию. Или хотя бы посмотреть на слайды этой презентации.

Пара слов от переводчика.

Во-первых, если вы нашли какие-то неточности, ошибки и опечатки — нужно пройти в личку к olegchir и описать всё как есть. Я действительно читаю сообщения и исправляю баги.

Если вы интересуетесь Java, новыми технологиями и библиотеками, то вам стоит посетить наши Java-конференции. Ближайшие — JPoint и JBreak. Кстати, сотрудники ZeroTurnaround часто выступают на наших конференциях как спикеры и работают как члены Программного коммитета.

Если же вам интереснее тестирование, то мы проводим конференцию Heisenbug 2017 Moscow, которая состоится буквально через полторы недели. Тема тестирования с использованием Docker там так или иначе присутствует во многих докладах.

Будете ли вы пользоваться TestContainers? Понравилась идея, есть сомнения? Пишите в комментариях!

Источник

12 инструментов для интеграционных и unit-тестов в Java

Авторизуйтесь

12 инструментов для интеграционных и unit-тестов в Java

интеграционные тесты java что это. интеграционные тесты java что это фото. картинка интеграционные тесты java что это. смотреть фото интеграционные тесты java что это. смотреть картинку интеграционные тесты java что это.

Я считаю, что разработка ПО — нечто большее, чем работа. Я вижу себя ремесленником, который каждый день пытается стать лучше. Самый «простой» путь для этого — найти несколько хороших инструментов и ответить на следующие вопросы:

Автоматизированное тестирование — очень важная часть разработки ПО, но в блогах программистов немного постов про используемые инструменты. Эта же статья позволит вам заглянуть в мой «ящик с инструментами». Я расскажу о 12-ти библиотеках и фреймворках, которые я использую для написания unit- и интеграционных тестов, а также предоставлю ссылки на страницы, которые помогут вам понять, как их использовать.

Заглянем в мой ящик с инструментами

Перед тем, как вы сможете использовать описанные далее инструменты, вы должны настроить сборку, которая автоматически запускает интеграционные и unit-тесты. У меня есть 2 заметки по этой теме:

Теперь вы готовы взглянуть на мои инструменты поближе. Я разделил их на категории, чтобы вам было проще ориентироваться.

Итак, вот 12 инструментов, которые я использую при интеграционном и unit-тестировании.

Запуск тестов

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

NestedRunner — расширение для JUnit, которое позволяет запускать тестовые методы из вложенных классов. Мне нравится NestedRunner по ряду причин:

junit-davaprovider — расширение для JUnit, позволяющее писать параметризованные тесты с использованием TestNG в качестве провайдера данных. Это большое улучшение по сравнению с обычным способом написания параметризованных тестов, который, прямо скажем, не очень.

Дополнительно:

Макеты, заглушки, подмены

Mockito — самый популярный фреймворк с поддержкой макетирования для unit-тестов. Мне он нравится из-за простого API, множества полезных возможностей и превосходной документации.

Greenmail — сервер электронной почты, который поддерживает SMTP, POP3 и IMAP с поддержкой SSL-соединения. Он мне нравится из-за простоты использования. Когда я искал отладочный сервер электронной почты, пересмотрев несколько альтернатив, остановился на Greenmail, т.к. он работал именно так, как мне требовалось.

MockFtpServer — библиотека, которая предоставляет две разные реализации FTP-сервера («заглушка» и «обманка»), которые можно использовать для тестирования различных сценариев. Если вам нужно протестировать код, взаимодействующий с FTP-сервером, наш выбор — MockFtpServer.

Дополнительно:

Утверждения

Hamcrest предоставляет инструменты для написания утверждений (assertions) для unit- и интеграционнаых тестов. Я его использую вместе со Spring MVC Test Framework.

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

Дополнительно:

Тестирование кода доступа к данным

H2 — быстрая БД, полезна для написания интеграционных тестов, которые запускаются на локальной машине разработчика.

DbUnit — расширение для JUnit, которое может быть использовано для инициализации БД в известное состояние перед выполнением каждого интеграционного теста и заполнения БД нужными данными. У DbUnit есть свои недостатки, но это очень полезный инструмент, позволяющий разделить тестовые данные и тестовый код.

Дополнительно:

Тестирование Spring приложений

Spring Test DbUnit — интегрирует DbUnit во фреймфорк String Test. Если вам нужно написать тесты доступа к данным для приложения, использующего Spring и реляционную БД, то Spring Test DbUnit вам в помощь.

Источник

Передовой опыт тестирования в Java

интеграционные тесты java что это. интеграционные тесты java что это фото. картинка интеграционные тесты java что это. смотреть фото интеграционные тесты java что это. смотреть картинку интеграционные тесты java что это.

Чтобы покрытие кода было достаточным, а создание нового функционала и рефакторинг старого проходили без страха что-то сломать, тесты должны быть поддерживаемыми и легко читаемыми. В этой статье я расскажу о множестве приёмов написания юнит- и интеграционных тестов на Java, собранных мной за несколько лет. Я буду опираться на современные технологии: JUnit5, AssertJ, Testcontainers, а также не обойду вниманием Kotlin. Некоторые советы покажутся вам очевидными, другие могут идти вразрез с тем, что вы читали в книгах о разработке ПО и тестировании.

Вкратце

Общие положения

Given, When, Then (Дано, Когда, То)

Тест должен содержать три блока, разделённых пустыми строками. Каждый блок должен быть максимально коротким. Используйте локальные методы для компактности записи.

Given / Дано (ввод): подготовка теста, например, создание данных и конфигурация моков.
When / Когда (действие): вызов тестируемого метода
Then / То (вывод): проверка корректности полученного значения

Используйте префиксы “actual*” и “expected*”

Если вы собираетесь использовать переменные в проверке на совпадение значений, добавьте к этим переменным префиксы “actual” и “expected”. Так вы улучшите читаемость кода и проясните назначение переменных. Кроме того, так их сложнее перепутать при сравнении.

Используйте заданные значения вместо случайных

Избегайте подавать случайные значения на вход тестов. Это может привести к «морганию» тестов, что чертовски сложно отлаживать. Кроме того, увидев в сообщении об ошибке случайное значение, вы не сможете проследить его до того места, где ошибка возникла.

Используйте для всего подряд разные заранее заданные значения. Так вы получите идеально воспроизводимые результаты тестов, а также быстро найдёте нужное место в коде по сообщению об ошибке.

Вы можете записать это ещё короче, используя вспомогательные функции (см. ниже).

Пишите краткие и конкретные тесты

Где можно, используйте вспомогательные функции

Вычленяйте повторяющийся код в локальные функции и давайте им понятные имена. Так ваши тесты будут компактными и легко читаемыми с первого взгляда.

Вспомогательные функции на Kotlin можно реализовать так:

Не злоупотребляйте переменными

Условный рефлекс у программиста — вынести часто используемые значения в переменные.

Увы, это очень перегружает код. Хуже того, увидев значение в сообщении об ошибке, — его будет невозможно проследить до места, где ошибка возникла.

Если вы стараетесь писать тесты максимально компактно (что я, в любом случае, горячо рекомендую), то переиспользуемые значения хорошо видны. Сам код становится более убористым и хорошо читаемым. И, наконец, сообщение об ошибке приведёт вас точно к той строке, где ошибка возникла.

Не расширяйте существующие тесты, чтобы «добавить ещё одну маленькую штучку»

Всегда есть соблазн добавить частный случай к существующему тесту, проверяющему базовую функциональность. Но в результате тесты становятся больше и сложнее для понимания. Частные случаи, раскиданные по большой простыне кода, легко не заметить. Если тест сломался, вы не сразу поймёте, что именно послужило причиной.

Вместо этого напишите новый тест с наглядным названием, из которого сразу будет понятно, какого поведения он ожидает от тестируемого кода. Да, придётся набрать больше букв на клавиатуре (против этого, напомню, хорошо способствуют вспомогательные функции), но зато вы получите простой и понятный тест с предсказуемым результатом. Это, кстати, отличный способ документировать новый функционал.

Проверяйте только то, что хотите протестировать

Думайте о том функционале, который тестируется. Избегайте делать лишние проверки просто потому, что есть такая возможность. Более того, помните о том, что уже проверялось в ранее написанных тестах и не проверяйте это повторно. Тесты должны быть компактными и их ожидаемое поведение должно быть очевидным и лишённым ненужных подробностей.

Предположим, что мы хотим проверить HTTP-ручку, возвращающую список товаров. Наш тестовый набор должен содержать следующие тесты:

3. Ещё пару тестов, проверяющих особые случаи или особую бизнес-логику, например что определённые значения в ответе вычислены корректно. В этом случае нас интересуют только несколько полей из всего JSON-ответа. Тем самым мы своим тестом документируем именно эту специальную логику. Понятно, что ничего кроме этих полей нам здесь не нужно.

Самодостаточные тесты

Не прячьте релевантные параметры (во вспомогательных функциях)

Использовать вспомогательные функции для генерации данных и проверки условий удобно, но их следует вызывать с параметрами. Принимайте на вход параметры для всего, что значимо в рамках теста и должно контролироваться из кода теста. Не заставляйте читателя переходить внутрь вспомогательной функции, чтобы понять смысл теста. Простое правило: смысл теста должен быть понятен при взгляде на сам тест.

Держите тестовые данные внутри самих тестов

Всё должно быть внутри. Велик соблазн перенести часть данных в метод @Before и переиспользовать их оттуда. Но это вынудит читателя скакать туда-сюда по файлу, чтобы понять, что именно тут происходит. Опять же, вспомогательные функции помогут избежать повторений и сделают тесты более понятными.

Используйте композицию вместо наследования

Не выстраивайте сложных иерархий тестовых классов.

«Лучше продублировать код, чем выбрать неправильную абстракцию» (Sandi Metz)

Вместо этого я рекомендую использовать композицию. Напишите маленькие фрагменты кода и классы для каждой задачи, связанной с фикстурами (запустить тестовую базу данных, создать схему, вставить данные, запустить мок-сервер). Переиспользуйте эти запчасти в методе @BeforeAll или через присвоение созданных объектов полям тестового класса. Таким образом, вы сможете собирать каждый новый тестовый класс из этих заготовок, как из деталей Лего. В результате каждый тест будет иметь свой собственный понятный набор фикстур и гарантировать, что в нём не происходит ничего постороннего. Тест становится самодостаточным, потому что содержит в себе всё необходимое.

Прямолинейные тесты — это хорошо. Сравнивайте результат с константами

Не переиспользуйте продакшн-код

Вместо этого при написании тестов думайте в терминах ввода и вывода. Тест подаёт данные на вход и сравнивает вывод с предопределёнными константами. Бóльшую часть времени переиспользование кода не требуется.

Не копируйте бизнес-логику в тесты

Правильным будет решение, при котором actualDTO сравнивается с созданным вручную эталонным объектом с заданными значениями. Это предельно просто, понятно и защищает от потенциальных ошибок.

Если вы не хотите создавать и проверять на совпадение целый эталонный объект, можете проверить дочерний объект или вообще только релевантные тесту свойства объекта.

Не пишите слишком много логики

Напомню, что тестирование касается в основном ввода и вывода. Подавайте на вход данные и проверяйте, что вам вернулось. Нет необходимости писать сложную логику внутри тестов. Если вы вводите в тест циклы и условия, вы делаете его менее понятным и более неустойчивым к ошибкам. Если ваша логика проверки сложна, пользуйтесь многочисленными функциями AssertJ, которые сделают эту работу за вас.

Запускайте тесты в среде, максимально похожей на боевую

Тестируйте максимально полную связку компонентов

Обычно рекомендуется тестировать каждый класс изолированно при помощи моков. У этого подхода, однако, есть и недостатки: таким образом не тестируется взаимодействие классов между собой, и любой рефакторинг общих сущностей сломает все тесты разом, потому что у каждого внутреннего класса свои тесты. Кроме того, если писать тесты для каждого класса, то их будет просто слишком много.

интеграционные тесты java что это. интеграционные тесты java что это фото. картинка интеграционные тесты java что это. смотреть фото интеграционные тесты java что это. смотреть картинку интеграционные тесты java что это.
Изолированное юнит-тестирование каждого класса

Вместо этого я рекомендую сосредоточиться на интеграционном тестировании. Под «интеграционным тестированием» я подразумеваю сбор всех классов воедино (как на продакшене) и тестирование всей связки, включая инфраструктурные компоненты (HTTP-сервер, базу данных, бизнес-логику). В этом случае вы тестируете поведение вместо реализации. Такие тесты более аккуратны, близки к реальному миру и устойчивы к рефакторингу внутренних компонентов. В идеале, вам будет достаточно одного класса тестов.

интеграционные тесты java что это. интеграционные тесты java что это фото. картинка интеграционные тесты java что это. смотреть фото интеграционные тесты java что это. смотреть картинку интеграционные тесты java что это.
Интеграционное тестирование (= собрать все классы вместе и тестировать связку)

Не используйте in-memory-базы данных для тестов

интеграционные тесты java что это. интеграционные тесты java что это фото. картинка интеграционные тесты java что это. смотреть фото интеграционные тесты java что это. смотреть картинку интеграционные тесты java что это.
С in-memory базой вы тестируете не в той среде, где будет работать ваш код

Используя in-memory базу (H2, HSQLDB, Fongo) для тестов, вы жертвуете их достоверностью и рамками применимости. Такие базы данных часто ведут себя иначе и выдают отличающиеся результаты. Такой тест может пройти успешно, но не гарантирует корректной работы приложения на проде. Более того, вы можете запросто оказаться в ситуации, когда вы не можете использовать или протестировать какое-то характерное для вашей базы поведение или фичу, потому в in-memory БД они не реализованы или ведут себя иначе.

Решение: использовать такую же БД, как и в реальной эксплуатации. Замечательная библиотека Testcontainers предоставляет богатый API для Java-приложений, позволяющий управлять контейнерами прямо из кода тестов.

Java/JVM

Совет: добавьте эти аргументы к шаблону конфигурации “JUnit” в IntelliJ IDEA, чтобы не делать это каждый раз при создании нового проекта.

интеграционные тесты java что это. интеграционные тесты java что это фото. картинка интеграционные тесты java что это. смотреть фото интеграционные тесты java что это. смотреть картинку интеграционные тесты java что это.

Используйте AssertJ

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

Избегайте использовать assertTrue() и assertFalse()

Использование простых assertTrue() или assertFalse() приводит к загадочным сообщениям об ошибках тестов:

Используйте вместо них вызовы AssertJ, которые «из коробки» возвращают понятные и информативные сообщения.

Если вам надо проверить boolean-значение, сделайте сообщение более информативным при помощи метода as() AssertJ.

Используйте JUnit5

JUnit5 — превосходная библиотека для (юнит-)тестирования. Она находится в процессе постоянного развития и предоставляет программисту множество полезных возможностей, таких, например, как параметризованные тесты, группировки, условные тесты, контроль жизненного цикла.

Используйте параметризованные тесты

Я горячо рекомендую использовать этот приём по максимуму, поскольку он позволяет тестировать больше кейсов с минимальными трудозатратами.

@MethodSource особенно эффективен в связке с отдельным тестовым объектом, содержащим все нужные параметры и ожидаемые результаты. К сожалению, в Java описание таких структур данных (т.н. POJO) очень громоздки. Поэтому я приведу пример с использованием дата-классов Kotlin.

Группируйте тесты

интеграционные тесты java что это. интеграционные тесты java что это фото. картинка интеграционные тесты java что это. смотреть фото интеграционные тесты java что это. смотреть картинку интеграционные тесты java что это.

Читаемые названия тестов при помощи @DisplayName или обратных кавычек в Kotlin

интеграционные тесты java что это. интеграционные тесты java что это фото. картинка интеграционные тесты java что это. смотреть фото интеграционные тесты java что это. смотреть картинку интеграционные тесты java что это.

В Kotlin можно использовать имена функций с пробелами внутри, если заключить их в обратные одиночные кавычки. Так вы получите читаемость результатов без избыточности кода.

Имитируйте внешние сервисы

Для тестирования HTTP-клиентов нам необходимо имитировать сервисы, к которым они обращаются. Я часто использую в этих целях MockWebServer из OkHttp. Альтернативами могут служить WireMock или Mockserver из Testcontainers.

Используйте Awaitility для тестирования асинхронного кода

Awaitility — это библиотека для тестирования асинхронного кода. Вы можете указать, сколько раз надо повторять попытки проверки результата перед тем, как признать тест неудачным.

Не надо резолвить DI-зависимости (Spring)

Инициализация DI-фреймворка занимает несколько секунд перед тем, как тесты могут стартовать. Это замедляет цикл обратной связи, особенно на начальном этапе разработки.

Поэтому я стараюсь не использовать DI в интеграционных тестах, а создаю нужные объекты вручную и «провязываю» их между собой. Если вы используете внедрение в конструктор, то это самое простое. Как правило, в своих тестах вы проверяете бизнес-логику, а для этого DI не нужно.

Более того, начиная с версии 2.2, Spring Boot поддерживает ленивую инициализацию бинов, что заметно ускоряет тесты, использующие DI.

Ваш код должен быть тестируемым

Не используйте статический доступ. Никогда

Статический доступ — это антипаттерн. Во-первых, он запутывает зависимости и побочные эффекты, делая весь код сложночитаемым и подверженным неочевидным ошибкам. Во-вторых, статический доступ мешает тестированию. Вы больше не можете заменять объекты, но в тестах вам нужно использовать моки или реальные объекты с другой конфигурацией (например, DAO-объект, указывающий на тестовую базу данных).

Вместо статического доступа к коду положите его в нестатический метод, создайте экземпляр класса и передайте полученный объект в конструктор.

К счастью, DI-фреймворки типа Spring предоставляют инструменты, делающие статический доступ ненужным, автоматически создавая и связывая объекты без нашего участия.

Параметризуйте

Все релевантные части класса должны иметь возможность настройки со стороны теста. Такие настройки можно передавать в конструктор класса.

Представьте, например, что ваш DAO имеет фиксированный лимит в 1000 объектов на запрос. Чтобы проверить этот лимит, вам надо будет перед тестом добавить в тестовую БД 1001 объект. Используя аргумент конструктора, вы можете сделать это значение настраиваемым: в продакшене оставить 1000, в тестировании сократить до 2. Таким образом, чтобы проверить работу лимита вам будет достаточно добавить в тестовую БД всего 3 записи.

Используйте внедрение в конструктор

Внедрение полей — зло, оно ведёт к плохой тестируемости кода. Вам необходимо инициализировать DI перед тестами или заниматься стрёмной магией рефлексий. Поэтому предпочтительно использовать внедрение через конструктор, чтобы легко контролировать зависимые объекты в процессе тестирования.

На Java придётся написать немного лишнего кода:

В Kotlin тоже самое пишется намного лаконичнее:

Не используйте Instant.now() или new Date()

Не надо получать текущее время вызовами Instant.now() или new Date() в продакшн-коде, если вы хотите тестировать это поведение.

Проблема в том, что полученное время не может контролироваться со стороны теста. Вы не сможете сравнить полученный результат с конкретным значением, потому что он всё время разный. Вместо этого используйте класс Clock из Java.

Разделяйте асинхронное выполнение и собственно логику

Тестирование асинхронного кода — непростая штука. Библиотеки типа Awaitility оказывают большую помощь, но процесс всё равно запутан, и мы можем получить «моргающий» тест. Есть смысл разделять бизнес-логику (обычно синхронную) и асинхронный инфраструктурный код, если такая возможность имеется.

Например, вынеся бизнес-логику в ProductController мы сможем запросто протестировать её синхронно. Вся асинхронная и параллельная логика останутся в ProductScheduler, который можно протестировать изолированно.

Источник

Добавить комментарий

Ваш адрес email не будет опубликован. Обязательные поля помечены *