JavaRush /مدونة جافا /Random-AR /دعونا ننفذ نمط الأوامر للعمل مع الروبوت. (الجزء الثاني) -...
Roman Beekeeper
مستوى

دعونا ننفذ نمط الأوامر للعمل مع الروبوت. (الجزء الثاني) - "مشروع جافا من الألف إلى الياء"

نشرت في المجموعة

نكتب اختبارات للتطبيق

بداية المقال: كتابة JRTB-3 . الآن علينا أن نفكر في الاختبار. يجب أن تتم تغطية جميع التعليمات البرمجية المضافة بالاختبارات حتى نتمكن من التأكد من أن الوظيفة تعمل كما نتوقع. أولاً سنكتب اختبارات الوحدة لخدمة SendBotMessageService.
اختبار الوحدة هو اختبار يختبر منطق جزء صغير من التطبيق: عادةً ما تكون هذه طرقًا. ويتم استبدال جميع الاتصالات التي تحتوي على هذه الطريقة بأخرى وهمية باستخدام وهمية.
الآن سوف ترى كل شيء. في نفس الحزمة، فقط في المجلد ./src/test/java ، قمنا بإنشاء فئة بنفس اسم الفئة التي سنختبرها، وأضفنا اختبارًا في النهاية . وهذا يعني أنه بالنسبة لـ 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 وهمي، وقمت بتمريره إلى منشئ خدمتنا. بعد ذلك، كتبت اختبارًا واحدًا (كل طريقة مع التعليق التوضيحي للاختبار هي اختبار منفصل). بنية هذه الطريقة هي نفسها دائمًا - فهي لا تأخذ أي وسيطات وترجع فارغة. يجب أن يخبرك اسم الاختبار بما نختبره. في حالتنا، هذا هو: يجب إرسال الرسالة بشكل صحيح - يجب إرسال الرسالة بشكل صحيح. ينقسم اختبارنا إلى ثلاثة أجزاء:
  • الكتلة // المعطاة - حيث نقوم بإعداد كل ما هو ضروري للاختبار؛
  • كتلة //متى - حيث نطلق الطريقة التي نخطط لاختبارها؛
  • // ثم الحظر - حيث نتحقق مما إذا كانت الطريقة تعمل بشكل صحيح.
وبما أن المنطق في خدمتنا بسيط حتى الآن، فإن اختبار واحد لهذه الفئة سيكون كافيا. لنكتب الآن اختبارًا لـ 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 سيكون الإعداد الافتراضي، فأنت بحاجة إلى اختبار منفصل - mustReturnUnknownCommand . أنصحك بإعادة كتابة هذه الاختبارات وتحليلها. ستكون هناك اختبارات شبه رسمية للفرق في الوقت الحالي، لكن يجب أن تكون كتابية. سيكون المنطق هو نفسه المستخدم في اختبار 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 -> مبدأ عدم تكرار نفسك). بالإضافة إلى ذلك، لدينا الآن اختبارات حقيقية يمكننا من خلالها الحكم على أداء التطبيق. سيكون من الجيد أيضًا كتابة اختبار للروبوت نفسه، لكن كل شيء لن يعمل بهذه السهولة وبشكل عام، ربما لا تستحق اللعبة كل هذا العناء، كما يقولون. لذلك، في هذه المرحلة سوف نكمل مهمتنا. الشيء الأخير والمفضل - نقوم بإنشاء التزام، يكتب الرسالة: 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-проект от А до Я": Реализуем Command Pattern для работы с ботом. Часть 2 - 5أنا لا أحذف الفرع، لذلك يمكنك دائمًا البحث ومقارنة ما تغير. تم تحديث لوحة المهام لدينا:"Java-проект от А до Я": Реализуем Command Pattern для работы с ботом. Часть 2 - 6

الاستنتاجات

لقد فعلنا اليوم شيئًا كبيرًا: قدمنا ​​قالب الأوامر للعمل. تم إعداد كل شيء، والآن ستكون إضافة فريق جديد عملية بسيطة ومباشرة. تحدثنا أيضًا عن الاختبار اليوم. لقد لعبنا قليلاً مع عدم تكرار الكود في اختبارات مختلفة للفرق. كالعادة أقترح التسجيل في GitHub ومتابعة حسابي لمتابعة هذه السلسلة والمشاريع الأخرى التي أعمل عليها هناك. لقد قمت أيضًا بإنشاء قناة برقية سأقوم فيها بتكرار إصدار المقالات الجديدة. الشيء المثير للاهتمام هو أن الكود يتم إصداره عادةً قبل أسبوع من المقالة نفسها، وسأكتب على القناة في كل مرة يتم فيها إكمال مهمة جديدة، مما سيمنحني الفرصة لمعرفة الكود قبل قراءة المقال. قريبا سأقوم بنشر البوت بشكل مستمر، ومن يشترك في قناة التليجرام سيكون أول من يعرف عنه ;) شكرا لكم جميعا على القراءة، يتبع.

توجد قائمة بجميع المواد الموجودة في السلسلة في بداية هذه المقالة.

تعليقات
TO VIEW ALL COMMENTS OR TO MAKE A COMMENT,
GO TO FULL VERSION