JavaRush /Java блогу /Random-KY /Биз маалымат базасына тиешелүү нерселердин баарын кошобуз...
Roman Beekeeper
Деңгээл

Биз маалымат базасына тиешелүү нерселердин баарын кошобуз. (2-бөлүк) - "Адан Яга чейин Java долбоору"

Группада жарыяланган
Баарына салам. Эске сала кетейин: биринчи бөлүктө биз Flywayди коштук. уланталы.

docker-compose.yml маалымат базасын кошуу

Кийинки этап - негизги docker-compose.yml ичинде маалымат базасы менен иштөө. Келгиле, маалымат базасын docker-compose файлына кошолу:
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 программасына телеграмма ботундагыдай эле - айлана-чөйрө өзгөрмөлөрү аркылуу алабыз. Мен муну бизде маалымат базасынын колдонуучу аты менен анын сырсөзүнүн баалуулуктарын койгон бир гана жерибиз болушу үчүн жасадым. Биз аларды колдонмобуздун докер сүрөтүнө жана маалымат базабыздын докер контейнерине өткөрүп беребиз. Андан кийин SpringBoot программасын маалымат базасы үчүн өзгөрмөлөрдү кабыл алууга үйрөтүү үчүн Dockerfileди жаңыртышыбыз керек.
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}"
Докер файлындагы акыркы сап (ENTRYPOINT менен башталат) оролбостон болушу керек. Эгер сиз которууну жасасаңыз, бул code иштебейт. Акыркы кадам өзгөрмөлөрдү маалымат базасына өткөрүү үчүн 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'
Бул жерде биз маалымат базасынын колдонуучу атын жана паролду орнотобуз. Албетте, биз боттун аталышы жана энбелгиси үчүн кылгандай, bash скриптин иштеткенде бул өзгөрмөлөрдү өткөрүүгө болот. Бирок мага мунун кереги жоктой сезилет. Маалыматтар базасына чындап кирүү үчүн, сиз маалымат базасы жайгаштырыла турган serverдин IP дарегин бorп, суроо-талапка уруксат берилген IP даректердин тизмесинде болушуңуз керек. Мага келсек, бул жетиштүү. Фундамент түптөлдү: эми сиз иштеп чыгуучуга түшүнүктүү болгон нерселерди жасай аласыз - code жазыңыз. Ага чейин биз DevOps инженерлери кылган иштерди аткарып жатканбыз - чөйрөнү орнотуу.

Репозиторий катмарын кошуу

Адатта, колдонмо үч катмардан турат:
  1. Контроллерлор колдонмого кирүү чекиттери болуп саналат.
  2. Кызматтар бизнес логикасы иштейт. Бизде буга чейин жарым-жартылай бар: SendMessageService бизнес логикасынын ачык өкүлү.
  3. Репозиторийлер маалымат базасы менен иштөө үчүн жер. Биздин учурда, бул телеграмма боту.
Эми биз үчүнчү катмарды - репозиторийлерди кошобуз. Бул жерде биз Жазгы экосистеманын долбоорун колдонобуз - Жазгы маалыматтар. Бул жөнүндө бул макалада окуй аласыз Habré . Биз бир нече пункттарды бorп, түшүнүшүбүз керек:
  1. Биз JDBC менен иштешпейбиз: биз түздөн-түз жогорку абстракциялар менен иштейбиз. Башкача айтканда, маалымат базасындагы tableларга туура келген POJOларды сактаңыз. Биз мындай класстарды entity деп атайбыз , анткени алар расмий түрдө Java Persistence API'де аталат (бул ORM аркылуу маалымат базасы менен иштөө үчүн интерфейстердин жалпы жыйындысы, башкача айтканда, JDBC менен иштөө боюнча абстракция). Бизде маалымат базасында сактай турган an object классы болот жана алар бизге керектүү tableга жазылат. Маалыматтар базасынан издөөдө биз ошол эле an objectтерди алабыз.
  2. Spring Data өз интерфейстеринин топтомун колдонууну сунуш кылат: JpaRepository , CrudRepository , ж.б.... Башка интерфейстер бар: толук тизмени бул жерден тапса болот . Сулуулук, алардын ыкмаларын ишке ашырбай эле колдоно аласың(!). Мындан тышкары, интерфейсте жаңы ыкмаларды жазууга мүмкүн болгон белгилүү бир шаблон бар жана алар автоматтык түрдө ишке ашырылат.
  3. Жаз биздин өнүгүүбүздү мүмкүн болушунча жеңилдетет. Бул үчүн биз өзүбүздүн интерфейсибизди түзүп, жогоруда сүрөттөлгөндөрдөн мурасташыбыз керек. Жаз бул интерфейсти колдонуу керек экенин бorши үчүн, Репозиторий annotationсын кошуңуз.
  4. Эгерде бизде жок маалымат базасы менен иштөө ыкмасын жазуу керек болсо, анда бул да көйгөй эмес - биз аны жазабыз. Мен сага ал жерде эмне кылуу керектигин жана кантип көрсөтөм.
