JavaRush /Java-Blog /Random-DE /Lassen Sie uns das Befehlsmuster implementieren, um mit d...

Lassen Sie uns das Befehlsmuster implementieren, um mit dem Bot zu arbeiten. (Teil 2) - „Java-Projekt von A bis Z“

Veröffentlicht in der Gruppe Random-DE

Wir schreiben Tests für die Anwendung

Anfang des Artikels: JRTB-3 schreiben . Jetzt müssen wir über das Testen nachdenken. Der gesamte hinzugefügte Code sollte mit Tests abgedeckt werden, damit wir sicher sein können, dass die Funktionalität wie erwartet funktioniert. Zuerst schreiben wir Unit-Tests für den SendBotMessageService-Dienst.
Ein Unit-Test ist ein Test, der die Logik eines kleinen Teils der Anwendung testet: Normalerweise handelt es sich dabei um Methoden. Und alle Verbindungen, die über diese Methode verfügen, werden mithilfe von Mocks durch gefälschte ersetzt.
Jetzt werden Sie alles sehen. Im selben Paket, nur im Ordner ./src/test/java , erstellen wir eine Klasse mit demselben Namen wie die Klasse, die wir testen werden, und fügen am Ende Test hinzu . Das heißt, für SendBotMessageService haben wir SendBotMessageServiceTest , das alle Tests für diese Klasse enthält. Die Idee beim Testen ist folgende: Wir fügen einen simulierten (gefälschten) JavaRushTelegarmBot ein, den wir dann fragen, ob die Ausführungsmethode mit einem solchen Argument aufgerufen wurde oder nicht. Folgendes ist passiert:
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);
   }
}
Mit Mockito habe ich ein simuliertes JavaRushBot-Objekt erstellt, das ich an den Konstruktor unseres Dienstes übergeben habe. Als nächstes habe ich einen Test geschrieben (jede Methode mit der Test-Annotation ist ein separater Test). Die Struktur dieser Methode ist immer gleich – sie akzeptiert keine Argumente und gibt void zurück. Der Testname sollte Ihnen sagen, was wir testen. In unserem Fall ist dies: sollte die Nachricht korrekt senden – muss die Nachricht korrekt senden. Unser Test gliedert sich in drei Teile:
  • Block //gegeben – wo wir alles Notwendige für den Test vorbereiten;
  • block //when – wo wir die Methode starten, die wir testen möchten;
  • //dann Block – wo wir prüfen, ob die Methode korrekt funktioniert hat.
