JavaRush /Blogue Java /Random-PT /Vamos implementar o Command Pattern para trabalhar com o ...
Roman Beekeeper
Nível 35

Vamos implementar o Command Pattern para trabalhar com o bot. (Parte 2) - "Projeto Java de A a Z"

Publicado no grupo Random-PT

Escrevemos testes para o aplicativo

Início do artigo: escrevendo JRTB-3 . Agora precisamos pensar em testar. Todo código adicionado deve ser coberto com testes para que possamos ter certeza de que a funcionalidade funciona conforme esperado. Primeiro escreveremos testes unitários para o serviço SendBotMessageService.
Um teste de unidade é um teste que testa a lógica de alguma pequena parte do aplicativo: geralmente são métodos. E todas as conexões que possuem esse método são substituídas por falsas usando mocks.
Agora você verá tudo. No mesmo pacote, apenas na pasta ./src/test/java , criamos uma classe com o mesmo nome da classe que iremos testar, e adicionamos Test no final . Ou seja, para SendBotMessageService teremos SendBotMessageServiceTest , que conterá todos os testes desta classe. A ideia ao testá-lo é a seguinte: inserimos um JavaRushTelegarmBot simulado (falso), que então perguntamos se o método execute foi chamado com tal argumento ou não. Aqui está o que aconteceu:
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);
   }
}
Usando o Mockito, criei um objeto JavaRushBot simulado, que passei para o construtor do nosso serviço. A seguir, escrevi um teste (cada método com a anotação Test é um teste separado). A estrutura deste método é sempre a mesma – não aceita argumentos e retorna nulo. O nome do teste deve informar o que estamos testando. No nosso caso, isto é: deve enviar a mensagem corretamente - deve enviar a mensagem corretamente. Nosso teste é dividido em três partes:
  • bloco //dado - onde preparamos tudo o que é necessário para o teste;
  • bloco //quando - onde lançamos o método que pretendemos testar;
  • //então bloco - onde verificamos se o método funcionou corretamente.
Como a lógica do nosso serviço é simples até agora, um teste para esta classe será suficiente. Agora vamos escrever um teste para 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());
   }
}
Este não é um teste muito óbvio. Depende da lógica do contêiner. Todos os comandos suportados pelo bot estão na lista CommandName e devem estar no contêiner. Então peguei todas as variáveis ​​​​CommandName, fui até a API Stream e para cada uma procurei um comando do container. Se não existisse tal comando, o UnknownCommand seria retornado. Isto é o que verificamos nesta linha:
Assertions.assertNotEquals(UnknownCommand.class, command.getClass());
E para verificar se UnknownCommand será o padrão, você precisa de um teste separado - shouldReturnUnknownCommand . Aconselho você a reescrever e analisar esses testes. Haverá testes semiformais para equipes por enquanto, mas eles precisam ser escritos. A lógica será a mesma do teste SendBotMessageService, então moverei a lógica geral do teste para a classe AbstractCommandTest, e cada classe de teste específica será herdada e definirá os campos necessários. Como todos os testes são do mesmo tipo, escrever sempre a mesma coisa não é fácil, e isso não é sinal de um bom código. Foi assim que a classe abstrata generalizada ficou:
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);
   }
}
Como você pode ver, temos três métodos abstratos, após definir qual cada comando deve executar o teste que está escrito aqui e executar corretamente. Esta é uma abordagem muito conveniente quando a lógica principal está em uma classe abstrata, mas os detalhes são definidos nos descendentes. E aqui estão, de fato, as implementações de testes específicos:

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);
   }
}

Sem Teste de Comando:

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);
   }
}

IniciarCommandTest:

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);
   }
}

PararComandoTeste:

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);
   }
}

Teste de comando desconhecido:

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);
   }
}
É claro que o jogo valeu a pena e, graças ao AbstractCommandTest, obtivemos testes simples e compreensíveis, fáceis de escrever e fáceis de entender. Além disso, nos livramos da duplicação desnecessária de código (olá ao princípio DRY -> Don't Repeat Yourself). Além disso, agora temos testes reais pelos quais podemos avaliar o desempenho da aplicação. Também seria bom escrever um teste para o próprio bot, mas nem tudo vai dar certo e, em geral, talvez o jogo não valha a pena, como dizem. Portanto, nesta fase concluiremos nossa tarefa. A última e favorita coisa - criamos um commit, escreve a mensagem: JRTB-3: adicionado padrão de comando para lidar com comandos do Telegram Bot E como sempre - o Github já sabe e se oferece para criar uma solicitação pull: "Projeto Java de A a Z": Implementando um Padrão de Comando para trabalhar com um bot.  Parte 2 - 1A construção passou e você já pode fundir... Mas não! Esqueci de atualizar a versão do projeto e escrevê-la em RELEASE_NOTES. Adicionamos uma entrada com a nova versão - 0.2.0-SNAPSHOT: "Projeto Java de A a Z": Implementando um Padrão de Comando para trabalhar com um bot.  Parte 2 - 2Atualizamos esta versão em pom.xml e criamos um novo commit: "Projeto Java de A a Z": Implementando um Padrão de Comando para trabalhar com um bot.  Parte 2 - 3Novo commit: JRTB-3: RELEASE_NOTES.md atualizado"Projeto Java de A a Z": Implementando um Padrão de Comando para trabalhar com um bot.  Parte 2 - 4 Agora pressione e aguarde a conclusão da compilação. A compilação passou, você pode mesclá-la: "Projeto Java de A a Z": Implementando um Padrão de Comando para trabalhar com um bot.  Parte 2 - 5não estou excluindo o branch, então você sempre pode ver e comparar o que mudou. Nosso quadro de tarefas foi atualizado:"Projeto Java de A a Z": Implementando um Padrão de Comando para trabalhar com um bot.  Parte 2 - 6

conclusões

Hoje fizemos uma grande coisa: introduzimos o modelo Command para o trabalho. Está tudo configurado e agora adicionar uma nova equipe será um processo simples e direto. Também falamos sobre testes hoje. Até brincamos um pouco em não repetir o código em diferentes testes por equipes. Como de costume, sugiro se cadastrar no GitHub e seguir minha conta para acompanhar esta série e outros projetos em que estou trabalhando por lá. Também criei um canal de telegram no qual irei duplicar o lançamento de novos artigos. Uma coisa interessante é que o código geralmente é lançado uma semana antes do artigo em si, e no canal escreverei toda vez que uma nova tarefa for concluída, o que me dará a oportunidade de descobrir o código antes de ler o artigo. Em breve publicarei o bot de forma contínua, e quem se inscrever no canal do Telegram será o primeiro a saber ;) Obrigado a todos pela leitura, continua.

Uma lista de todos os materiais da série está no início deste artigo.

Comentários
TO VIEW ALL COMMENTS OR TO MAKE A COMMENT,
GO TO FULL VERSION