Бул макалада биз TelegramUserтин бүт жолун кошуу менен иштейбиз жана бул бөлүгүн мисал катары көрсөтөбүз. Калганын башка тапшырмалар боюнча кеңейтебиз. Башкача айтканда, /start буйругун аткарганда, биз колдонуучубуздун маалымат базасына active = true деп жазабыз. Бул колдонуучу бот колдонуп жатканын билдирет. Колдонуучу мурунтан эле маалымат базасында болсо, биз талааны жаңыртабыз активдүү = чындык. /stop буйругун аткарууда биз колдонуучуну жок кылбайбыз, бирок активдүү талааны жалганга гана жаңылайбыз, эгер колдонуучу ботту кайра колдонгусу келсе, аны баштап, токтогон жеринен уланта алат. Жана тестирлөөдө биз бир нерсе болуп жатканын көрө алышыбыз үчүн, биз /stat буйругун түзөбүз: ал активдүү колдонуучулардын санын көрсөтөт. Биз репозиторий пакетин бот, буйрук, кызмат пакеттеринин жанында түзөбүз . Бул пакетте биз дагы бир an object түзөбүз . Объект пакетинде биз TelegramUser классын түзөбүз:
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 пакетинин бардык annotationлары бар экенин көрө аласыз. Бул бардык ORM ишке ашыруу үчүн колдонулган жалпы annotationлар. Демейки боюнча, Spring Data Jpa Hibernate режимин колдонот, бирок башка ишке ашыруулар колдонулушу мүмкүн. Бул жерде биз колдонгон annotationлардын тизмеси:
  • Объект - бул маалымат базасы менен иштөө үчүн an object экендигин көрсөтөт;
  • Таблица – бул жерде tableнын атын аныктайбыз;
  • Id - annotation tableда кайсы талаа Негизги ачкыч болоорун айтат;
  • Мамыча - tableдан талаанын атын аныктайт.
Андан кийин, биз маалымат базасы менен иштөө үчүн интерфейс түзөбүз. Адатта, мындай интерфейстердин аттары шаблон аркылуу жазылат - EntiryNameRepository. Бизде TelegramuserRepository болот:
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 tableсынан бардык жазууларды алуу керек экенин түшүнөт, анын активдүү талаасы = true . Биз TelegramUser an objectи менен иштөө кызматын кошобуз (биз SOLIDден көз карандылыктын инversionсын башка субъекттердин кызматтары башка an objectтин репозиторийлери менен түздөн-түз байланыша албаган контекстте колдонобуз - ошол субъекттин кызматы аркылуу гана). Биз пакетте TelegramUserService кызматын түзөбүз, анын азырынча бир нече ыкмалары болот: колдонуучуну сактап, колдонуучуну анын идентификатору боюнча алуу жана активдүү колдонуучулардын тизмесин көрсөтүү. Алгач 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);
   }
}
Бул жерде биз Autowired annotationсын колдонуу менен TelegramuserRepository an objectинин көз карандылык инъекциясын (класстын инстанциясын киргизебиз) жана конструктордо колдонгонубузду белгилей кетүү керек. Сиз муну өзгөрмө үчүн жасай аласыз, бирок бул Spring Framework командасы бизге сунуш кылган ыкма.

Боттун статистикасын кошуу

