JavaRush /وبلاگ جاوا /Random-FA /بیایید الگوی فرمان را برای کار با ربات پیاده سازی کنیم. (...
Roman Beekeeper
مرحله

بیایید الگوی فرمان را برای کار با ربات پیاده سازی کنیم. (قسمت 2) - "پروژه جاوا از A تا Z"

در گروه منتشر شد

ما برای برنامه تست می نویسیم

آغاز مقاله: نوشتن 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 ایجاد کردم که آن را به سازنده سرویس ما منتقل کردم. بعد، یک تست نوشتم (هر روش با حاشیه نویسی Test یک تست جداگانه است). ساختار این روش همیشه یکسان است - هیچ آرگومان نمی‌گیرد و void برمی‌گرداند. نام آزمون باید به شما بگوید که ما چه چیزی را آزمایش می کنیم. در مورد ما، این است: باید به درستی پیام ارسال شود - باید پیام را به درستی ارسال کند. آزمون ما به سه بخش تقسیم می شود:
  • block //given - جایی که ما همه چیز لازم را برای آزمایش آماده می کنیم.
  • 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);
   }
}
همانطور که می بینید ما سه روش انتزاعی داریم که بعد از تعریف هر دستور باید تستی که در اینجا نوشته شده را اجرا کرده و به درستی اجرا شود. زمانی که منطق اصلی در یک کلاس انتزاعی است، اما جزئیات در نوادگان تعریف می‌شوند، این یک رویکرد راحت است. و در واقع در اینجا پیاده سازی تست های خاص وجود دارد:

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);
   }
}
واضح است که بازی ارزش شمع را داشت و به لطف AbstractCommandTest ما به تست‌های ساده و قابل فهمی رسیدیم که نوشتن و درک آن آسان است. علاوه بر این، از تکرار کدهای غیر ضروری خلاص شدیم (سلام به اصل DRY -> Don't Repeat Yourself). علاوه بر این، اکنون ما تست های واقعی داریم که می توانیم با استفاده از آنها عملکرد برنامه را قضاوت کنیم. همچنین نوشتن یک تست برای خود ربات خوب است، اما همه چیز به این راحتی و به طور کلی انجام نمی شود، شاید بازی به قول آنها ارزش شمع را نداشته باشد. بنابراین در این مرحله وظیفه خود را تکمیل خواهیم کرد. آخرین مورد و مورد علاقه - ما یک commit ایجاد می کنیم، این پیام را می نویسد: JRTB-3: الگوی فرمان اضافه شده برای مدیریت دستورات ربات تلگرام و طبق معمول - Github قبلاً می داند و پیشنهاد ایجاد یک درخواست کشش را می دهد: "پروژه جاوا از A تا Z": پیاده سازی یک الگوی فرمان برای کار با یک ربات.  قسمت 2 - 1بیلد گذشته است و شما می توانید قبلاً ادغام ... اما نه! فراموش کردم نسخه پروژه را به روز کنم و در RELEASE_NOTES بنویسم. ما یک ورودی با نسخه جدید اضافه می کنیم - 0.2.0-SNAPSHOT: "پروژه جاوا از A تا Z": پیاده سازی یک الگوی فرمان برای کار با یک ربات.  قسمت 2 - 2ما این نسخه را در pom.xml به روز می کنیم و یک commit جدید ایجاد می کنیم: "پروژه جاوا از A تا Z": پیاده سازی یک الگوی فرمان برای کار با یک ربات.  قسمت 2 - 3commit جدید: JRTB-3: به روز شده RELEASE_NOTES.md"پروژه جاوا از A تا Z": پیاده سازی یک الگوی فرمان برای کار با یک ربات.  قسمت 2 - 4 اکنون فشار دهید و منتظر بمانید تا ساخت کامل شود. ساختن گذشته است، می‌توانید آن را ادغام کنید: "Java-проект от А до Я": Реализуем Command Pattern для работы с ботом. Часть 2 - 5من شاخه را حذف نمی‌کنم، بنابراین همیشه می‌توانید آنچه را تغییر کرده است نگاه کنید و مقایسه کنید. صفحه کار ما به روز شده است:"Java-проект от А до Я": Реализуем Command Pattern для работы с ботом. Часть 2 - 6

نتیجه گیری

امروز یک کار بزرگ انجام دادیم: قالب Command را برای کار معرفی کردیم. همه چیز آماده است و اکنون افزودن یک تیم جدید یک فرآیند ساده و سرراست خواهد بود. امروز در مورد تست هم صحبت کردیم. ما حتی کمی با تکرار نکردن کد در تست های مختلف برای تیم ها بازی کردیم. طبق معمول، پیشنهاد می کنم در GitHub ثبت نام کنید و حساب کاربری خود را دنبال کنید تا این مجموعه و پروژه های دیگری را که در آنجا کار می کنم دنبال کنید. همچنین یک کانال تلگرامی ایجاد کردم که در آن انتشار مقالات جدید را کپی خواهم کرد. یک چیز جالب این است که کد معمولا یک هفته قبل از خود مقاله منتشر می شود و هر بار که یک کار جدید انجام شده است در کانال می نویسم که این فرصت را به من می دهد تا قبل از خواندن مقاله کد را بفهمم. به زودی ربات را به صورت مداوم منتشر خواهم کرد و کسانی که در کانال تلگرام عضو می شوند اولین کسانی هستند که از آن مطلع می شوند ;) از همه شما سپاسگزارم که مطالعه می کنید ادامه دارد.

فهرستی از تمام مواد این مجموعه در ابتدای این مقاله است.

نظرات
TO VIEW ALL COMMENTS OR TO MAKE A COMMENT,
GO TO FULL VERSION