Початок статті: пишемо JRTB-3 . Тепер слід подумати про тестування. Весь доданий код повинен бути покритий тестами, щоб ми були впевнені, що функціональність працює, як ми очікуємо. Першими напишемо unit-тести для сервісу SendBotMessageService.
Unit-тест - це тест, який перевіряє логіку якоїсь маленької частини програми: зазвичай це методи. А всі зв'язки, які мають цей метод, замінюються на несправжні за допомогою моків.
Зараз ви побачите все. У тому ж пакеті, тільки вже в папці ./src/test/java створюємо клас з таким самим ім'ям, як у класу, який тестуватимемо, і додаємо в кінці Test . Тобто для SendBotMessageService ми матимемо SendBotMessageServiceTest , в якому будуть всі тести на цей клас. Ідея в його тестуванні наступна: ми підсовуємо моковий (фейковий) CodeGymTelegarmBot, у якого потім спитаємо, чи викликався метод execute з таким аргументом чи ні. Ось що вийшло:
За допомогою Mockito я створив моковий об'єкт CodeGymBot, який передав конструктор нашому сервісу. Далі написав один тест (кожен метод із анотацією Test – це окремий тест). Структура цього методу одна й та сама завжди - він не приймає аргументи, і повертає void. Ім'я тесту має розповісти, що ми тестуємо. У нашому випадку це: should properly send message - має правильно надіслати повідомлення. Тест у нас поділений на три частини:
блок //given - де ми готуємо все необхідне тесту;
блок //when - де запускаємо той метод, який плануємо тестувати;
блок //then - де ми перевіряємо, чи правильно відпрацював метод.
Оскільки поки що логіка у нашому сервісі проста, одного тесту для цього класу буде достатньо. Тепер напишемо тест на CommandContainer:
Тут не зовсім очевидний тест. Він спирається на логіку роботи контейнера. Усі команди, які підтримують бот, перебувають у списку CommandName і мають бути у контейнері. Тому я взяв усі змінні CommandName, перейшов у Stream API і для кожного здійснив пошук команди з контейнера. Якби такої команди не було, було б повернено команду UnknownCommand. Це ми і перевіряємо у цьому рядку:
А щоб перевірити, що за замовчуванням буде UnknownCommand, потрібен окремий тест - shouldReturnUnknownCommand . Раджу ці тести переписати та проаналізувати. Для команд поки що будуть напівформальні випробування, але їх треба писати. Логіка буде така ж, як і для тестування SendBotMessageService, тому я винесу загальну логіку тестів до AbstractCommandTest класу, і вже кожен конкретний тест-клас успадковуватиметься і визначатиме необхідні йому поля. Так як всі тести однотипні, писати одне і теж щоразу не з руки, плюс це не ознака хорошого коду. Ось такий вийшов узагальнений абстрактний клас:
Як бачимо, у нас є три абстрактні методи, після визначення яких кожна команда має запуститися тест, який тут написаний, і виконається правильно. Це такий зручний підхід, коли основна логіка знаходиться в абстрактному класі, а ось деталі визначаються спадкоємцями. А ось, власне, реалізації конкретних тестів:
HelpCommandTest:
packagecom.github.codegymcommunity.jrtb.command;importorg.junit.jupiter.api.DisplayName;importstaticcom.github.codegymcommunity.jrtb.command.CommandName.HELP;importstaticcom.github.codegymcommunity.jrtb.command.HelpCommand.HELP_MESSAGE;@DisplayName("Unit-level testing for HelpCommand")publicclassHelpCommandTestextendsAbstractCommandTest{@OverrideStringgetCommandName(){return HELP.getCommandName();}@OverrideStringgetCommandMessage(){return HELP_MESSAGE;}@OverrideCommandgetCommand(){returnnewHelpCommand(sendBotMessageService);}}
NoCommandTest:
packagecom.github.codegymcommunity.jrtb.command;importorg.junit.jupiter.api.DisplayName;importstaticcom.github.codegymcommunity.jrtb.command.CommandName.NO;importstaticcom.github.codegymcommunity.jrtb.command.NoCommand.NO_MESSAGE;@DisplayName("Unit-level testing for NoCommand")publicclassNoCommandTestextendsAbstractCommandTest{@OverrideStringgetCommandName(){return NO.getCommandName();}@OverrideStringgetCommandMessage(){return NO_MESSAGE;}@OverrideCommandgetCommand(){returnnewNoCommand(sendBotMessageService);}}
StartCommandTest:
packagecom.github.codegymcommunity.jrtb.command;importorg.junit.jupiter.api.DisplayName;importstaticcom.github.codegymcommunity.jrtb.command.CommandName.START;importstaticcom.github.codegymcommunity.jrtb.command.StartCommand.START_MESSAGE;@DisplayName("Unit-level testing for StartCommand")classStartCommandTestextendsAbstractCommandTest{@OverrideStringgetCommandName(){return START.getCommandName();}@OverrideStringgetCommandMessage(){return START_MESSAGE;}@OverrideCommandgetCommand(){returnnewStartCommand(sendBotMessageService);}}
StopCommandTest:
packagecom.github.codegymcommunity.jrtb.command;importorg.junit.jupiter.api.DisplayName;importstaticcom.github.codegymcommunity.jrtb.command.CommandName.STOP;importstaticcom.github.codegymcommunity.jrtb.command.StopCommand.STOP_MESSAGE;@DisplayName("Unit-level testing for StopCommand")publicclassStopCommandTestextendsAbstractCommandTest{@OverrideStringgetCommandName(){return STOP.getCommandName();}@OverrideStringgetCommandMessage(){return STOP_MESSAGE;}@OverrideCommandgetCommand(){returnnewStopCommand(sendBotMessageService);}}
UnknownCommandTest:
packagecom.github.codegymcommunity.jrtb.command;importorg.junit.jupiter.api.DisplayName;importstaticcom.github.codegymcommunity.jrtb.command.UnknownCommand.UNKNOWN_MESSAGE;@DisplayName("Unit-level testing for UnknownCommand")publicclassUnknownCommandTestextendsAbstractCommandTest{@OverrideStringgetCommandName(){return"/fdgdfgdfgdbd";}@OverrideStringgetCommandMessage(){return UNKNOWN_MESSAGE;}@OverrideCommandgetCommand(){returnnewUnknownCommand(sendBotMessageService);}}
Відмінно видно, що гра коштувала свічок, і завдяки AbstractCommandTest ми отримали в результаті прості та зрозумілі тести, які легко писати, легко розуміти. На додаток позбулися зайвого дублювання коду (привіт принципу DRY -> Don't Repeat Yourself). До того ж, тепер у нас є справжні тести, за якими можна судити про роботу програми. Ще добре б написати тест на самого бота, але там все так просто не вийде і взагалі, може, гра не варта свічок, як то кажуть. Тому на даному етапі завершуватимемо наше завдання. Останнє і улюблене - створюємо коміт, пише повідомлення: JRTB-3: І як зазвичай - гітхаб вже знає і пропонує створити пулл-реквест:Білд пройшов і вже можна мержить… Але ні! Я ж забув оновити версію проекту та записати у RELEASE_NOTES. Додаємо запис з новою версією - 0.2.0-SNAPSHOT: Оновлюємо цю версію в pom.xml і створюємо новий коміт: Новий комміт: JRTB-3: updated RELEASE_NOTES.md Тепер пуш і чекаємо поки пройде білд. Білд пройшов, можна й мержити: Гілку я не видаляю, тож завжди можна буде подивитися і порівняти, що змінилося. Наша дошка із завданнями оновилася:
Висновки
Сьогодні ми зробабо велику справу: запровадабо Command шаблон для роботи. Все налаштовано, і тепер додавання нової команди буде простим та зрозумілим процесом. Також сьогодні сьогодні поговорабо про тестування. Трохи навіть погралися для того, щоб не повторювати код у різних тестах для команд. Традиційно пропоную зареєструватися на GitHub і підписатися на мій обліковий запис , щоб стежити за цією серією та іншими проектами, які я там веду. Також я створив телеграм-канал, в якому дублюватиму вихід нових статей. З цікавого — код зазвичай виходить на тиждень раніше за саму статтю, і на каналі я щоразу писатиму про те, що нове завдання зроблено, що дасть можливість розібратися з кодом до прочитання статті. Незабаром я опублікую бота на постійній основі, і першим дізнаються про це ті, хто підписаний на телеграм канал ;) Всім дякую за прочитання, продовження слідує.