Андан кийин / баштоо жана / токтотуу буйруктарын жаңыртышыңыз керек. /start буйругу колдонулганда, сиз жаңы колдонуучуну маалымат базасына сактап, аны активдүү = чындык кылып коюшуңуз керек. Жана /stop болгондо, колдонуучунун маалыматтарын жаңыртыңыз: активдүү = 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 an objectисин конструкторго өткөрүп беребиз, анын жардамы менен жаңы колдонуучуну сактап калабыз. Андан тышкары, Java'дагы Optional'дын ырахаттарын колдонуу менен, төмөнкү логика иштейт: эгерде бизде маалымат базасында колдонуучу болсо, биз аны жөн гана активдүү кылабыз, эгерде жок болсо, жаңы активдүүнү түзөбүз. StopCommand:
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 бар колдонуучу болсо, биз аны өчүрөбүз, башкача айтканда, биз активдүү = жалган деп коёбуз. Муну өз көзүң менен кантип көрө аласың? Боттун статистикасын көрсөтө турган жаңы /stat буйругун түзөлү. Бул этапта, бул бардык колдонуучулар үчүн жеткorктүү жөнөкөй статистика болот. Келечекте биз аны чектеп, администраторлор үчүн гана мүмкүнчүлүк беребиз. Статистикада бир жазуу болот: активдүү бот колдонуучуларынын саны. Бул үчүн, CommandNameге STAT("/stat") маанисин кошуңуз . Андан кийин, 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));
   }
}
Бул жерде бардыгы жөнөкөй: биз retrieveAllActiveUsers ыкмасын колдонуу менен бардык активдүү колдонуучулардын тизмесин алабыз жана коллекциянын көлөмүн алабыз. Ошондой эле, биз азыр өсүүчү класстарды жаңыртышыбыз керек: CommandContainer жана JavarushTelegramBot , алар бизге керектүү жаңы кызматты өткөрүүгө үйрөнүшөт. CommandContainer:
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 конструктору аркылуу өткөрдүк. Бирок боттун өзүндө конструктор гана өзгөрөт:
@Autowired
public JavarushTelegramBot(TelegramUserService telegramUserService) {
   this.commandContainer = new CommandContainer(new SendBotMessageServiceImpl(this), telegramUserService);
}
Эми биз Autowired annotationсын кошуп, аргумент катары TelegramUserService тапшырабыз. Бул биз аны Колдонмо контекстинен алабыз дегенди билдирет. Биз ошондой эле HelpCommand классын жаңыртабыз , ошентип жаңы статистика буйругу сыпаттамада пайда болот.

Кол менен сыноо

Келгиле, маалымат базасын docker-compose-test.yml жана JavarushTelegramBotApplication классындагы негизги ыкманы ишке киргизели. Андан кийин биз буйруктардын топтомун жазабыз:
  • /stat - биз маалымат базасы бош болсо, бул ботту колдонгон адам нөл болот деп күтөбүз;
  • /старт - ботту баштоо;
  • /stat - азыр биз ботту 1 адам колдонот деп күтөбүз;
  • /стоп - ботту токтотуу;
  • /stat - биз аны кайрадан 0 адам колдонот деп күтөбүз.
"Java долбоору Адан Яга": Маалыматтар базасына тиешелүү нерселердин бардыгын кошуу.  2-2-бөлүкЭгер натыйжа сиз үчүн бирдей болсо, функция туура иштеген жана бот туура иштеп жатат деп айта алабыз. Эгер бир нерсе туура эмес болуп кетсе, анда эч кандай мааниге ээ эмес: биз мүчүлүштүктөрдү оңдоо режиминде негизги ыкманы кайра иштетип, ката эмне болгонун табуу үчүн бүт жолду ачык басып өтөбүз.

Биз тесттерди жазып, жаңыртабыз

