JavaRush /Java Blog /Random-IT /Implementiamo il modello di comando per lavorare con il b...
Roman Beekeeper
Livello 35

Implementiamo il modello di comando per lavorare con il bot. (Parte 2) - "Progetto Java dalla A alla Z"

Pubblicato nel gruppo Random-IT

Scriviamo test per l'applicazione

Inizio dell'articolo: scrivere JRTB-3 . Ora bisogna pensare ai test. Tutto il codice aggiunto dovrebbe essere coperto da test in modo da poter essere sicuri che la funzionalità funzioni come previsto. Per prima cosa scriveremo unit test per il servizio SendBotMessageService.
Uno unit test è un test che testa la logica di qualche piccola parte dell'applicazione: solitamente si tratta di metodi. E tutte le connessioni che utilizzano questo metodo vengono sostituite con false utilizzando mock.
Ora vedrai tutto. Nello stesso pacchetto, solo nella cartella ./src/test/java , creiamo una classe con lo stesso nome della classe che testeremo, e aggiungiamo alla fine Test . Cioè, per SendBotMessageService avremo SendBotMessageServiceTest , che conterrà tutti i test per questa classe. L'idea nel testarlo è la seguente: inseriamo un finto (falso) JavaRushTelegarmBot, al quale poi chiediamo se il metodo di esecuzione è stato chiamato con tale argomento o meno. Ecco cosa è successo:
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);
   }
}
Utilizzando Mockito, ho creato un finto oggetto JavaRushBot, che ho passato al costruttore del nostro servizio. Successivamente, ho scritto un test (ciascun metodo con l'annotazione Test è un test separato). La struttura di questo metodo è sempre la stessa: non accetta argomenti e restituisce void. Il nome del test dovrebbe dirti cosa stiamo testando. Nel nostro caso, questo è: deve inviare correttamente il messaggio - deve inviare il messaggio correttamente. Il nostro test è diviso in tre parti:
  • blocco //dato - dove prepariamo tutto il necessario per il test;
  • block //when - dove lanciamo il metodo che intendiamo testare;
  • //quindi blocca - dove controlliamo se il metodo ha funzionato correttamente.
Poiché la logica del nostro servizio finora è semplice, sarà sufficiente un test per questa classe. Ora scriviamo un test per 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());
   }
}
Questo non è un test molto ovvio. Si basa sulla logica del contenitore. Tutti i comandi supportati dal bot sono nell'elenco CommandName e devono trovarsi nel contenitore. Quindi ho preso tutte le variabili CommandName, sono andato all'API Stream e per ognuna ho cercato un comando dal contenitore. Se non esistesse tale comando, verrebbe restituito UnknownCommand. Questo è ciò che controlliamo in questa riga:
Assertions.assertNotEquals(UnknownCommand.class, command.getClass());
E per verificare che UnknownCommand sia l'impostazione predefinita, è necessario un test separato: ShouldReturnUnknownCommand . Ti consiglio di riscrivere e analizzare questi test. Per ora ci saranno prove semi-formali per le squadre, ma dovranno essere scritte. La logica sarà la stessa del test di SendBotMessageService, quindi sposterò la logica generale del test nella classe AbstractCommandTest e ogni classe di test specifica verrà ereditata e definirà i campi di cui ha bisogno. Dato che tutti i test sono dello stesso tipo, scrivere ogni volta la stessa cosa non è semplice, inoltre questo non è indice di un buon codice. Ecco come è risultata la classe astratta generalizzata:
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);
   }
}
Come puoi vedere, abbiamo tre metodi astratti, dopo aver definito quale ciascun comando deve eseguire il test scritto qui ed eseguirlo correttamente. Questo è un approccio molto conveniente quando la logica principale è in una classe astratta, ma i dettagli sono definiti nei discendenti. Ed ecco, infatti, le implementazioni di test specifici:

AiutoComandoTest:

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

NessunCommandTest:

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

Test comando sconosciuto:

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);
   }
}
È chiaro che il gioco vale la candela e grazie a AbstractCommandTest ci siamo ritrovati con test semplici e comprensibili, facili da scrivere e facili da comprendere. Inoltre, abbiamo eliminato la duplicazione non necessaria del codice (ciao al principio DRY -> Non ripeterti). Inoltre, ora disponiamo di test reali grazie ai quali possiamo giudicare le prestazioni dell'applicazione. Sarebbe bello anche scrivere un test per il bot stesso, ma non funzionerà tutto così facilmente e in generale forse il gioco non vale la candela, come si suol dire. Pertanto, in questa fase completeremo il nostro compito. L'ultima e preferita cosa: creiamo un commit, scrive il messaggio: JRTB-3: aggiunto modello di comando per la gestione dei comandi di Telegram Bot E come al solito - Github sa già e si offre di creare una richiesta pull: "Progetto Java dalla A alla Z": implementazione di un modello di comando per lavorare con un bot.  Parte 2 - 1la build è passata e puoi già unisci... Ma no! Ho dimenticato di aggiornare la versione del progetto e di scriverla in RELEASE_NOTES. Aggiungiamo una voce con la nuova versione - 0.2.0-SNAPSHOT: "Progetto Java dalla A alla Z": implementazione di un modello di comando per lavorare con un bot.  Parte 2 - 2Aggiorniamo questa versione in pom.xml e creiamo un nuovo commit: "Progetto Java dalla A alla Z": implementazione di un modello di comando per lavorare con un bot.  Parte 2 - 3Nuovo commit: JRTB-3: aggiornato RELEASE_NOTES.md"Progetto Java dalla A alla Z": implementazione di un modello di comando per lavorare con un bot.  Parte 2 - 4 Ora esegui il push e attendi il completamento della build. La build è passata, puoi unirla: "Progetto Java dalla A alla Z": implementazione di un modello di comando per lavorare con un bot.  Parte 2 - 5non elimino il ramo, quindi puoi sempre guardare e confrontare cosa è cambiato. La nostra task board è stata aggiornata:"Progetto Java dalla A alla Z": implementazione di un modello di comando per lavorare con un bot.  Parte 2 - 6

conclusioni

Oggi abbiamo fatto una cosa importante: abbiamo introdotto il modello Command per il lavoro. Tutto è pronto e ora aggiungere una nuova squadra sarà un processo semplice e diretto. Oggi abbiamo parlato anche di test. Abbiamo anche giocato un po' a non ripetere il codice in diversi test per squadre. Come al solito, suggerisco di registrarsi su GitHub e di seguire il mio account per seguire questa serie e altri progetti a cui sto lavorando lì. Ho anche creato un canale Telegram in cui duplicherò l'uscita di nuovi articoli. Una cosa interessante è che il codice viene solitamente rilasciato una settimana prima dell'articolo stesso, e sul canale scriverò ogni volta che verrà completata una nuova attività, il che mi darà l'opportunità di capire il codice prima di leggere l'articolo. Presto pubblicherò il bot in modo continuativo e chi si iscrive al canale Telegram sarà il primo a saperlo ;) Grazie a tutti per aver letto, continua.

Un elenco di tutti i materiali della serie si trova all'inizio di questo articolo.

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