JavaRush /จาวาบล็อก /Random-TH /ลองใช้ Command Pattern เพื่อทำงานกับบอท (ตอนที่ 2) - "โคร...
Roman Beekeeper
ระดับ

ลองใช้ Command Pattern เพื่อทำงานกับบอท (ตอนที่ 2) - "โครงการ Java จาก A ถึง Z"

เผยแพร่ในกลุ่ม

เราเขียนการทดสอบสำหรับแอปพลิเคชัน

จุดเริ่มต้นของบทความ : การเขียน 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 ซึ่งฉันส่งต่อไปยังตัวสร้างบริการของเรา ต่อไป ฉันเขียนการทดสอบหนึ่งรายการ (แต่ละวิธีที่มีคำอธิบายประกอบการทดสอบจะเป็นการทดสอบแยกกัน) โครงสร้างของเมธอดนี้จะเหมือนกันเสมอ - ไม่มีการโต้แย้งใด ๆ และคืนค่าเป็นโมฆะ ชื่อการทดสอบควรบอกคุณว่าเรากำลังทดสอบอะไร ในกรณีของเราคือ: ควรส่งข้อความอย่างถูกต้อง - ต้องส่งข้อความอย่างถูกต้อง การทดสอบของเราแบ่งออกเป็นสามส่วน:
  • block //given - ที่เราเตรียมทุกสิ่งที่จำเป็นสำหรับการทดสอบ
  • บล็อก // เมื่อ - ที่เราเปิดตัววิธีการที่เราวางแผนจะทดสอบ
  • //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 รู้อยู่แล้วและเสนอให้สร้างคำขอดึง: "โปรเจ็กต์ 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

ข้อสรุป

วันนี้เราทำสิ่งที่ยิ่งใหญ่: เราได้แนะนำเทมเพลต Command สำหรับการทำงาน ทุกอย่างได้รับการตั้งค่าแล้ว และการเพิ่มทีมใหม่จะเป็นกระบวนการที่เรียบง่ายและตรงไปตรงมา เรายังพูดคุยเกี่ยวกับการทดสอบในวันนี้ เรายังเล่นได้เพียงเล็กน้อยโดยไม่ใช้โค้ดซ้ำในการทดสอบต่างๆ สำหรับทีม ตามปกติ ฉันขอแนะนำให้ลงทะเบียนบน GitHub และติดตามบัญชีของฉันเพื่อติดตามซีรีส์นี้และโปรเจ็กต์อื่น ๆ ที่ฉันกำลังทำอยู่ ฉันยังสร้างช่องทางโทรเลขซึ่งฉันจะทำซ้ำการเผยแพร่บทความใหม่ สิ่งที่น่าสนใจอย่างหนึ่งคือโค้ดมักจะเผยแพร่หนึ่งสัปดาห์ก่อนบทความ และฉันจะเขียนในช่องทุกครั้งที่มีงานใหม่เสร็จสมบูรณ์ ซึ่งจะทำให้ฉันมีโอกาสคิดโค้ดก่อนที่จะอ่านบทความ ในไม่ช้าฉันจะเผยแพร่บอทอย่างต่อเนื่องและผู้ที่สมัครรับข้อมูลช่องโทรเลขจะเป็นคนแรกที่รู้เกี่ยวกับมัน ;) ขอบคุณทุกท่านที่อ่านเพื่อดำเนินการต่อ

รายการเนื้อหาทั้งหมดในซีรีส์นี้อยู่ที่ตอนต้นของบทความนี้

ความคิดเห็น
TO VIEW ALL COMMENTS OR TO MAKE A COMMENT,
GO TO FULL VERSION