Биз конструкторлорду алмаштыргандыктан, тест класстарын да жаңыртышыбыз керек болот. AbstractCommandTest классында биз дагы бир талааны кошушубуз керек - үч буйрук үчүн зарыл болгон TelegramUserService классы:
protected TelegramUserService telegramUserService = Mockito.mock(TelegramUserService.class);
Андан кийин, CommandContainer ичиндеги init() ыкмасын жаңырталы :
@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 annotationсы бар , ал колдонмону баштайт. Бирок бул ыкма биз үчүн иштебейт, анткени тиркеме ишке киргенде телеграмма боту да ишке кирет. Бирок бул жерде карама-каршылык бар. Сыноолор биздин машинада жергorктүү түрдө жана GitHub Actions аркылуу жалпыга ачык жүргүзүлөт. Тесттер бүт тиркемени ишке киргизүү менен өтүшү үчүн, биз аларды телеграмма ботундагы жарактуу маалыматтар менен иштетишибиз керек: башкача айтканда, анын аты жана белгиси боюнча... Ошондуктан, бизде эки вариант бар:
  1. Андыктан боттун атын жана токенин жалпыга ачык кылып, баары жакшы болот, аны эч ким пайдаланып, бизге кийлигишпейт деп ишениңиз.
  2. Башка жол менен кел.
Мен экинчи вариантты тандадым. SpringBoot тестинде DataJpaTest annotationсы бар , ал маалымат базасын сынап жатканда биз өзүбүзгө керектүү класстарды гана колдонуп, башкаларды жалгыз калтырыш үчүн түзүлгөн. Бирок бул бизге ылайыктуу, анткени телеграмма боту такыр ишке кирбейт. Бул ага жарактуу аталышты жана белгини тапшыруунун кереги жок дегенди билдирет!))) Биз тесттен өтөбүз, анда Spring Data биз үчүн ишке ашырган ыкмалар биз күткөндөй иштей тургандыгын текшеребиз. Бул жерде биз @ActiveProfiles("test") annotationсын сыноо профorнин колдонулушун тактоо үчүн колдонгонубузду белгилей кетүү маанилүү . Бул биздин маалымат базабыз үчүн туура касиеттерди санай алышыбыз үчүн, дал ушул нерсе. Биздин тесттерди өткөрүүдөн мурун маалымат базасы даярдалса жакшы болмок. Бул маселе боюнча мындай ыкма бар: тестке Sql annotationсын кошуп, аны тестти баштоодон мурун иштетүү керек болгон скрипт аталыштарынын жыйнагын тапшырыңыз:
@Sql(scripts = {"/sql/clearDbs.sql", "/sql/telegram_users.sql"})
Биз үчүн алар ./src/test/resources/ + annotationда көрсөтүлгөн жолдун боюнда жайгашат. Бул жерде алар кандай көрүнөт:
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 тестибиз ушундай болот (көрүп тургандай, интеграциялык тестирлөөнүн аталышы башкача болот - биз Тестти эмес, ITти кошобуз):
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());
   }
}
Биз тесттерди жаздык, бирок суроо туулат: GitHub'та биздин CI процессибизди ишке киргизүү менен эмне болот? Анын маалымат базасы болбойт. Азырынча чындап эле жөн гана кызыл курулуш болот. Бул үчүн, бизде 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
Азыр жаңы MySQL орнотуу блогу бар . Анда биз MySQLди CI процессибизге кошобуз, ошол эле учурда бизге керектүү өзгөрмөлөрдү аныктайбыз. Азыр биз каалаган нерсенин бардыгын коштук. Акыркы этап өзгөрүүлөрдү түртүп, куруу өтүп, жашыл болорун көрүүгө болот.

Документти жаңылоо

Келгиле, долбоордун versionсын pom.xml ичиндеги 0.3.0-SNAPSHOTтен 0.4.0-SNAPSHOTка чейин жаңырталы, ошондой эле RELEASE_NOTESге кошолу:
## 0.4.0-SNAPSHOT

*   JRTB-1: added repository layer.
Ушунун бардыгынан кийин, биз милдеттенме, түртүү жана тартуу өтүнүчүн түзөбүз. Эң негизгиси биздин курулушубуз жашыл!"Java долбоору Адан Яга": Маалыматтар базасына тиешелүү нерселердин бардыгын кошуу.  2-3-бөлүк

Пайдалуу шилтемелер:

Бардык өзгөртүүлөр бул жерде түзүлгөн тартуу сурамында көрүүгө болот . Окуу үчүн баарына рахмат.

Сериядагы бардык материалдардын тизмеси ушул макаланын башында.

Комментарийлер
TO VIEW ALL COMMENTS OR TO MAKE A COMMENT,
GO TO FULL VERSION