JavaRush /Java Blog /Random-TL /Ipatupad natin ang Command Pattern upang gumana sa bot. (...

Ipatupad natin ang Command Pattern upang gumana sa bot. (Bahagi 2) - "Proyekto ng Java mula A hanggang Z"

Nai-publish sa grupo

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 {
       //given
       String chatId = "test_chat_id";
       String message = "test_message";

       SendMessage sendMessage = new SendMessage();
       sendMessage.setText(message);
       sendMessage.setChatId(chatId);
       sendMessage.enableHtml(true);

       //when
       sendBotMessageService.sendMessage(chatId, message);

       //then
       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() {
       //when-then
       Arrays.stream(CommandName.values())
               .forEach(commandName -> {
                   Command command = commandContainer.retrieveCommand(commandName.getCommandName());
                   Assertions.assertNotEquals(UnknownCommand.class, command.getClass());
               });
   }

   @Test
   public void shouldReturnUnknownCommand() {
       //given
       String unknownCommand = "/fgjhdfgdfg";

       //when
       Command command = commandContainer.retrieveCommand(unknownCommand);

       //then
       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 for testing {@link Command}s.
*/
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 {
       //given
       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);

       //when
       getCommand().execute(update);

       //then
       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 обычно — гитхаб уже знает и предлагает создать пулл-реквест:"Java-проект от А до Я": Реализуем Command Pattern для работы с ботом. Часть 2 - 1Билд прошел и можно уже мержить… Но нет! Я ж забыл обновить версию проекта и записать в RELEASE_NOTES. Добавляем запись с новой версией — 0.2.0-SNAPSHOT:"Java-проект от А до Я": Реализуем Command Pattern для работы с ботом. Часть 2 - 2Обновляем эту версию в pom.xml и создаем новый коммит:"Java-проект от А до Я": Реализуем Command Pattern для работы с ботом. Часть 2 - 3Новый коммит: JRTB-3: updated RELEASE_NOTES.md"Java-проект от А до Я": Реализуем Command Pattern для работы с ботом. Часть 2 - 4Теперь пуш и ждем пока пройдет билд. Билд прошел, можно и мержить:"Java-проект от А до Я": Реализуем Command Pattern для работы с ботом. Часть 2 - 5Ветку я не удаляю, так что всегда можно будет посмотреть и сравнить, что изменилось. Наша доска с задачами обновилась:"Java-проект от А до Я": Реализуем Command Pattern для работы с ботом. Часть 2 - 6

Выводы

Сегодня мы сделали большое дело: внедрor Command шаблон для работы. Все настроено, и теперь добавление новой команды будет простым и понятным процессом. Также сегодня сегодня поговорor о тестировании. Немного даже поигрались с тем, чтобы не повторять code в разных тестах для команд. Традиционно предлагаю зарегистрироваться на GitHub и подписаться на мой аккаунт, чтобы следить за этой серией и другими проектами, которые я там веду. Также я создал телеграм-канал, в котором буду дублировать выход новых статей. Из интересного — code обычно выходит на неделю раньше самой статьи, и на канале я буду каждый раз писать о том, что новая задача сделана, что даст возможность разобраться с codeом до прочтения статьи. В скором времени я опубликую бота на постоянной основе, и первым узнают об этом те, кто подписан на телеграмм канал ;) Всем спасибо за прочтение, продолжение следует.

Список всех материалов серии в начале этой статьи.

Mga komento
TO VIEW ALL COMMENTS OR TO MAKE A COMMENT,
GO TO FULL VERSION