JavaRush /Blog Java /Random-FR /Implémentons le modèle de commande pour travailler avec l...
Roman Beekeeper
Niveau 35

Implémentons le modèle de commande pour travailler avec le bot. (Partie 2) - "Projet Java de A à Z"

Publié dans le groupe Random-FR

Nous écrivons des tests pour l'application

Début de l'article : rédaction de JRTB-3 . Il faut maintenant penser aux tests. Tout le code ajouté doit être couvert par des tests afin que nous puissions être sûrs que la fonctionnalité fonctionne comme prévu. Nous allons d’abord écrire des tests unitaires pour le service SendBotMessageService.
Un test unitaire est un test qui teste la logique d'une petite partie de l'application : ce sont généralement des méthodes. Et toutes les connexions utilisant cette méthode sont remplacées par de fausses connexions utilisant des simulations.
Maintenant, vous verrez tout. Dans le même package, uniquement dans le dossier ./src/test/java , nous créons une classe avec le même nom que la classe que nous allons tester, et ajoutons Test à la fin . Autrement dit, pour SendBotMessageService, nous aurons SendBotMessageServiceTest , qui contiendra tous les tests pour cette classe. L'idée du test est la suivante : on glisse un faux (faux) JavaRushTelegarmBot, auquel on demande ensuite si la méthode d'exécution a été appelée avec un tel argument ou non. Voici ce qui s'est passé :
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);
   }
}
À l'aide de Mockito, j'ai créé un objet JavaRushBot fictif, que j'ai transmis au constructeur de notre service. Ensuite, j'ai écrit un test (chaque méthode avec l'annotation Test est un test distinct). La structure de cette méthode est toujours la même : elle ne prend aucun argument et renvoie void. Le nom du test doit vous indiquer ce que nous testons. Dans notre cas, ceci est : doit envoyer correctement le message - doit envoyer le message correctement. Notre test est divisé en trois parties :
  • bloc //donné - où nous préparons tout le nécessaire pour le test ;
  • block //when - où nous lançons la méthode que nous prévoyons de tester ;
  • //puis bloquez - où nous vérifions si la méthode a fonctionné correctement.
Puisque la logique de notre service est jusqu'à présent simple, un seul test pour cette classe suffira. Écrivons maintenant un test pour 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());
   }
}
Ce n'est pas un test très évident. Cela repose sur la logique du conteneur. Toutes les commandes prises en charge par le bot figurent dans la liste CommandName et doivent se trouver dans le conteneur. J'ai donc pris toutes les variables CommandName, je suis allé à l'API Stream et pour chacune j'ai recherché une commande dans le conteneur. S'il n'existait pas de commande de ce type, la UnknownCommand serait renvoyée. C'est ce que l'on vérifie dans cette ligne :
Assertions.assertNotEquals(UnknownCommand.class, command.getClass());
Et pour vérifier que UnknownCommand sera la valeur par défaut, vous avez besoin d'un test distinct - ShouldReturnUnknownCommand . Je vous conseille de réécrire et d'analyser ces tests. Il y aura pour l'instant des tests semi-formels pour les équipes, mais ils doivent être écrits. La logique sera la même que pour tester SendBotMessageService, je vais donc déplacer la logique générale de test dans la classe AbstractCommandTest, et chaque classe de test spécifique sera héritée et définira les champs dont elle a besoin. Puisque tous les tests sont du même type, écrire la même chose à chaque fois n’est pas facile, et ce n’est pas non plus le signe d’un bon code. Voici comment s'est avérée la classe abstraite généralisée :
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);
   }
}
Comme vous pouvez le voir, nous avons trois méthodes abstraites, après avoir défini lesquelles chaque commande doit exécuter le test écrit ici et s'exécuter correctement. C’est une approche très pratique lorsque la logique principale se trouve dans une classe abstraite, mais que les détails sont définis dans les descendants. Et voici en effet les implémentations de tests spécifiques :

AideCommandTest :

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

Pas de test de commande :

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

DémarrerCommandTest :

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

Test de commande inconnu :

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);
   }
}
Il est clair que le jeu en valait la chandelle, et grâce à AbstractCommandTest nous avons abouti à des tests simples et compréhensibles, faciles à écrire et à comprendre. De plus, nous nous sommes débarrassés des duplications de code inutiles (bonjour le principe DRY -> Don't Repeat Yourself). De plus, nous disposons désormais de tests réels grâce auxquels nous pouvons juger des performances de l’application. Ce serait aussi bien d'écrire un test pour le bot lui-même, mais tout ne fonctionnera pas si facilement et en général, peut-être que le jeu n'en vaut pas la chandelle, comme on dit. Par conséquent, à ce stade, nous terminerons notre tâche. La dernière et préférée - nous créons un commit, écrit le message : JRTB-3 : ajout d'un modèle de commande pour gérer les commandes Telegram Bot Et comme d'habitude - Github le sait déjà et propose de créer une pull request : "Projet Java de A à Z" : Implémentation d'un modèle de commande pour travailler avec un bot.  Partie 2 - 1La build est passée et vous pouvez déjà fusionner... Mais non ! J'ai oublié de mettre à jour la version du projet et de l'écrire dans RELEASE_NOTES. Nous ajoutons une entrée avec la nouvelle version - 0.2.0-SNAPSHOT : "Projet Java de A à Z" : Implémentation d'un modèle de commande pour travailler avec un bot.  Partie 2 - 2Nous mettons à jour cette version dans pom.xml et créons un nouveau commit : "Projet Java de A à Z" : Implémentation d'un modèle de commande pour travailler avec un bot.  Partie 2 - 3Nouveau commit : JRTB-3 : RELEASE_NOTES.md mis à jour"Projet Java de A à Z" : Implémentation d'un modèle de commande pour travailler avec un bot.  Partie 2 - 4 Maintenant, poussez et attendez que la construction soit terminée. Le build est réussi, vous pouvez le fusionner : "Projet Java de A à Z" : Implémentation d'un modèle de commande pour travailler avec un bot.  Partie 2 à 5je ne supprime pas la branche, vous pouvez donc toujours regarder et comparer ce qui a changé. Notre tableau des tâches a été mis à jour :"Projet Java de A à Z" : Implémentation d'un modèle de commande pour travailler avec un bot.  Partie 2 à 6

conclusions

Aujourd'hui, nous avons fait une grande chose : nous avons introduit le modèle de commande pour le travail. Tout est mis en place et l’ajout d’une nouvelle équipe sera désormais un processus simple et direct. Nous avons également parlé de tests aujourd'hui. Nous avons même un peu joué à ne pas répéter le code dans différents tests pour les équipes. Comme d'habitude, je suggère de vous inscrire sur GitHub et de suivre mon compte pour suivre cette série et les autres projets sur lesquels je travaille. J'ai également créé une chaîne de télégrammes dans laquelle je dupliquerai la sortie de nouveaux articles. Une chose intéressante est que le code est généralement publié une semaine avant l'article lui-même, et sur la chaîne, j'écrirai chaque fois qu'une nouvelle tâche sera terminée, ce qui me donnera l'occasion de comprendre le code avant de lire l'article. Bientôt, je publierai le bot de manière continue, et ceux qui s'abonnent à la chaîne télégramme seront les premiers informés ;) Merci à tous d'avoir lu, à suivre.

Une liste de tous les matériaux de la série se trouve au début de cet article.

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