JavaRush /Blog Java /Random-ES /Implementemos el patrón de comando para trabajar con el b...

Implementemos el patrón de comando para trabajar con el bot. (Parte 2) - "Proyecto Java de la A a la Z"

Publicado en el grupo Random-ES

Escribimos pruebas para la aplicación.

Inicio del artículo: redacción JRTB-3 . Ahora tenemos que pensar en las pruebas. Todo el código agregado debe cubrirse con pruebas para que podamos estar seguros de que la funcionalidad funciona como esperamos. Primero escribiremos pruebas unitarias para el servicio SendBotMessageService.
Una prueba unitaria es una prueba que prueba la lógica de una pequeña parte de la aplicación: normalmente son métodos. Y todas las conexiones que tienen este método se reemplazan por otras falsas mediante simulacros.
Ahora lo verás todo. En el mismo paquete, solo que en la carpeta ./src/test/java , creamos una clase con el mismo nombre que la clase que probaremos y agregamos Test al final . Es decir, para SendBotMessageService tendremos SendBotMessageServiceTest , que contendrá todas las pruebas para esta clase. La idea al probarlo es la siguiente: insertamos un JavaRushTelegarmBot simulado (falso), al que luego preguntamos si el método de ejecución fue llamado con tal argumento o no. Esto es lo que pasó:
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 Mockito, creé un objeto JavaRushBot simulado, que pasé al constructor de nuestro servicio. A continuación, escribí una prueba (cada método con la anotación Prueba es una prueba independiente). La estructura de este método es siempre la misma: no acepta argumentos y devuelve void. El nombre de la prueba debería indicarle lo que estamos probando. En nuestro caso, esto es: debe enviar el mensaje correctamente - debe enviar el mensaje correctamente. Nuestra prueba se divide en tres partes:
  • bloque //dado - donde preparamos todo lo necesario para la prueba;
  • bloquear //cuando - donde lanzamos el método que planeamos probar;
  • // luego bloquear: donde verificamos si el método funcionó correctamente.
Dado que la lógica de nuestro servicio hasta ahora es simple, una prueba para esta clase será suficiente. Ahora escribamos una prueba 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());
   }
}
Esta no es una prueba muy obvia. Se basa en la lógica del contenedor. Todos los comandos que admite el bot están en la lista CommandName y deben estar en el contenedor. Entonces tomé todas las variables CommandName, fui a Stream API y para cada una busqué un comando del contenedor. Si no existiera tal comando, se devolvería UnknownCommand. Esto es lo que comprobamos en esta línea:
Assertions.assertNotEquals(UnknownCommand.class, command.getClass());
Y para comprobar que UnknownCommand será el predeterminado, necesita una prueba por separado: deberíaReturnUnknownCommand . Te aconsejo que reescribas y analices estas pruebas. Por ahora habrá pruebas semiformales para equipos, pero es necesario escribirlas. La lógica será la misma que para probar SendBotMessageService, por lo que trasladaré la lógica de prueba general a la clase AbstractCommandTest, y cada clase de prueba específica se heredará y definirá los campos que necesita. Dado que todas las pruebas son del mismo tipo, escribir lo mismo cada vez no es fácil, además esto no es señal de un buen código. Así resultó la clase abstracta generalizada:
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 puedes ver, tenemos tres métodos abstractos, luego de definir cuál de cada comando debe ejecutar la prueba que aquí está escrita y ejecutarse correctamente. Este es un enfoque muy conveniente cuando la lógica principal está en una clase abstracta, pero los detalles se definen en los descendientes. Y aquí, de hecho, están las implementaciones de pruebas específicas:

AyudaComandoPrueba:

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

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

Iniciar prueba de comando:

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

Detener prueba de comando:

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

Prueba de comando desconocida:

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);
   }
}
Está claro que el juego valió la pena y gracias a AbstractCommandTest terminamos con pruebas simples y comprensibles que son fáciles de escribir y de entender. Además, nos deshicimos de la duplicación de código innecesaria (hola al principio DRY -> No te repitas). Además, ahora disponemos de pruebas reales mediante las cuales podemos juzgar el rendimiento de la aplicación. También sería bueno escribir una prueba para el bot en sí, pero no todo saldrá tan fácilmente y, en general, tal vez el juego no valga la pena, como dicen. Por lo tanto, en esta etapa completaremos nuestra tarea. Lo último y favorito: creamos una confirmación, escribe el mensaje: JRTB-3: patrón de comando agregado para manejar los comandos de Telegram Bot Y, como de costumbre, Github ya lo sabe y ofrece crear una solicitud de extracción: "Proyecto Java de la A a la Z": Implementación de un patrón de comando para trabajar con un bot.  Parte 2 - 1la compilación pasó y ya puedes fusionarse... ¡Pero no! Olvidé actualizar la versión del proyecto y escribirla en RELEASE_NOTES. Agregamos una entrada con la nueva versión - 0.2.0-SNAPSHOT: "Proyecto Java de la A a la Z": Implementación de un patrón de comando para trabajar con un bot.  Parte 2 - 2Actualizamos esta versión en pom.xml y creamos una nueva confirmación: "Proyecto Java de la A a la Z": Implementación de un patrón de comando para trabajar con un bot.  Parte 2 - 3Nueva confirmación: JRTB-3: RELEASE_NOTES.md actualizado."Proyecto Java de la A a la Z": Implementación de un patrón de comando para trabajar con un bot.  Parte 2 - 4 Ahora presione y espere a que se complete la compilación. La compilación pasó, puedes fusionarla: "Proyecto Java de la A a la Z": Implementación de un patrón de comando para trabajar con un bot.  Parte 2 - 5no voy a eliminar la rama, por lo que siempre puedes mirar y comparar lo que ha cambiado. Nuestro tablero de tareas ha sido actualizado:"Proyecto Java de la A a la Z": Implementación de un patrón de comando para trabajar con un bot.  Parte 2 - 6

conclusiones

Hoy hicimos algo importante: presentamos la plantilla Comando para trabajar. Todo está configurado y ahora agregar un nuevo equipo será un proceso simple y directo. También hablamos sobre las pruebas hoy. Incluso jugamos un poco con no repetir el código en diferentes pruebas por equipos. Como siempre, sugiero registrarse en GitHub y seguir mi cuenta para seguir esta serie y otros proyectos en los que estoy trabajando allí. También creé un canal de Telegram en el que duplicaré la publicación de nuevos artículos. Una cosa interesante es que el código generalmente se publica una semana antes del artículo en sí, y en el canal escribiré cada vez que se complete una nueva tarea, lo que me dará la oportunidad de descifrar el código antes de leer el artículo. Pronto publicaré el bot de forma continua, y aquellos que se suscriban al canal de Telegram serán los primeros en enterarse ;) Gracias a todos por leer, continuará.

Al principio de este artículo encontrará una lista de todos los materiales de la serie.

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