Da die Logik in unserem Dienst bisher einfach ist, reicht ein Test für diese Klasse aus. Schreiben wir nun einen Test für 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());
   }
}
Dies ist kein sehr offensichtlicher Test. Es basiert auf der Logik des Containers. Alle vom Bot unterstützten Befehle befinden sich in der CommandName-Liste und müssen sich im Container befinden. Also nahm ich alle CommandName-Variablen, ging zur Stream-API und suchte für jede einzelne nach einem Befehl aus dem Container. Wenn kein solcher Befehl vorhanden wäre, würde UnknownCommand zurückgegeben. Das überprüfen wir in dieser Zeile:
Assertions.assertNotEquals(UnknownCommand.class, command.getClass());
Und um zu überprüfen, ob UnknownCommand die Standardeinstellung ist, benötigen Sie einen separaten Test – ShouldReturnUnknownCommand . Ich rate Ihnen, diese Tests neu zu schreiben und zu analysieren. Es wird vorerst halbformelle Tests für Teams geben, diese müssen jedoch schriftlich festgehalten werden. Die Logik ist dieselbe wie beim Testen von SendBotMessageService, daher werde ich die allgemeine Testlogik in die Klasse AbstractCommandTest verschieben und jede spezifische Testklasse wird geerbt und definiert die benötigten Felder. Da alle Tests vom gleichen Typ sind, ist es nicht einfach, jedes Mal dasselbe zu schreiben, und das ist kein Zeichen für guten Code. So entstand die verallgemeinerte abstrakte Klasse:
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);
   }
}
Wie Sie sehen können, haben wir drei abstrakte Methoden, nachdem wir definiert haben, welche Befehle den hier geschriebenen Test ausführen und korrekt ausführen sollen. Dies ist ein sehr praktischer Ansatz, wenn die Hauptlogik in einer abstrakten Klasse liegt, die Details jedoch in den Nachkommen definiert werden. Und hier sind tatsächlich die Implementierungen spezifischer Tests:

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);
   }
}
Es ist klar, dass sich das Spiel gelohnt hat, und dank AbstractCommandTest haben wir am Ende einfache und verständliche Tests erhalten, die leicht zu schreiben und leicht zu verstehen sind. Darüber hinaus haben wir unnötige Codeduplizierungen beseitigt (Hallo zum DRY -> Don't Repeat Yourself-Prinzip). Darüber hinaus verfügen wir jetzt über echte Tests, anhand derer wir die Leistung der Anwendung beurteilen können. Es wäre auch schön, einen Test für den Bot selbst zu schreiben, aber alles wird nicht so einfach klappen und im Allgemeinen ist das Spiel vielleicht nicht die Kerze wert, wie sie sagen. Daher werden wir in dieser Phase unsere Aufgabe abschließen. Die letzte und beliebteste Sache – wir erstellen einen Commit, schreiben die Nachricht: JRTB-3: Befehlsmuster für die Handhabung von Telegram-Bot-Befehlen hinzugefügt Und wie üblich – Github weiß es bereits und bietet an, einen Pull-Request zu erstellen: „Java-Projekt von A bis Z“: Implementierung eines Befehlsmusters für die Arbeit mit einem Bot.  Teil 2 - 1Der Build ist bestanden und Sie können es bereits tun verschmelzen... Aber nein! Ich habe vergessen, die Projektversion zu aktualisieren und in RELEASE_NOTES zu schreiben. Wir fügen einen Eintrag mit der neuen Version hinzu – 0.2.0-SNAPSHOT: „Java-Projekt von A bis Z“: Implementierung eines Befehlsmusters für die Arbeit mit einem Bot.  Teil 2 - 2Wir aktualisieren diese Version in pom.xml und erstellen einen neuen Commit: „Java-Projekt von A bis Z“: Implementierung eines Befehlsmusters für die Arbeit mit einem Bot.  Teil 2 - 3Neuer Commit: JRTB-3: aktualisierte RELEASE_NOTES.md„Java-Projekt von A bis Z“: Implementierung eines Befehlsmusters für die Arbeit mit einem Bot.  Teil 2 - 4 Drücken Sie nun und warten Sie, bis der Build abgeschlossen ist. Der Build ist erfolgreich, Sie können ihn zusammenführen: „Java-Projekt von A bis Z“: Implementierung eines Befehlsmusters für die Arbeit mit einem Bot.  Teil 2 - 5Ich lösche den Zweig nicht, sodass Sie jederzeit nachsehen und vergleichen können, was sich geändert hat. Unser Taskboard wurde aktualisiert:„Java-Projekt von A bis Z“: Implementierung eines Befehlsmusters für die Arbeit mit einem Bot.  Teil 2 - 6

Schlussfolgerungen

Heute haben wir eine große Sache gemacht: Wir haben die Command-Vorlage für die Arbeit eingeführt. Alles ist eingerichtet und das Hinzufügen eines neuen Teams ist nun ein einfacher und unkomplizierter Vorgang. Wir haben heute auch über das Testen gesprochen. Wir haben sogar ein wenig damit gespielt, den Code in verschiedenen Tests für Teams nicht zu wiederholen. Wie üblich schlage ich vor, mich auf GitHub zu registrieren und meinem Konto zu folgen, um diese Serie und andere Projekte, an denen ich dort arbeite, zu verfolgen. Ich habe auch einen Telegram-Kanal erstellt , in dem ich die Veröffentlichung neuer Artikel duplizieren werde. Interessant ist, dass der Code normalerweise eine Woche vor dem eigentlichen Artikel veröffentlicht wird und ich auf dem Kanal jedes Mal schreibe, wenn eine neue Aufgabe erledigt wurde, was mir die Möglichkeit gibt, den Code herauszufinden, bevor ich den Artikel lese. Bald werde ich den Bot fortlaufend veröffentlichen und diejenigen, die den Telegram-Kanal abonnieren, werden als Erste davon erfahren ;) Vielen Dank an alle fürs Lesen, Fortsetzung folgt.

Eine Liste aller Materialien der Serie finden Sie am Anfang dieses Artikels.

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