JavaRush /Java Blog /Random-JA /ボットを操作するコマンド パターンを実装しましょう。(パート 2) - 「Java プロジェクトの A から Z ...
Roman Beekeeper
レベル 35

ボットを操作するコマンド パターンを実装しましょう。(パート 2) - 「Java プロジェクトの A から Z まで」

Random-JA グループに公開済み

アプリケーションのテストを作成します

記事の始まり: JRTB-3 の作成。次に、テストについて考える必要があります。機能が期待どおりに動作することを確認できるように、追加されたすべてのコードをテストする必要があります。まず、SendBotMessageService サービスの単体テストを作成します。
単体テストは、アプリケーションの小さな部分のロジックをテストするテストです。通常、これらはメソッドです。そして、このメソッドを持つすべての接続は、モックを使用した偽の接続に置き換えられます。
これですべてがわかります。同じパッケージ内の./src/test/javaフォルダー内にのみ、テストするクラスと同じ名前のクラスを作成し、最後にTestを追加します。つまり、SendBotMessageServiceにはSendBotMessageServiceTestがあり、これにはこのクラスのすべてのテストが含まれます。テストの考え方は次のとおりです。モック (偽) JavaRushTelegarmBot を挿入し、execute メソッドがそのような引数で呼び出されたかどうかを尋ねます。何が起こったかは次のとおりです。
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 オブジェクトを作成し、それをサービスのコンストラクターに渡しました。次に、テストを 1 つ作成しました (Test アノテーションが付いている各メソッドは別個のテストです)。このメソッドの構造は常に同じです。引数をとらず、void を返します。テスト名から、何をテストしているのかがわかります。私たちの場合、これは次のとおりです: メッセージを正しく送信する必要があります - メッセージを正しく送信する必要があります。私たちのテストは 3 つの部分に分かれています。
  • block //given - ここでテストに必要なものをすべて準備します。
  • block //when - テストする予定のメソッドを起動する場所。
  • //その後ブロック - メソッドが正しく動作したかどうかを確認します。
私たちのサービスのロジックはこれまでのところ単純であるため、このクラスのテストは 1 つで十分です。次に、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);
   }
}
ご覧のとおり、ここに書かれているテストを実行する各コマンドを定義した後、3 つの抽象メソッドがあり、正しく実行されます。これは、メイン ロジックが抽象クラス内にあるものの、詳細は子孫で定義される場合に非常に便利なアプローチです。実際、ここに特定のテストの実装があります。

ヘルプコマンドテスト:

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

開始コマンドテスト:

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'trepeat Yourself 原則に従う)。さらに、アプリケーションのパフォーマンスを判断できる実際のテストが行​​われるようになりました。ボット自体のテストを作成することもできますが、すべてがそう簡単にうまくいくわけではありません。一般的に、彼らが言うように、おそらくゲームはろうそくの価値がないかもしれません。したがって、この段階でタスクを完了します。最後でお気に入りのこと - コミットを作成し、メッセージを書き込みます: JRTB-3: Telegram Bot コマンドを処理するためのコマンド パターンを追加しました そしていつものように - Github はすでにプル リクエストの作成を認識しており、プル リクエストの作成を提案しています:「Java プロジェクトの A から Z まで」: ボットを操作するためのコマンド パターンを実装します。 パート 2 - 1ビルドは完了しており、すでに作成できますマージ...しかし、違います! プロジェクトのバージョンを更新してRELEASE_NOTESに書くのを忘れていました。新しいバージョン - 0.2.0-SNAPSHOT のエントリを追加します「Java プロジェクトの A から Z まで」: ボットを操作するためのコマンド パターンを実装します。 パート 2 - 2。pom.xml でこのバージョンを更新し、新しいコミットを作成します。「Java プロジェクトの A から Z まで」: ボットを操作するためのコマンド パターンを実装します。 パート 2 ~ 3新しいコミット: JRTB-3: RELEASE_NOTES.md を更新しまし「Java プロジェクトの A から Z まで」: ボットを操作するためのコマンド パターンを実装します。 パート 2 ~ 4た。次に、プッシュしてビルドが完了するのを待ちます。ビルドは完了したので、マージできます。「Java プロジェクトの A から Z まで」: ボットを操作するためのコマンド パターンを実装します。 パート 2 ~ 5ブランチは削除していないので、いつでも変更内容を確認して比較できます。タスクボードが更新されました:「Java プロジェクトの A から Z まで」: ボットを操作するためのコマンド パターンを実装します。 パート 2 ~ 6

結論

今日、私たちは大きなことを行いました。仕事用のコマンド テンプレートを導入しました。すべての設定が完了したので、新しいチームの追加はシンプルで簡単なプロセスになります。今日はテストについても話しました。チームのさまざまなテストでコードを繰り返さないように少し試してみたりしました。 いつものように、GitHub に登録し、私のアカウントをフォローして、このシリーズや私がそこで取り組んでいる他のプロジェクトをフォローすることをお勧めします。新しい記事のリリースを複製する電報チャンネルも作成しました。興味深い点の 1 つは、コードは通常、記事自体の 1 週間前に公開され、新しいタスクが完了するたびにチャンネルに書き込むため、記事を読む前にコードを理解する機会が与えられることです。間もなく、このボットを継続的に公開する予定です。電報チャンネルに登録している人が最初にそのことを知るでしょう ;) 読んでいただきありがとうございます。

シリーズのすべてのマテリアルのリストは、この記事の冒頭にあります。

コメント
TO VIEW ALL COMMENTS OR TO MAKE A COMMENT,
GO TO FULL VERSION