สวัสดีทุกคน. ฉันขอเตือนคุณ: ในส่วนแรกเราได้เพิ่ม Flyway มาต่อกัน
หากผลลัพธ์เหมือนกันสำหรับคุณ เราสามารถพูดได้ว่าฟังก์ชันการทำงานถูกต้องและบอททำงานอย่างถูกต้อง หากมีสิ่งผิดปกติเกิดขึ้น ก็ไม่สำคัญ: เราจะรีสตาร์ทวิธีการหลักในโหมดแก้ไขข้อบกพร่อง และดำเนินการตามเส้นทางทั้งหมดอย่างชัดเจนเพื่อค้นหาว่าข้อผิดพลาดคืออะไร
การเพิ่มฐานข้อมูลลงใน docker-compose.yml
ขั้นตอนต่อไปคือการตั้งค่างานกับฐานข้อมูลใน main docker-compose.yml มาเพิ่มฐานข้อมูลลงในไฟล์นักเทียบท่า:version: '3.1'
services:
jrtb-bot:
depends_on:
- jrtb-db
build:
context: .
environment:
BOT_NAME: ${BOT_NAME}
BOT_TOKEN: ${BOT_TOKEN}
BOT_DB_USERNAME: ${BOT_DB_USERNAME}
BOT_DB_PASSWORD: ${BOT_DB_PASSWORD}
restart: always
jrtb-db:
image: mysql:5.7
restart: always
environment:
MYSQL_USER: ${BOT_DB_USERNAME}
MYSQL_PASSWORD: ${BOT_DB_PASSWORD}
MYSQL_DATABASE: 'jrtb_db'
MYSQL_ROOT_PASSWORD: 'root'
ports:
- '3306:3306'
expose:
- '3306'
ฉันยังเพิ่มบรรทัดนี้ในแอปพลิเคชันของเราด้วย:
depends_on:
- jrtb-db
ซึ่งหมายความว่าเรารอให้ฐานข้อมูลเริ่มต้นก่อนที่จะเริ่มแอปพลิเคชัน ต่อไป คุณจะสังเกตเห็นการเพิ่มตัวแปรอีกสองตัวที่เราต้องทำงานกับฐานข้อมูล:
${BOT_DB_USERNAME}
${BOT_DB_PASSWORD}
เราจะนำพวกมันมาใน docker-compose ในลักษณะเดียวกับ telegram bot - ผ่านตัวแปรสภาพแวดล้อม ฉันทำสิ่งนี้เพื่อให้เรามีเพียงที่เดียวที่เราตั้งค่าชื่อผู้ใช้ฐานข้อมูลและรหัสผ่าน เราส่งต่อไปยังอิมเมจนักเทียบท่าของแอปพลิเคชันของเราและไปยังคอนเทนเนอร์นักเทียบท่าของฐานข้อมูลของเรา ต่อไปเราต้องอัปเดต Dockerfile เพื่อสอน SpringBoot ให้ยอมรับตัวแปรสำหรับฐานข้อมูล
FROM adoptopenjdk/openjdk11:ubi
ARG JAR_FILE=target/*.jar
ENV BOT_NAME=test.javarush_community_bot
ENV BOT_TOKEN=1375780501:AAE4A6Rz0BSnIGzeu896OjQnjzsMEG6_uso
ENV BOT_DB_USERNAME=jrtb_db_user
ENV BOT_DB_PASSWORD=jrtb_db_password
COPY ${JAR_FILE} app.jar
ENTRYPOINT ["java","-Dspring.datasource.password=${BOT_DB_PASSWORD}", "-Dbot.username=${BOT_NAME}", "-Dbot.token=${BOT_TOKEN}", "-Dspring.datasource.username=${BOT_DB_USERNAME}", "-jar", "app.jar"]
ตอนนี้เราเพิ่มตัวแปรฐานข้อมูลให้กับ Dockerfile:
ENV BOT_DB_USERNAME=jrtb_db_user
ENV BOT_DB_PASSWORD=jrtb_db_password
ค่าตัวแปรจะแตกต่างกัน อย่างไรก็ตาม สิ่งที่เราจะส่งผ่านไปยัง Dockerfile ต้องใช้ค่าเริ่มต้น ดังนั้นฉันจึงป้อนบางส่วน เราขยายบรรทัดสุดท้ายด้วยสององค์ประกอบ โดยเราจะส่งชื่อผู้ใช้และรหัสผ่าน DB เพื่อเปิดแอปพลิเคชัน:
"-Dspring.datasource.password=${BOT_DB_PASSWORD}", "-Dbot.username=${BOT_NAME}"
บรรทัดสุดท้ายใน Dockerfile (ซึ่งขึ้นต้นด้วย ENTRYPOINT) จะต้องไม่มีการตัดคำ หากคุณทำการโอนเงิน รหัสนี้จะใช้งานไม่ได้ ขั้นตอนสุดท้ายคือการอัพเดต ไฟล์ start.shเพื่อส่งผ่านตัวแปรไปยังฐานข้อมูล
#!/bin/bash
# Pull new changes
git pull
# Prepare Jar
mvn clean
mvn package
# Ensure, that docker-compose stopped
docker-compose stop
# Add environment variables
export BOT_NAME=$1
export BOT_TOKEN=$2
export BOT_DB_USERNAME='prod_jrtb_db_user'
export BOT_DB_PASSWORD='Pap9L9VVUkNYj99GCUCC3mJkb'
# Start new deployment
docker-compose up --build -d
เรารู้วิธีเพิ่มตัวแปรสภาพแวดล้อมก่อนรัน docker-compose แล้ว ในการดำเนินการนี้ คุณเพียงแค่ต้องดำเนินการส่งออก var_name=var_value.. ดังนั้นเราจึงเพิ่มเพียงสองบรรทัด:
export BOT_DB_USERNAME='prod_jrtb_db_user'
export BOT_DB_PASSWORD='Pap9L9VVUkNYj99GCUCC3mJkb'
นี่คือที่ที่เราตั้งชื่อผู้ใช้และรหัสผ่านฐานข้อมูล แน่นอนว่า มันเป็นไปได้ที่จะส่งผ่านตัวแปรเหล่านี้เมื่อรันสคริปต์ทุบตี เช่นเดียวกับที่เราทำกับชื่อและโทเค็นของบอท แต่สำหรับฉันดูเหมือนว่านี่ไม่จำเป็น ในการเข้าถึงฐานข้อมูลจริง คุณจำเป็นต้องทราบ IP ของเซิร์ฟเวอร์ที่จะใช้งานฐานข้อมูล และอยู่ในรายการที่อยู่ IP ที่อนุญาตสำหรับคำขอ สำหรับฉันเท่านี้ก็เพียงพอแล้ว วางรากฐานแล้ว: ตอนนี้คุณสามารถทำสิ่งที่นักพัฒนาเข้าใจได้มากขึ้น - เขียนโค้ด ก่อนหน้านั้น เรากำลังทำสิ่งที่วิศวกร DevOps ทำ นั่นคือการตั้งค่าสภาพแวดล้อม
การเพิ่มชั้นพื้นที่เก็บข้อมูล
โดยทั่วไปแอปพลิเคชันจะมีสามชั้น:- ตัวควบคุมเป็นจุดเริ่มต้นเข้าสู่แอปพลิเคชัน
- บริการคือจุดที่ตรรกะทางธุรกิจทำงาน เรามีสิ่งนี้อยู่แล้วบางส่วน: SendMessageService เป็นตัวแทนที่ชัดเจนของตรรกะทางธุรกิจ
- ที่เก็บเป็นสถานที่ทำงานกับฐานข้อมูล ในกรณีของเรา นี่คือบอทโทรเลข
- เราจะไม่ต้องทำงานกับ JDBC: เราจะทำงานโดยตรงกับสิ่งที่เป็นนามธรรมที่สูงกว่า นั่นคือจัดเก็บ POJO ที่สอดคล้องกับตารางในฐานข้อมูล เราจะเรียกคลาส ดังกล่าวว่าเอนทิตี เนื่องจากถูกเรียกอย่างเป็นทางการในJava Persistence API (นี่คือชุดอินเทอร์เฟซทั่วไปสำหรับการทำงานกับฐานข้อมูลผ่าน ORM นั่นคือสิ่งที่เป็นนามธรรมมากกว่าการทำงานกับ JDBC) เราจะมีคลาสเอนทิตีที่เราจะบันทึกไว้ในฐานข้อมูล และจะถูกเขียนลงในตารางที่เราต้องการ เราจะได้รับวัตถุเดียวกันเมื่อทำการค้นหาในฐานข้อมูล
- Spring Data เสนอให้ใช้ชุดอินเทอร์เฟซ: JpaRepository , CrudRepositoryฯลฯ ... มีอินเทอร์เฟซอื่น ๆ : สามารถดูรายการทั้งหมดได้ที่นี่ สิ่งที่สวยงามคือคุณสามารถใช้วิธีการของพวกเขาได้โดยไม่ต้องนำไปปฏิบัติ(!) นอกจากนี้ยังมีเทมเพลตบางตัวที่คุณสามารถเขียนวิธีการใหม่ในอินเทอร์เฟซได้ และจะมีการใช้งานโดยอัตโนมัติ
- Spring ช่วยให้การพัฒนาของเราง่ายขึ้นมากที่สุด ในการทำเช่นนี้ เราจำเป็นต้องสร้างอินเทอร์เฟซของเราเองและสืบทอดจากที่อธิบายไว้ข้างต้น และเพื่อให้ Spring รู้ว่าจำเป็นต้องใช้อินเทอร์เฟซนี้ ให้เพิ่มคำอธิบายประกอบ Repository
- หากเราจำเป็นต้องเขียนวิธีการทำงานกับฐานข้อมูลที่ไม่มีอยู่นี่ก็ไม่ใช่ปัญหาเช่นกัน - เราจะเขียนมันขึ้นมา ฉันจะแสดงให้คุณเห็นว่าต้องทำอะไรที่นั่น
package com.github.javarushcommunity.jrtb.repository.entity;
import lombok.Data;
import javax.persistence.Column;
import javax.persistence.Entity;
import javax.persistence.Table;
/**
* Telegram User entity.
*/
@Data
@Entity
@Table(name = "tg_user")
public class TelegramUser {
@Id
@Column(name = "chat_id")
private String chatId;
@Column(name = "active")
private boolean active;
}
ที่นี่คุณจะเห็นว่าเรามีคำอธิบายประกอบทั้งหมดจากแพ็คเกจ javax.persistence เหล่านี้เป็นคำอธิบายประกอบทั่วไปที่ใช้สำหรับการใช้งาน ORM ทั้งหมด ตามค่าเริ่มต้น Spring Data Jpa จะใช้ Hibernate แม้ว่าการใช้งานอื่นๆ ก็สามารถใช้ได้ก็ตาม นี่คือรายการคำอธิบายประกอบที่เราใช้:
- เอนทิตี - ระบุว่านี่คือเอนทิตีสำหรับการทำงานกับฐานข้อมูล
- ตาราง - ที่นี่เรากำหนดชื่อของตาราง
- Id - คำอธิบายประกอบบอกว่าฟิลด์ใดจะเป็นคีย์หลักในตาราง
- คอลัมน์ - กำหนดชื่อของฟิลด์จากตาราง
package com.github.javarushcommunity.jrtb.repository;
import com.github.javarushcommunity.jrtb.repository.entity.TelegramUser;
import org.springframework.data.jpa.repository.JpaRepository;
import org.springframework.stereotype.Repository;
import java.util.List;
/**
* {@link Repository} for handling with {@link TelegramUser} entity.
*/
@Repository
public interface TelegramUserRepository extends JpaRepository<TelegramUser, String> {
List<TelegramUser> findAllByActiveTrue();
}
ที่นี่ คุณสามารถดูว่าฉันเพิ่ม เมธอด findAllByActiveTrue()ซึ่งฉันไม่ได้นำไปใช้ที่ไหนเลย แต่นั่นจะไม่ทำให้เขาหยุดทำงาน Spring Data จะเข้าใจว่าจำเป็นต้องรับบันทึกทั้งหมดจากตาราง tg_user ซึ่งมีactive field = true เราเพิ่มบริการสำหรับการทำงานกับเอนทิตี TelegramUser (เราใช้การผกผันการพึ่งพาจาก SOLID ในบริบทที่บริการของเอนทิตีอื่นไม่สามารถสื่อสารโดยตรงกับที่เก็บของเอนทิตีอื่น - ผ่านบริการของเอนทิตีนั้นเท่านั้น) เราสร้างบริการ TelegramUserService ในแพ็คเกจซึ่งตอนนี้จะมีหลายวิธี: บันทึกผู้ใช้ รับผู้ใช้ด้วย ID ของเขา และแสดงรายการผู้ใช้ที่ใช้งานอยู่ ขั้นแรกเราสร้างอินเทอร์เฟซ TelegramUserService:
package com.github.javarushcommunity.jrtb.service;
import com.github.javarushcommunity.jrtb.repository.entity.TelegramUser;
import org.springframework.stereotype.Service;
import java.util.List;
import java.util.Optional;
/**
* {@link Service} for handling {@link TelegramUser} entity.
*/
public interface TelegramUserService {
/**
* Save provided {@link TelegramUser} entity.
*
* @param telegramUser provided telegram user.
*/
void save(TelegramUser telegramUser);
/**
* Retrieve all active {@link TelegramUser}.
*
* @return the collection of the active {@link TelegramUser} objects.
*/
List<TelegramUser> retrieveAllActiveUsers();
/**
* Find {@link TelegramUser} by chatId.
*
* @param chatId provided Chat ID
* @return {@link TelegramUser} with provided chat ID or null otherwise.
*/
Optional<TelegramUser> findByChatId(String chatId);
}
และในความเป็นจริงแล้ว การใช้งาน TelegramUserServiceImpl:
package com.github.javarushcommunity.jrtb.service;
import com.github.javarushcommunity.jrtb.repository.TelegramUserRepository;
import com.github.javarushcommunity.jrtb.repository.entity.TelegramUser;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
import java.util.List;
import java.util.Optional;
/**
* Implementation of {@link TelegramUserService}.
*/
@Service
public class TelegramUserServiceImpl implements TelegramUserService {
private final TelegramUserRepository telegramUserRepository;
@Autowired
public TelegramUserServiceImpl(TelegramUserRepository telegramUserRepository) {
this.telegramUserRepository = telegramUserRepository;
}
@Override
public void save(TelegramUser telegramUser) {
telegramUserRepository.save(telegramUser);
}
@Override
public List<TelegramUser> retrieveAllActiveUsers() {
return telegramUserRepository.findAllByActiveTrue();
}
@Override
public Optional<TelegramUser> findByChatId(String chatId) {
return telegramUserRepository.findById(chatId);
}
}
ควรสังเกตว่าเราใช้การพึ่งพาการฉีด (แนะนำคลาสอินสแตนซ์) ของวัตถุ TelegramuserRepository โดยใช้ คำอธิบาย ประกอบ Autowiredและบนตัวสร้าง คุณสามารถทำเช่นนี้กับตัวแปรได้ แต่นี่คือแนวทางที่ทีมงาน Spring Framework แนะนำให้เรา
เพิ่มสถิติให้กับบอท
ถัดไปคุณจะต้องอัปเดตคำสั่ง /start และ /stop เมื่อใช้คำสั่ง /start คุณจะต้องบันทึกผู้ใช้ใหม่ในฐานข้อมูลและตั้งค่าเป็น active = true และเมื่อมี /stop ให้อัพเดตข้อมูลผู้ใช้: set active = false มาแก้ไข คลาสStartCommand :package com.github.javarushcommunity.jrtb.command;
import com.github.javarushcommunity.jrtb.repository.entity.TelegramUser;
import com.github.javarushcommunity.jrtb.service.SendBotMessageService;
import com.github.javarushcommunity.jrtb.service.TelegramUserService;
import org.telegram.telegrambots.meta.api.objects.Update;
/**
* Start {@link Command}.
*/
public class StartCommand implements Command {
private final SendBotMessageService sendBotMessageService;
private final TelegramUserService telegramUserService;
public final static String START_MESSAGE = "Привет. Я Javarush Telegram Bot. Я помогу тебе быть в курсе последних " +
"статей тех авторов, котрые тебе интересны. Я еще маленький и только учусь.";
public StartCommand(SendBotMessageService sendBotMessageService, TelegramUserService telegramUserService) {
this.sendBotMessageService = sendBotMessageService;
this.telegramUserService = telegramUserService;
}
@Override
public void execute(Update update) {
String chatId = update.getMessage().getChatId().toString();
telegramUserService.findByChatId(chatId).ifPresentOrElse(
user -> {
user.setActive(true);
telegramUserService.save(user);
},
() -> {
TelegramUser telegramUser = new TelegramUser();
telegramUser.setActive(true);
telegramUser.setChatId(chatId);
telegramUserService.save(telegramUser);
});
sendBotMessageService.sendMessage(chatId, START_MESSAGE);
}
}
ที่นี่เรายังส่งวัตถุ TelegramuserService ไปยัง Constructor ซึ่งเราจะบันทึกผู้ใช้ใหม่ นอกจากนี้ การใช้ความยินดีของ Optional ใน Java ตรรกะต่อไปนี้ใช้งานได้: หากเรามีผู้ใช้ในฐานข้อมูล เราก็ทำให้เขาแอคทีฟ หากไม่มี เราก็สร้างแอคทีฟใหม่ขึ้นมา คำสั่งหยุด:
package com.github.javarushcommunity.jrtb.command;
import com.github.javarushcommunity.jrtb.repository.entity.TelegramUser;
import com.github.javarushcommunity.jrtb.service.SendBotMessageService;
import com.github.javarushcommunity.jrtb.service.TelegramUserService;
import org.telegram.telegrambots.meta.api.objects.Update;
import java.util.Optional;
/**
* Stop {@link Command}.
*/
public class StopCommand implements Command {
private final SendBotMessageService sendBotMessageService;
private final TelegramUserService telegramUserService;
public static final String STOP_MESSAGE = "Деактивировал все ваши подписки \uD83D\uDE1F.";
public StopCommand(SendBotMessageService sendBotMessageService, TelegramUserService telegramUserService) {
this.sendBotMessageService = sendBotMessageService;
this.telegramUserService = telegramUserService;
}
@Override
public void execute(Update update) {
sendBotMessageService.sendMessage(update.getMessage().getChatId().toString(), STOP_MESSAGE);
telegramUserService.findByChatId(update.getMessage().getChatId().toString())
.ifPresent(it -> {
it.setActive(false);
telegramUserService.save(it);
});
}
}
เราส่ง TelegramServiceTest ไปยัง StopCommand ในลักษณะเดียวกัน ตรรกะเพิ่มเติมคือ: หากเรามีผู้ใช้ที่มี ID แชท เราจะปิดใช้งานมัน นั่นคือ เราตั้งค่า active = false คุณจะเห็นสิ่งนี้ด้วยตาของคุณเองได้อย่างไร? มาสร้างคำสั่งใหม่ /stat ซึ่งจะแสดงสถิติของบอท ในขั้นตอนนี้ สิ่งเหล่านี้จะเป็นสถิติง่ายๆ ที่ผู้ใช้ทุกคนสามารถใช้ได้ ในอนาคต เราจะจำกัดให้เข้าถึงได้เฉพาะผู้ดูแลระบบเท่านั้น จะมีหนึ่งรายการในสถิติ: จำนวนผู้ใช้บอทที่ใช้งานอยู่ เมื่อต้องการทำเช่นนี้ ให้เพิ่มค่าSTAT("/stat")ลงใน CommandName จากนั้นสร้าง คลาส StatCommand :
package com.github.javarushcommunity.jrtb.command;
import com.github.javarushcommunity.jrtb.service.SendBotMessageService;
import com.github.javarushcommunity.jrtb.service.TelegramUserService;
import org.springframework.beans.factory.annotation.Autowired;
import org.telegram.telegrambots.meta.api.objects.Update;
/**
* Statistics {@link Command}.
*/
public class StatCommand implements Command {
private final TelegramUserService telegramUserService;
private final SendBotMessageService sendBotMessageService;
public final static String STAT_MESSAGE = "Javarush Telegram Bot использует %s человек.";
@Autowired
public StatCommand(SendBotMessageService sendBotMessageService, TelegramUserService telegramUserService) {
this.sendBotMessageService = sendBotMessageService;
this.telegramUserService = telegramUserService;
}
@Override
public void execute(Update update) {
int activeUserCount = telegramUserService.retrieveAllActiveUsers().size();
sendBotMessageService.sendMessage(update.getMessage().getChatId().toString(), String.format(STAT_MESSAGE, activeUserCount));
}
}
ทุกอย่างเป็นเรื่องง่ายที่นี่: เราได้รับรายชื่อผู้ใช้ที่ใช้งานอยู่ทั้งหมดโดยใช้ วิธี ดึงข้อมูล AllActiveUsersและรับขนาดของคอลเลกชัน ตอนนี้เราจำเป็นต้องอัปเดตคลาสจากน้อยไปหามาก: CommandContainerและJavarushTelegramBotเพื่อให้คลาสเรียนรู้ที่จะส่งบริการใหม่ที่เราต้องการ คอนเทนเนอร์คำสั่ง:
package com.github.javarushcommunity.jrtb.command;
import com.github.javarushcommunity.jrtb.service.SendBotMessageService;
import com.github.javarushcommunity.jrtb.service.TelegramUserService;
import com.google.common.collect.ImmutableMap;
import static com.github.javarushcommunity.jrtb.command.CommandName.*;
/**
* Container of the {@link Command}s, which are using for handling telegram commands.
*/
public class CommandContainer {
private final ImmutableMap<String, Command> commandMap;
private final Command unknownCommand;
public CommandContainer(SendBotMessageService sendBotMessageService, TelegramUserService telegramUserService) {
commandMap = ImmutableMap.<String, Command>builder()
.put(START.getCommandName(), new StartCommand(sendBotMessageService, telegramUserService))
.put(STOP.getCommandName(), new StopCommand(sendBotMessageService, telegramUserService))
.put(HELP.getCommandName(), new HelpCommand(sendBotMessageService))
.put(NO.getCommandName(), new NoCommand(sendBotMessageService))
.put(STAT.getCommandName(), new StatCommand(sendBotMessageService, telegramUserService))
.build();
unknownCommand = new UnknownCommand(sendBotMessageService);
}
public Command retrieveCommand(String commandIdentifier) {
return commandMap.getOrDefault(commandIdentifier, unknownCommand);
}
}
ที่นี่เราได้เพิ่มคำสั่งใหม่ลงในแผนที่และส่งผ่านตัวสร้าง TelegramUserService แต่ในตัวบอทเอง มีเพียง Constructor เท่านั้นที่จะเปลี่ยนแปลง:
@Autowired
public JavarushTelegramBot(TelegramUserService telegramUserService) {
this.commandContainer = new CommandContainer(new SendBotMessageServiceImpl(this), telegramUserService);
}
ตอนนี้เราส่ง TelegramUserService เป็นอาร์กิวเมนต์ โดยเพิ่มคำอธิบายประกอบแบบ Autowired ซึ่งหมายความว่าเราจะได้รับจากบริบทของแอปพลิเคชัน นอกจากนี้เรายังจะอัปเดต คลาส HelpCommandเพื่อให้คำสั่งสถิติใหม่ปรากฏในคำอธิบาย
การทดสอบด้วยตนเอง
มาเปิดตัวฐานข้อมูลจาก docker-compose-test.yml และวิธีการหลักในคลาส JavarushTelegramBotApplication ต่อไปเราจะเขียนชุดคำสั่ง:- /stat - เราคาดว่าหากฐานข้อมูลว่างเปล่า จะไม่มีผู้ใช้บอทนี้
- /start - เริ่มบอท;
- /stat - ตอนนี้เราคาดว่าบอทจะมีคนใช้ 1 คน
- /stop - หยุดบอท;
- /stat - เราคาดว่าจะมีคนใช้มัน 0 คนอีกครั้ง

