Начало статьи: пишем JRTB-3.
Теперь нужно подумать о тестировании. Весь добавленный код должен быть покрыт тестами, чтобы мы были уверены, что функциональность работает как мы ожидаем.
Первыми напишем unit-тесты для сервиса SendBotMessageService.
Unit-тест — это тест, который проверяет логику какой-то маленькой части приложения: обычно это методы. А все связи, у которых есть этот метод, заменяются на ненастоящие при помощи моков.
Сейчас вы все увидите. В том же пакете, только уже в папке ./src/test/java создаем класс с таким же именем, как у класса, который будем тестировать, и добавляем в конце Test. То есть для SendBotMessageService у нас будет SendBotMessageServiceTest, в котором будут все тесты на этот класс.
Идея в его тестировании следующая: мы подсовываем моковый (фейковый) JavaRushTelegarmBot, у которого потом спросим, вызывался ли метод execute с таким аргументом или нет.
Вот что получилось:
При помощи Mockito я создал моковый объект JavaRushBot, который передал в конструктор нашему сервису.
Далее написал один тест (каждый метод с аннотацией Test — это отдельный тест). Структура этого метода одна и та же всегда — он не принимает аргументы, и возвращает void. Имя теста должно рассказать о том, что мы тестируем. В нашем случае это:
should properly send message — должен правильно отправить сообщение.
Тест у нас поделен на три части:
блок //given — где мы подготавливаем все необходимое к тесту;
блок //when — где запускаем тот метод, который планируем тестировать;
блок //then — где мы проверяем, правильно ли отработал метод.
Так как пока что логика в нашем сервисе простая, одного теста для этого класса будет достаточно.
Теперь напишем тест на CommandContainer:
Здесь не совсем очевидный тест. Он опирается на логику работы контейнера. Все команды, которые поддерживает бот, находятся в списке CommandName и должны быть в контейнере. Поэтому я взял все переменные CommandName, перешел в Stream API и для каждого выполнил поиск команды из контейнера. Если бы такой команды не было, была бы возвращена команда UnknownCommand.
Это мы и проверяем в этой строке:
А чтобы проверить, что по умолчанию будет UnknownCommand, нужен отдельный тест — shouldReturnUnknownCommand.
Советую эти тесты переписать и проанализировать.
Для команд пока что будут полуформальные тесты, но их нужно писать. Логика будет такая же, как и для тестирования SendBotMessageService, поэтому я вынесу общую логику тестов в AbstractCommandTest класс, и уже каждый конкретный тест-класс будет наследоваться и определять необходимые ему поля.
Так как все тесты однотипные, писать одно и тоже каждый раз не с руки, плюс это не признак хорошего кода. Вот такой получился обобщенный абстрактный класс:
Как видим, у нас есть три абстрактных метода, после определения которых у каждой команды должен запуститься тест, который здесь написан, и выполнится правильно.
Это такой удобный подход, когда основная логика находится в абстрактном классе, а вот детали определяются в наследниках. А вот, собственно, реализации конкретных тестов:
package com.github.javarushcommunity.jrtb.command;
import org.junit.jupiter.api.DisplayName;
import static com.github.javarushcommunity.jrtb.command.UnknownCommand.UNKNOWN_MESSAGE;
@DisplayName("Unit-level testing for UnknownCommand")
public class UnknownCommandTest extends AbstractCommandTest {
@Override
String getCommandName() {
return "/fdgdfgdfgdbd";
}
@Override
String getCommandMessage() {
return UNKNOWN_MESSAGE;
}
@Override
Command getCommand() {
return new UnknownCommand(sendBotMessageService);
}
}
Отлично видно, что игра стоила свеч, и благодаря AbstractCommandTest мы получили в итоге простые и понятные тесты, которые легко писать, легко понимать. В довесок избавились от лишнего дублирования кода (привет принципу DRY -> Don’t Repeat Yourself).
К тому же теперь у нас есть настоящие тесты, по которым можно судить о работе приложения.
Еще хорошо бы написать тест на самого бота, но там все так просто не получится и вообще, может, игра не стоит свеч, как говорится. Поэтому на данном этапе будем завершать нашу задачу.
Последнее и любимое — создаем коммит, пишет сообщение:
JRTB-3: added Command pattern for handling Telegram Bot commands
И как обычно — гитхаб уже знает и предлагает создать пулл-реквест:Билд прошел и можно уже мержить… Но нет!
Я ж забыл обновить версию проекта и записать в RELEASE_NOTES. Добавляем запись с новой версией — 0.2.0-SNAPSHOT:Обновляем эту версию в pom.xml и создаем новый коммит:Новый коммит: JRTB-3: updated RELEASE_NOTES.mdТеперь пуш и ждем пока пройдет билд.
Билд прошел, можно и мержить:Ветку я не удаляю, так что всегда можно будет посмотреть и сравнить, что изменилось.
Наша доска с задачами обновилась:
Выводы
Сегодня мы сделали большое дело: внедрили Command шаблон для работы. Все настроено, и теперь добавление новой команды будет простым и понятным процессом.
Также сегодня сегодня поговорили о тестировании. Немного даже поигрались с тем, чтобы не повторять код в разных тестах для команд.
Традиционно предлагаю зарегистрироваться на GitHub и подписаться на мой аккаунт, чтобы следить за этой серией и другими проектами, которые я там веду.
Также я создал телеграм-канал, в котором буду дублировать выход новых статей. Из интересного — код обычно выходит на неделю раньше самой статьи, и на канале я буду каждый раз писать о том, что новая задача сделана, что даст возможность разобраться с кодом до прочтения статьи.
В скором времени я опубликую бота на постоянной основе, и первым узнают об этом те, кто подписан на телеграмм канал ;)
Всем спасибо за прочтение, продолжение следует.
Непонятно, зачем в абстрактном классе строка
Mockito.when(message.getText()).thenReturn(getCommandName());
Ведь получение текста из Message при выполнении команд не вызывается ( по крайней мере на текущий момент). Тесты работают, соответственно и без этой команды
Подобный способ вынесения реализации методов в конкретные классы, при сохранении основной логики (последовательности действий) в абстрактном классе ни что иное как паттерн шаблонный метод.
⚡️UPDATE⚡️
Друзья, создал телеграм-канал 🤓, в котором освещаю свою писательскую деятельность и свою open-source разработку в целом.
Не хотите пропустить новые статьи? Присоединяйтесь ✌️
Почему в CommandContainerTest мне пришлось импортировать классы Command, CommandContainer, CommandName и UnknownCommand, поскольку Идея ругалась на неизвесный класс? Что я сделал не так?
ПЕРЕЙДИТЕ В ПОЛНУЮ ВЕРСИЮ