JavaRush /Java Blog /Random-KO /봇과 함께 작동하도록 명령 패턴을 구현해 보겠습니다. (2부) - "Java 프로젝트 A부터 Z까지"
Roman Beekeeper
레벨 35

봇과 함께 작동하도록 명령 패턴을 구현해 보겠습니다. (2부) - "Java 프로젝트 A부터 Z까지"

Random-KO 그룹에 게시되었습니다

우리는 애플리케이션에 대한 테스트를 작성합니다

기사 시작: 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 block - 메소드가 올바르게 작동하는지 확인합니다.
지금까지 우리 서비스의 로직은 간단했기 때문에 이 클래스에 대한 테스트는 한 번이면 충분합니다. 이제 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은 이미 알고 있고 끌어오기 요청 생성을 제안합니다. "A부터 Z까지의 Java 프로젝트": 봇 작업을 위한 명령 패턴 구현.  파트 2 - 1빌드가 통과되었으며 이미 할 수 있습니다. 병합... 하지만 안돼! 프로젝트 버전을 업데이트하고 RELEASE_NOTES에 작성하는 것을 잊었습니다. 새 버전인 0.2.0-SNAPSHOT 항목을 추가합니다. "A부터 Z까지의 Java 프로젝트": 봇 작업을 위한 명령 패턴 구현.  파트 2 - 2pom.xml에서 이 버전을 업데이트하고 새 커밋을 만듭니다. "A부터 Z까지의 Java 프로젝트": 봇 작업을 위한 명령 패턴 구현.  파트 2 - 3새 커밋: JRTB-3: 업데이트된 RELEASE_NOTES.md"A부터 Z까지의 Java 프로젝트": 봇 작업을 위한 명령 패턴 구현.  파트 2 - 4 이제 푸시하고 빌드가 완료될 때까지 기다립니다. 빌드가 완료되었으므로 병합할 수 있습니다. "A부터 Z까지의 Java 프로젝트": 봇 작업을 위한 명령 패턴 구현.  파트 2 - 5브랜치를 삭제하는 것이 아니므로 항상 변경된 내용을 보고 비교할 수 있습니다. 작업 보드가 업데이트되었습니다:"A부터 Z까지의 Java 프로젝트": 봇 작업을 위한 명령 패턴 구현.  파트 2 - 6

결론

오늘 우리는 중요한 일을 했습니다. 업무용 Command 템플릿을 도입한 것입니다. 모든 것이 설정되었으며 이제 새 팀을 추가하는 과정은 간단하고 간단합니다. 오늘 테스트에 대해서도 이야기했습니다. 우리는 팀을 위한 다양한 테스트에서 코드를 반복하지 않고 약간의 플레이도 했습니다. 평소와 마찬가지로 GitHub에 등록하고 내 계정을 팔로우하여 이 시리즈와 내가 진행 중인 다른 프로젝트를 팔로우하는 것이 좋습니다. 또한 새 기사의 공개를 복제할 전보 채널 도 만들었습니다 . 한 가지 흥미로운 점은 코드가 일반적으로 기사가 나오기 일주일 전에 공개된다는 것입니다. 그리고 채널에서는 새로운 작업이 완료될 때마다 글을 쓰게 되어 기사를 읽기 전에 코드를 알아낼 수 있는 기회를 얻게 됩니다. 곧 지속적으로 봇을 게시할 예정이며, 텔레그램 채널을 구독하시는 분들이 가장 먼저 이 사실을 알게 될 것입니다. ;) 계속 읽어주셔서 감사합니다.

시리즈의 모든 자료 목록은 이 기사의 시작 부분에 있습니다.

코멘트
TO VIEW ALL COMMENTS OR TO MAKE A COMMENT,
GO TO FULL VERSION