JavaRush /Java 博客 /Random-ZH /让我们实现命令模式来与机器人一起工作。(第 2 部分)-“Java 项目从头到尾”
Roman Beekeeper
第 35 级

让我们实现命令模式来与机器人一起工作。(第 2 部分)-“Java 项目从头到尾”

已在 Random-ZH 群组中发布

我们为应用程序编写测试

文章开头:编写JRTB-3。现在我们需要考虑测试。所有添加的代码都应该经过测试,以便我们可以确保功能按我们的预期工作。首先,我们将为 SendBotMessageService 服务编写单元测试。
单元测试是测试应用程序的一小部分逻辑的测试:通常这些是方法。所有具有此方法的连接都会使用模拟替换为假连接。
现在你会看到一切。在同一个包中,仅在./src/test/java文件夹中,我们创建一个与我们将测试的类同名的类,并在末尾添加Test。也就是说,对于SendBotMessageService我们将有SendBotMessageServiceTest,它将包含此类的所有测试。测试它的想法如下:我们放入一个模拟(假)JavaRushTelegarmBot,然后我们询问是否使用这样的参数调用了执行方法。事情是这样的:
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);
   }
}
我使用 Mockito 创建了一个模拟 JavaRushBot 对象,并将其传递给我们服务的构造函数。接下来,我编写了一个测试(带有 Test 注释的每个方法都是一个单独的测试)。此方法的结构始终相同 - 它不带参数并返回 void。测试名称应该告诉您我们正在测试什么。在我们的例子中,这是:应该正确发送消息 - 必须正确发送消息。我们的测试分为三个部分:
  • //给定块 - 我们准备测试所需的一切;
  • block //when - 我们启动计划测试的方法的位置;
  • //then 块 - 我们检查该方法是否正确工作。
由于到目前为止我们服务中的逻辑很简单,因此对此类进行一次测试就足够了。现在让我们为 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());
   }
}
这不是一个非常明显的测试。它依赖于容器的逻辑。机器人支持的所有命令都在 CommandName 列表中,并且必须位于容器中。因此,我获取了所有 CommandName 变量,转到 Stream API,并为每个变量从容器中搜索了一个命令。如果没有这样的命令,则返回 UnknownCommand。这是我们在这一行中检查的内容:
Assertions.assertNotEquals(UnknownCommand.class, command.getClass());
要检查 UnknownCommand 是否为默认命令,您需要一个单独的测试 - shouldReturnUnknownCommand。我建议您重写并分析这些测试。目前将为团队提供半正式测试,但需要编写。逻辑将与测试 SendBotMessageService 相同,因此我将把通用测试逻辑移至 AbstractCommandTest 类中,并且将继承每个特定测试类并定义其所需的字段。由于所有测试都是同一类型,因此每次编写相同的内容并不容易,而且这也不是好代码的标志。这就是广义抽象类的结果:
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);
   }
}
正如您所看到的,我们有三个抽象方法,定义每个命令后应该运行此处编写的测试并正确执行。当主要逻辑位于抽象类中但细节在后代中定义时,这是一种非常方便的方法。事实上,这里是具体测试的实现:

帮助命令测试:

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

无命令测试:

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

开始命令测试:

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

停止命令测试:

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

未知命令测试:

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);
   }
}
很明显,这款游戏是值得的,并且感谢 AbstractCommandTest,我们最终得到了简单易懂的测试,这些测试易于编写且易于理解。此外,我们消除了不必要的代码重复(向 DRY -> Don't Repeat Yourself 原则致敬)。另外,现在我们有真实的测试来判断应用程序的性能。为机器人本身编写一个测试也很好,但一切不会那么容易解决,总的来说,也许游戏得不偿失,正如他们所说。因此,在这个阶段我们将完成我们的任务。最后也是最喜欢的一件事 - 我们创建一个提交,写入消息: JRTB-3:添加了用于处理 Telegram Bot 命令的命令模式 和往常一样 - Github 已经知道并提供创建拉取请求:“Java 项目从头到尾”:实现用于机器人工作的命令模式。 第 2 - 1 部分构建已经通过,您已经可以合并...但是不!我忘记更新项目版本并写在RELEASE_NOTES中。我们添加新版本的条目 - 0.2.0-SNAPSHOT:“Java 项目从头到尾”:实现用于机器人工作的命令模式。 第 2 - 2 部分我们在 pom.xml 中更新此版本并创建一个新提交:“Java 项目从头到尾”:实现用于机器人工作的命令模式。 第 2 - 3 部分新提交:JRTB-3:更新的 RELEASE_NOTES.md“Java 项目从头到尾”:实现用于机器人工作的命令模式。 第 2 - 4 部分现在推送并等待构建完成。构建已经通过,你可以合并它:“Java 项目从头到尾”:实现用于机器人工作的命令模式。 第 2 - 5 部分我不会删除分支,所以你可以随时查看和比较发生了什么变化。我们的任务板已更新:“Java 项目从头到尾”:实现用于机器人工作的命令模式。 第 2 - 6 部分

结论

今天我们做了一件大事:我们引入了工作命令模板。一切都已准备就绪,现在添加新团队将是一个简单明了的过程。我们今天还讨论了测试。我们甚至在团队的不同测试中尝试不重复代码。 与往常一样,我建议在 GitHub 上注册并关注我的帐户,以关注本系列以及我在那里从事的其他项目。我还创建了一个电报频道,我将在其中复制新文章的发布。一个有趣的事情是,代码通常在文章发布前一周发布,每次完成新任务我都会在频道上写,这让我有机会在阅读文章之前弄清楚代码。很快我将持续发布该机器人,那些订阅电报频道的人将是第一个知道它的人;) 感谢大家的阅读,待续。

该系列所有材料的列表位于本文开头。

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