Nagsusulat kami ng mga pagsubok para sa aplikasyon
Simula ng artikulo:
pagsulat ng JRTB-3 . Ngayon kailangan nating mag-isip tungkol sa pagsubok. Ang lahat ng idinagdag na code ay dapat na sakop ng mga pagsubok upang makatiyak kami na gumagana ang functionality gaya ng inaasahan namin. Magsusulat muna kami ng mga unit test para sa serbisyo ng SendBotMessageService.
Ang unit test ay isang pagsubok na sumusubok sa lohika ng ilang maliit na bahagi ng application: kadalasan ito ay mga pamamaraan. At lahat ng koneksyon na may ganitong paraan ay pinapalitan ng mga pekeng gamit gamit ang mga pangungutya. |
Ngayon makikita mo ang lahat. Sa parehong pakete, sa folder lamang na
./src/test/java , lumikha kami ng isang klase na may parehong pangalan sa klase na aming susubukan, at idagdag ang
Pagsubok sa dulo . Iyon ay, para sa
SendBotMessageService magkakaroon tayo
ng SendBotMessageServiceTest , na maglalaman ng lahat ng mga pagsubok para sa klase na ito. Ang ideya sa pagsubok nito ay ang mga sumusunod: kami ay nadulas sa isang kunwaring (pekeng) JavaRushTelegarmBot, na pagkatapos ay itatanong namin kung ang execute na paraan ay tinawag na may ganoong argumento o hindi. Narito ang nangyari:
package com.github.javarushcommunity.jrtb.service;
import com.github.javarushcommunity.jrtb.bot.JavarushTelegramBot;
import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.DisplayName;
import org.junit.jupiter.api.Test;
import org.mockito.Mockito;
import org.telegram.telegrambots.meta.api.methods.send.SendMessage;
import org.telegram.telegrambots.meta.exceptions.TelegramApiException;
@DisplayName("Unit-level testing for SendBotMessageService")
public class SendBotMessageServiceTest {
private SendBotMessageService sendBotMessageService;
private JavarushTelegramBot javarushBot;
@BeforeEach
public void init() {
javarushBot = Mockito.mock(JavarushTelegramBot.class);
sendBotMessageService = new SendBotMessageServiceImpl(javarushBot);
}
@Test
public void shouldProperlySendMessage() throws TelegramApiException {
String chatId = "test_chat_id";
String message = "test_message";
SendMessage sendMessage = new SendMessage();
sendMessage.setText(message);
sendMessage.setChatId(chatId);
sendMessage.enableHtml(true);
sendBotMessageService.sendMessage(chatId, message);
Mockito.verify(javarushBot).execute(sendMessage);
}
}
Gamit ang Mockito, gumawa ako ng mock JavaRushBot object, na ipinasa ko sa constructor ng aming serbisyo. Susunod, nagsulat ako ng isang pagsubok (bawat pamamaraan na may Test annotation ay isang hiwalay na pagsubok). Ang istraktura ng pamamaraang ito ay palaging pareho - hindi ito tumatagal ng mga argumento at nagbabalik ng walang bisa. Dapat sabihin sa iyo ng pangalan ng pagsubok kung ano ang aming sinusubok. Sa aming kaso, ito ay: dapat maayos na magpadala ng mensahe - dapat magpadala ng mensahe nang tama. Ang aming pagsubok ay nahahati sa tatlong bahagi:
- block //ibinigay - kung saan inihahanda namin ang lahat ng kailangan para sa pagsubok;
- block //when - kung saan ilulunsad namin ang paraan na plano naming subukan;
- //then block - kung saan sinusuri namin kung ang pamamaraan ay gumana nang tama.
Dahil ang lohika sa aming serbisyo ay simple sa ngayon, isang pagsubok para sa klase na ito ay sapat na. Ngayon magsulat tayo ng isang pagsubok para sa CommandContainer:
package com.github.javarushcommunity.jrtb.command;
import com.github.javarushcommunity.jrtb.service.SendBotMessageService;
import org.junit.jupiter.api.Assertions;
import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.DisplayName;
import org.junit.jupiter.api.Test;
import org.mockito.Mockito;
import java.util.Arrays;
@DisplayName("Unit-level testing for CommandContainer")
class CommandContainerTest {
private CommandContainer commandContainer;
@BeforeEach
public void init() {
SendBotMessageService sendBotMessageService = Mockito.mock(SendBotMessageService.class);
commandContainer = new CommandContainer(sendBotMessageService);
}
@Test
public void shouldGetAllTheExistingCommands() {
Arrays.stream(CommandName.values())
.forEach(commandName -> {
Command command = commandContainer.retrieveCommand(commandName.getCommandName());
Assertions.assertNotEquals(UnknownCommand.class, command.getClass());
});
}
@Test
public void shouldReturnUnknownCommand() {
String unknownCommand = "/fgjhdfgdfg";
Command command = commandContainer.retrieveCommand(unknownCommand);
Assertions.assertEquals(UnknownCommand.class, command.getClass());
}
}
Ito ay hindi isang napakalinaw na pagsubok. Ito ay umaasa sa lohika ng lalagyan. Ang lahat ng command na sinusuportahan ng bot ay nasa listahan ng CommandName at dapat nasa container. Kaya kinuha ko ang lahat ng mga variable ng CommandName, pumunta sa Stream API at para sa bawat isa ay naghanap ako ng isang command mula sa lalagyan. Kung walang ganoong utos, ibabalik ang UnknownCommand. Ito ang sinusuri namin sa linyang ito:
Assertions.assertNotEquals(UnknownCommand.class, command.getClass());
At upang suriin na ang UnknownCommand ang magiging default, kailangan mo ng hiwalay na pagsubok -
shouldReturnUnknownCommand . Ipinapayo ko sa iyo na muling isulat at suriin ang mga pagsubok na ito. Magkakaroon ng semi-formal na pagsusulit para sa mga koponan sa ngayon, ngunit kailangan nilang isulat. Ang lohika ay magiging kapareho ng para sa pagsubok sa SendBotMessageService, kaya ililipat ko ang pangkalahatang lohika ng pagsubok sa klase ng AbstractCommandTest, at ang bawat partikular na klase ng pagsubok ay mamamana at tutukuyin ang mga patlang na kailangan nito. Dahil ang lahat ng mga pagsubok ay may parehong uri, ang pagsulat ng parehong bagay sa bawat oras ay hindi madali, at ito ay hindi isang tanda ng magandang code. Ganito ang naging pangkalahatang abstract na klase:
package com.github.javarushcommunity.jrtb.command;
import com.github.javarushcommunity.jrtb.bot.JavarushTelegramBot;
import com.github.javarushcommunity.jrtb.service.SendBotMessageService;
import com.github.javarushcommunity.jrtb.service.SendBotMessageServiceImpl;
import org.junit.jupiter.api.Test;
import org.mockito.Mockito;
import org.mockito.internal.verification.VerificationModeFactory;
import org.telegram.telegrambots.meta.api.methods.send.SendMessage;
import org.telegram.telegrambots.meta.api.objects.Message;
import org.telegram.telegrambots.meta.api.objects.Update;
import org.telegram.telegrambots.meta.exceptions.TelegramApiException;
abstract class AbstractCommandTest {
protected JavarushTelegramBot javarushBot = Mockito.mock(JavarushTelegramBot.class);
protected SendBotMessageService sendBotMessageService = new SendBotMessageServiceImpl(javarushBot);
abstract String getCommandName();
abstract String getCommandMessage();
abstract Command getCommand();
@Test
public void shouldProperlyExecuteCommand() throws TelegramApiException {
Long chatId = 1234567824356L;
Update update = new Update();
Message message = Mockito.mock(Message.class);
Mockito.when(message.getChatId()).thenReturn(chatId);
Mockito.when(message.getText()).thenReturn(getCommandName());
update.setMessage(message);
SendMessage sendMessage = new SendMessage();
sendMessage.setChatId(chatId.toString());
sendMessage.setText(getCommandMessage());
sendMessage.enableHtml(true);
getCommand().execute(update);
Mockito.verify(javarushBot).execute(sendMessage);
}
}
Как видим, у нас есть три абстрактных метода, после определения которых у каждой команды должен запуститься тест, который здесь написан, и выполнится правильно. Это такой удобный подход, когда основная логика находится в абстрактном классе, а вот детали определяются в наследниках. А вот, собственно, реализации конкретных тестов:
HelpCommandTest:
package com.github.javarushcommunity.jrtb.command;
import org.junit.jupiter.api.DisplayName;
import static com.github.javarushcommunity.jrtb.command.CommandName.HELP;
import static com.github.javarushcommunity.jrtb.command.HelpCommand.HELP_MESSAGE;
@DisplayName("Unit-level testing for HelpCommand")
public class HelpCommandTest extends AbstractCommandTest {
@Override
String getCommandName() {
return HELP.getCommandName();
}
@Override
String getCommandMessage() {
return HELP_MESSAGE;
}
@Override
Command getCommand() {
return new HelpCommand(sendBotMessageService);
}
}
NoCommandTest:
package com.github.javarushcommunity.jrtb.command;
import org.junit.jupiter.api.DisplayName;
import static com.github.javarushcommunity.jrtb.command.CommandName.NO;
import static com.github.javarushcommunity.jrtb.command.NoCommand.NO_MESSAGE;
@DisplayName("Unit-level testing for NoCommand")
public class NoCommandTest extends AbstractCommandTest {
@Override
String getCommandName() {
return NO.getCommandName();
}
@Override
String getCommandMessage() {
return NO_MESSAGE;
}
@Override
Command getCommand() {
return new NoCommand(sendBotMessageService);
}
}
StartCommandTest:
package com.github.javarushcommunity.jrtb.command;
import org.junit.jupiter.api.DisplayName;
import static com.github.javarushcommunity.jrtb.command.CommandName.START;
import static com.github.javarushcommunity.jrtb.command.StartCommand.START_MESSAGE;
@DisplayName("Unit-level testing for StartCommand")
class StartCommandTest extends AbstractCommandTest {
@Override
String getCommandName() {
return START.getCommandName();
}
@Override
String getCommandMessage() {
return START_MESSAGE;
}
@Override
Command getCommand() {
return new StartCommand(sendBotMessageService);
}
}
StopCommandTest:
package com.github.javarushcommunity.jrtb.command;
import org.junit.jupiter.api.DisplayName;
import static com.github.javarushcommunity.jrtb.command.CommandName.STOP;
import static com.github.javarushcommunity.jrtb.command.StopCommand.STOP_MESSAGE;
@DisplayName("Unit-level testing for StopCommand")
public class StopCommandTest extends AbstractCommandTest {
@Override
String getCommandName() {
return STOP.getCommandName();
}
@Override
String getCommandMessage() {
return STOP_MESSAGE;
}
@Override
Command getCommand() {
return new StopCommand(sendBotMessageService);
}
}
UnknownCommandTest:
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 мы получor в итоге простые и понятные тесты, которые легко писать, легко понимать. В довесок избавorсь от лишнего дублирования codeа (привет принципу DRY -> Don’t Repeat Yourself). К тому же теперь у нас есть настоящие тесты, по которым можно судить о работе applications. Еще хорошо бы написать тест на самого бота, но там все так просто не получится и вообще, может, игра не стоит свеч, How говорится. Поэтому на данном этапе будем завершать нашу задачу. Последнее и любимое — создаем коммит, пишет сообщение:
JRTB-3: added Command pattern for handling Telegram Bot commands И How обычно — гитхаб уже знает и предлагает создать пулл-реквест:
Билд прошел и можно уже мержить… Но нет! Я ж забыл обновить версию проекта и записать в RELEASE_NOTES. Добавляем запись с новой версией — 0.2.0-SNAPSHOT:
Обновляем эту версию в pom.xml и создаем новый коммит:
Новый коммит:
JRTB-3: updated RELEASE_NOTES.mdТеперь пуш и ждем пока пройдет билд. Билд прошел, можно и мержить:
Ветку я не удаляю, так что всегда можно будет посмотреть и сравнить, что изменилось. Наша доска с задачами обновилась:
Выводы
Сегодня мы сделали большое дело: внедрor Command шаблон для работы. Все настроено, и теперь добавление новой команды будет простым и понятным процессом. Также сегодня сегодня поговорor о тестировании. Немного даже поигрались с тем, чтобы не повторять code в разных тестах для команд.
Традиционно предлагаю зарегистрироваться на GitHub и подписаться на мой аккаунт, чтобы следить за этой серией и другими проектами, которые я там веду. Также я создал телеграм-канал, в котором буду дублировать выход новых статей. Из интересного — code обычно выходит на неделю раньше самой статьи, и на канале я буду каждый раз писать о том, что новая задача сделана, что даст возможность разобраться с codeом до прочтения статьи. В скором времени я опубликую бота на постоянной основе, и первым узнают об этом те, кто подписан на телеграмм канал ;) Всем спасибо за прочтение, продолжение следует.
GO TO FULL VERSION