เราเขียนการทดสอบสำหรับแอปพลิเคชัน จุดเริ่มต้นของบทความ :
การเขียน 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