ما برای برنامه تست می نویسیم
آغاز مقاله:
نوشتن 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 {
String chatId = "test_chat_id";
String message = "test_message";
SendMessage sendMessage = new SendMessage();
sendMessage.setText(message);
sendMessage.setChatId(chatId);
sendMessage.enableHtml(true);
sendBotMessageService.sendMessage(chatId, message);
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() {
Arrays.stream(CommandName.values())
.forEach(commandName -> {
Command command = commandContainer.retrieveCommand(commandName.getCommandName());
Assertions.assertNotEquals(UnknownCommand.class, command.getClass());
});
}
@Test
public void shouldReturnUnknownCommand() {
String unknownCommand = "/fgjhdfgdfg";
Command command = commandContainer.retrieveCommand(unknownCommand);
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 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 {
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);
getCommand().execute(update);
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 قبلاً می داند و پیشنهاد ایجاد یک درخواست کشش را می دهد:
بیلد گذشته است و شما می توانید قبلاً ادغام ... اما نه! فراموش کردم نسخه پروژه را به روز کنم و در RELEASE_NOTES بنویسم. ما یک ورودی با نسخه جدید اضافه می کنیم - 0.2.0-SNAPSHOT:
ما این نسخه را در pom.xml به روز می کنیم و یک commit جدید ایجاد می کنیم:
commit جدید:
JRTB-3: به روز شده RELEASE_NOTES.md اکنون فشار دهید و منتظر بمانید تا ساخت کامل شود. ساختن گذشته است، میتوانید آن را ادغام کنید:
من شاخه را حذف نمیکنم، بنابراین همیشه میتوانید آنچه را تغییر کرده است نگاه کنید و مقایسه کنید. صفحه کار ما به روز شده است:
نتیجه گیری
امروز یک کار بزرگ انجام دادیم: قالب Command را برای کار معرفی کردیم. همه چیز آماده است و اکنون افزودن یک تیم جدید یک فرآیند ساده و سرراست خواهد بود. امروز در مورد تست هم صحبت کردیم. ما حتی کمی با تکرار نکردن کد در تست های مختلف برای تیم ها بازی کردیم.
طبق معمول، پیشنهاد می کنم در GitHub ثبت نام کنید و حساب کاربری خود را دنبال کنید تا این مجموعه و پروژه های دیگری را که در آنجا کار می کنم دنبال کنید. همچنین یک کانال تلگرامی ایجاد کردم که در آن انتشار مقالات جدید را کپی خواهم کرد. یک چیز جالب این است که کد معمولا یک هفته قبل از خود مقاله منتشر می شود و هر بار که یک کار جدید انجام شده است در کانال می نویسم که این فرصت را به من می دهد تا قبل از خواندن مقاله کد را بفهمم. به زودی ربات را به صورت مداوم منتشر خواهم کرد و کسانی که در کانال تلگرام عضو می شوند اولین کسانی هستند که از آن مطلع می شوند ;) از همه شما سپاسگزارم که مطالعه می کنید ادامه دارد.
GO TO FULL VERSION