เราเขียนและอัปเดตการทดสอบ
เนื่องจากเราเปลี่ยนตัวสร้าง เราจะต้องอัปเดตคลาสการทดสอบด้วย ใน คลาส AbstractCommandTestเราจำเป็นต้องเพิ่มอีกหนึ่งฟิลด์ - คลาส TelegramUserServiceซึ่งจำเป็นสำหรับสามคำสั่ง:protected TelegramUserService telegramUserService = Mockito.mock(TelegramUserService.class);
ต่อไป มาอัปเดตเมธอด init() ใน CommandContainer :
@BeforeEach
public void init() {
SendBotMessageService sendBotMessageService = Mockito.mock(SendBotMessageService.class);
TelegramUserService telegramUserService = Mockito.mock(TelegramUserService.class);
commandContainer = new CommandContainer(sendBotMessageService, telegramUserService);
}
ใน StartCommand คุณต้องอัปเดตเมธอดgetCommand() :
@Override
Command getCommand() {
return new StartCommand(sendBotMessageService, telegramUserService);
}
นอกจากนี้ใน StopCommand:
@Override
Command getCommand() {
return new StopCommand(sendBotMessageService, telegramUserService);
}
ต่อไปเรามาดูการทดสอบใหม่กัน มาสร้างการทดสอบทั่วไปสำหรับStatCommand :
package com.github.javarushcommunity.jrtb.command;
import static com.github.javarushcommunity.jrtb.command.CommandName.STAT;
import static com.github.javarushcommunity.jrtb.command.StatCommand.STAT_MESSAGE;
public class StatCommandTest extends AbstractCommandTest {
@Override
String getCommandName() {
return STAT.getCommandName();
}
@Override
String getCommandMessage() {
return String.format(STAT_MESSAGE, 0);
}
@Override
Command getCommand() {
return new StatCommand(sendBotMessageService, telegramUserService);
}
}
นี่เป็นเรื่องง่าย ตอนนี้เรามาพูดถึงวิธีที่เราจะทดสอบการทำงานกับฐานข้อมูล สิ่งที่เราทำก่อนหน้านี้คือการทดสอบหน่วย การทดสอบการรวมระบบจะทดสอบการรวมระหว่างหลายส่วนของแอปพลิเคชัน เช่น แอปพลิเคชันและฐานข้อมูล ที่นี่ทุกอย่างจะซับซ้อนมากขึ้นเพราะสำหรับการทดสอบเราจำเป็นต้องมีฐานข้อมูลที่ปรับใช้ ดังนั้นเมื่อเรารันการทดสอบในเครื่อง เราจะต้องมีฐานข้อมูลที่ทำงานจาก docker-compose-test.yml หากต้องการดำเนินการทดสอบนี้ คุณจะต้องเรียกใช้แอปพลิเคชัน SpringBoot ทั้งหมด คลาสทดสอบมี คำอธิบายประกอบ SpringBootTestที่จะเริ่มแอปพลิเคชัน แต่วิธีนี้ใช้ไม่ได้ผลสำหรับเรา เพราะเมื่อแอปพลิเคชันเปิดตัว บอทโทรเลขก็จะเปิดตัวเช่นกัน แต่มีความขัดแย้งที่นี่ การทดสอบจะดำเนินการทั้งภายในเครื่องของเราและแบบสาธารณะผ่าน GitHub Actions เพื่อให้การทดสอบผ่านการทดสอบพร้อมกับการเปิดตัวแอปพลิเคชันทั้งหมด เราต้องรันการทดสอบด้วยข้อมูลที่ถูกต้องบนบอตโทรเลข: นั่นคือตามชื่อและโทเค็น... ดังนั้นเราจึงมีสองตัวเลือก:
- ดังนั้นให้ตั้งชื่อและโทเค็นของบอทต่อสาธารณะและหวังว่าทุกอย่างจะเรียบร้อยดี จะไม่มีใครใช้มันมายุ่งเกี่ยวกับเรา
- คิดอีกวิธีหนึ่ง
@Sql(scripts = {"/sql/clearDbs.sql", "/sql/telegram_users.sql"})
สำหรับเรา พวกเขาจะตั้งอยู่ตามเส้นทาง ./src/test/resources/ + เส้นทางที่ระบุในคำอธิบายประกอบ นี่คือลักษณะที่ปรากฏ:
clearDbs.sql:
DELETE FROM tg_user;
telegram_users.sql:
INSERT INTO tg_user VALUES ("123456789", 1);
INSERT INTO tg_user VALUES ("123456788", 1);
INSERT INTO tg_user VALUES ("123456787", 1);
INSERT INTO tg_user VALUES ("123456786", 1);
INSERT INTO tg_user VALUES ("123456785", 1);
INSERT INTO tg_user VALUES ("123456784", 0);
INSERT INTO tg_user VALUES ("123456782", 0);
INSERT INTO tg_user VALUES ("123456781", 0);
นี่คือลักษณะการทดสอบ TelegramUserRepositoryIT ของเรา (ดังที่คุณเห็น ชื่อของการทดสอบการรวมระบบจะแตกต่างออกไป - เราเพิ่มไอที ไม่ใช่การทดสอบ):
package com.github.javarushcommunity.jrtb.repository;
import com.github.javarushcommunity.jrtb.repository.entity.TelegramUser;
import org.junit.jupiter.api.Assertions;
import org.junit.jupiter.api.Test;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.test.autoconfigure.jdbc.AutoConfigureTestDatabase;
import org.springframework.boot.test.autoconfigure.orm.jpa.DataJpaTest;
import org.springframework.test.context.ActiveProfiles;
import org.springframework.test.context.jdbc.Sql;
import java.util.List;
import java.util.Optional;
import static org.springframework.boot.test.autoconfigure.jdbc.AutoConfigureTestDatabase.Replace.NONE;
/**
* Integration-level testing for {@link TelegramUserRepository}.
*/
@ActiveProfiles("test")
@DataJpaTest
@AutoConfigureTestDatabase(replace = NONE)
public class TelegramUserRepositoryIT {
@Autowired
private TelegramUserRepository telegramUserRepository;
@Sql(scripts = {"/sql/clearDbs.sql", "/sql/telegram_users.sql"})
@Test
public void shouldProperlyFindAllActiveUsers() {
//when
List<TelegramUser> users = telegramUserRepository.findAllByActiveTrue();
//then
Assertions.assertEquals(5, users.size());
}
@Sql(scripts = {"/sql/clearDbs.sql"})
@Test
public void shouldProperlySaveTelegramUser() {
//given
TelegramUser telegramUser = new TelegramUser();
telegramUser.setChatId("1234567890");
telegramUser.setActive(false);
telegramUserRepository.save(telegramUser);
//when
Optional<TelegramUser> saved = telegramUserRepository.findById(telegramUser.getChatId());
//then
Assertions.assertTrue(saved.isPresent());
Assertions.assertEquals(telegramUser, saved.get());
}
}
เราเขียนการทดสอบ แต่มีคำถามเกิดขึ้น: จะเกิดอะไรขึ้นกับการเปิดตัวกระบวนการ CI ของเราบน GitHub มันจะไม่มีฐานข้อมูล สำหรับตอนนี้จะมีเพียงโครงสร้างสีแดงเท่านั้น ในการดำเนินการนี้ เรามีการดำเนินการของ GitHub ซึ่งเราสามารถกำหนดค่าการเปิดตัวบิลด์ของเราได้ ก่อนที่จะรันการทดสอบ คุณต้องเพิ่มการเปิดใช้ฐานข้อมูลด้วยการตั้งค่าที่จำเป็น ปรากฎว่ามีตัวอย่างไม่มากนักบนอินเทอร์เน็ต ดังนั้นฉันขอแนะนำให้คุณบันทึกสิ่งนี้ไว้ที่ใดที่หนึ่ง มาอัปเดตไฟล์ .github/workflows/maven.yml กัน:
# This workflow will build a Java project with Maven
# For more information see: https://help.github.com/actions/language-and-framework-guides/building-and-testing-java-with-maven
name: Java CI with Maven
on:
push:
branches: [ main ]
pull_request:
branches: [ main ]
jobs:
build:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v2
- name: Set up MySQL
uses: mirromutth/mysql-action@v1.1
with:
mysql version: '5.7'
mysql database: 'dev_jrtb_db'
mysql root password: 'root'
mysql user: 'dev_jrtb_db_user'
mysql password: 'dev_jrtb_db_password'
- name: Set up JDK 1.11
uses: actions/setup-java@v1
with:
java-version: 1.11
- name: Build with Maven
run: mvn -B package --file pom.xml
ขณะ นี้ มีSet up MySQL block ใหม่ ในนั้น เราได้เพิ่ม MySQL เข้าไปในกระบวนการ CI ของเรา พร้อมทั้งกำหนดตัวแปรที่เราต้องการไปพร้อมๆ กัน ตอนนี้เราได้เพิ่มทุกสิ่งที่เราต้องการแล้ว ขั้นตอนสุดท้ายคือการผลักดันการเปลี่ยนแปลงและดูว่าโครงสร้างจะผ่านและเป็นสีเขียว
กำลังอัปเดตเอกสาร
มาอัปเดตเวอร์ชันโปรเจ็กต์จาก 0.3.0-SNAPSHOT เป็น 0.4.0-SNAPSHOT ใน pom.xml และเพิ่มใน RELEASE_NOTES:## 0.4.0-SNAPSHOT
* JRTB-1: added repository layer.
หลังจากทั้งหมดนี้ เราสร้างคำร้องขอแบบ Commit, Push และ Pull และที่สำคัญที่สุด โครงสร้างของเราเป็นสีเขียว!
ลิงค์ที่เป็นประโยชน์:
- พื้นที่เก็บข้อมูลของบอทโทรเลขของเรา
- ดึงคำขอพร้อมการเปลี่ยนแปลงทั้งหมดที่อธิบายไว้ในบทความ
- บทความSpringBoot + Flyway
- ภาพ MySQLจาก DockerHub
- สื่อ: วิธีสร้างอินสแตนซ์ MySql ด้วย Docker Compose
- ชื่อ: Spring Data Jpa
- ช่องโทรเลขของฉัน
GO TO FULL VERSION