เราเขียนการทดสอบสำหรับแอปพลิเคชัน
จุดเริ่มต้นของบทความ :
การเขียน 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 {
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 ซึ่งฉันส่งต่อไปยังตัวสร้างบริการของเรา ต่อไป ฉันเขียนการทดสอบหนึ่งรายการ (แต่ละวิธีที่มีคำอธิบายประกอบการทดสอบจะเป็นการทดสอบแยกกัน) โครงสร้างของเมธอดนี้จะเหมือนกันเสมอ - ไม่มีการโต้แย้งใด ๆ และคืนค่าเป็นโมฆะ ชื่อการทดสอบควรบอกคุณว่าเรากำลังทดสอบอะไร ในกรณีของเราคือ: ควรส่งข้อความอย่างถูกต้อง - ต้องส่งข้อความอย่างถูกต้อง การทดสอบของเราแบ่งออกเป็นสามส่วน:
- 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() {
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);
}
}
อย่างที่คุณเห็น เรามีวิธีนามธรรมสามวิธี หลังจากกำหนดว่าแต่ละคำสั่งควรรันการทดสอบที่เขียนไว้ที่นี่และดำเนินการอย่างถูกต้อง นี่เป็นแนวทางที่สะดวกมากเมื่อตรรกะหลักอยู่ในคลาสนามธรรม แต่รายละเอียดถูกกำหนดไว้ในรายการสืบทอด และนี่คือการใช้งานการทดสอบเฉพาะ:
ช่วยเหลือทดสอบคำสั่ง:
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 รู้อยู่แล้วและเสนอให้สร้างคำขอดึง:
บิลด์ผ่านไปแล้วและคุณสามารถทำได้แล้ว รวม...แต่ไม่! ฉันลืมอัปเดตเวอร์ชันโปรเจ็กต์และเขียนไว้ใน RELEASE_NOTES เราเพิ่มรายการด้วยเวอร์ชันใหม่ - 0.2.0-SNAPSHOT:
เราอัปเดตเวอร์ชันนี้ใน pom.xml และสร้างการคอมมิตใหม่:
การคอมมิตใหม่:
JRTB-3: อัปเดต RELEASE_NOTES.mdตอนนี้กดและรอให้การสร้างเสร็จสมบูรณ์ บิลด์ผ่านไปแล้ว คุณสามารถรวมเข้าด้วยกันได้:
ฉันไม่ได้ลบสาขา ดังนั้นคุณจึงสามารถดูและเปรียบเทียบสิ่งที่เปลี่ยนแปลงได้ตลอดเวลา กระดานงานของเราได้รับการอัปเดต:
ข้อสรุป
วันนี้เราทำสิ่งที่ยิ่งใหญ่: เราได้แนะนำเทมเพลต Command สำหรับการทำงาน ทุกอย่างได้รับการตั้งค่าแล้ว และการเพิ่มทีมใหม่จะเป็นกระบวนการที่เรียบง่ายและตรงไปตรงมา เรายังพูดคุยเกี่ยวกับการทดสอบในวันนี้ เรายังเล่นได้เพียงเล็กน้อยโดยไม่ใช้โค้ดซ้ำในการทดสอบต่างๆ สำหรับทีม
ตามปกติ ฉันขอแนะนำให้ลงทะเบียนบน GitHub และติดตามบัญชีของฉันเพื่อติดตามซีรีส์นี้และโปรเจ็กต์อื่น ๆ ที่ฉันกำลังทำอยู่ ฉันยังสร้างช่องทางโทรเลขซึ่งฉันจะทำซ้ำการเผยแพร่บทความใหม่ สิ่งที่น่าสนใจอย่างหนึ่งคือโค้ดมักจะเผยแพร่หนึ่งสัปดาห์ก่อนบทความ และฉันจะเขียนในช่องทุกครั้งที่มีงานใหม่เสร็จสมบูรณ์ ซึ่งจะทำให้ฉันมีโอกาสคิดโค้ดก่อนที่จะอ่านบทความ ในไม่ช้าฉันจะเผยแพร่บอทอย่างต่อเนื่องและผู้ที่สมัครรับข้อมูลช่องโทรเลขจะเป็นคนแรกที่รู้เกี่ยวกับมัน ;) ขอบคุณทุกท่านที่อ่านเพื่อดำเนินการต่อ
GO TO FULL VERSION