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 {
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);
}
}
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() {
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());
}
}
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 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);
}
}
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:
A 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:
Atualizamos esta versão em pom.xml e criamos um novo commit:
Novo commit:
JRTB-3: RELEASE_NOTES.md atualizado Agora pressione e aguarde a conclusão da compilação. A compilação passou, você pode mesclá-la:
não estou excluindo o branch, então você sempre pode ver e comparar o que mudou. Nosso quadro de tarefas foi atualizado:
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.
GO TO FULL VERSION