JavaRush /Java Blog /Random-TL /Idinaragdag namin ang lahat ng nauugnay sa database. (Bah...

Idinaragdag namin ang lahat ng nauugnay sa database. (Bahagi 2) - "Proyekto ng Java mula A hanggang Z"

Nai-publish sa grupo
Kamusta kayong lahat. Paalalahanan kita: sa unang bahagi ay idinagdag namin ang Flyway. Ituloy natin.

Pagdaragdag ng database sa docker-compose.yml

Ang susunod na yugto ay ang pagse-set up ng trabaho kasama ang database sa pangunahing docker-compose.yml. Idagdag natin ang database sa docker-compose file:
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'
Idinagdag ko rin ang linyang ito sa aming aplikasyon:
depends_on:
 - jrtb-db
Nangangahulugan ito na hinihintay namin na magsimula ang database bago simulan ang application. Susunod, mapapansin mo ang pagdaragdag ng dalawa pang variable na kailangan namin upang gumana sa database:
${BOT_DB_USERNAME}
${BOT_DB_PASSWORD}
Dadalhin namin sila sa docker-compose sa parehong paraan tulad ng para sa telegram bot - sa pamamagitan ng mga variable ng kapaligiran. Ginawa ko ito upang mayroon lamang kaming isang lugar kung saan itinakda namin ang mga halaga ng pangalan ng gumagamit ng database at ang password nito. Ipinapasa namin ang mga ito sa docker image ng aming application at sa docker container ng aming database. Susunod na kailangan naming i-update ang Dockerfile upang turuan ang aming SpringBoot na tanggapin ang mga variable para sa database.
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"]
Ngayon ay nagdaragdag kami ng mga variable ng database sa Dockerfile:
ENV BOT_DB_USERNAME=jrtb_db_user
ENV BOT_DB_PASSWORD=jrtb_db_password
Ang mga variable na halaga ay magkakaiba. Ang mga ipapasa namin sa Dockerfile, gayunpaman, ay nangangailangan ng mga default na halaga, kaya nagpasok ako ng ilan. Pinapalawak namin ang huling linya na may dalawang elemento, sa tulong kung saan ipapasa namin ang username at password ng DB sa paglulunsad ng application:
"-Dspring.datasource.password=${BOT_DB_PASSWORD}", "-Dbot.username=${BOT_NAME}"
Ang huling linya sa Dockerfile (na nagsisimula sa ENTRYPOINT) ay dapat na walang pambalot. Kung gagawa ka ng paglipat, hindi gagana ang code na ito. Ang huling hakbang ay i-update ang start.sh file upang maipasa ang mga variable sa database.
#!/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
Alam na namin kung paano magdagdag ng mga variable ng kapaligiran bago patakbuhin ang docker-compose. Para magawa ito, kailangan mo lang mag-execute ng export var_name=var_value.. Samakatuwid, dalawang linya lang ang idinagdag namin:
export BOT_DB_USERNAME='prod_jrtb_db_user'
export BOT_DB_PASSWORD='Pap9L9VVUkNYj99GCUCC3mJkb'
Dito namin itinakda ang database username at password. Siyempre, posibleng ipasa ang mga variable na ito kapag pinapatakbo ang script ng bash, tulad ng ginagawa namin para sa pangalan at token ng bot. Ngunit tila sa akin na ito ay hindi kailangan. Upang aktwal na ma-access ang database, kailangan mong malaman ang IP ng server kung saan ide-deploy ang database, at nasa listahan ng mga pinapayagang IP address para sa kahilingan. Sa akin naman, ito ay sapat na. Ang pundasyon ay inilatag: ngayon ay maaari kang gumawa ng mga bagay na mas nauunawaan para sa isang developer - magsulat ng code. Bago iyon, ginagawa namin ang ginagawa ng mga inhinyero ng DevOps - ang pag-set up ng kapaligiran.

Pagdaragdag ng Layer ng Repository

Karaniwan ang isang application ay may tatlong layer:
  1. Ang mga controllers ay ang mga entry point sa application.
  2. Ang mga serbisyo ay kung saan gumagana ang lohika ng negosyo. Bahagyang mayroon na kami nito: Ang SendMessageService ay isang tahasang kinatawan ng lohika ng negosyo.
  3. Ang mga repositoryo ay isang lugar para magtrabaho kasama ang isang database. Sa aming kaso, ito ay isang telegram bot.
Ngayon ay idaragdag namin ang ikatlong layer - mga repositoryo. Dito gagamit tayo ng proyekto mula sa Spring ecosystem - Spring Data. Mababasa mo ang tungkol sa kung ano ito sa artikulong ito sa Habré . Kailangan nating malaman at maunawaan ang ilang mga punto:
  1. У нас не будет работы с JDBC: мы будем работать сразу с более высокими абстракциями. То есть, сохранять an objectы POJO, которые соответствуют tableм в базе данных. Такие классы мы будем называть entity, так, How они называются официально в Java Persistence API (это общий набор интерфейсов для работы БД через ORM, то есть абстракция над работой с JDBC). У нас будет класс entity, который мы будем сохранять в БД, и они запишутся именно в ту таблицу, которая нам нужна. Такие же an objectы мы будем получать при поиске в базе данных.
  2. Spring Data предлагает использовать их набор интерфейсов: JpaRepository, CrudRepository, etc... Есть и другие интерфейсы: полный перечень можно найти здесь. Прелесть заключается в том, что можно использовать их методы не реализовывая их(!). Более того, есть определенный шаблон, используя который можно писать в интерфейсе новые методы, и они будут реализованы автоматически.
  3. Spring упрощает нашу разработку How может. Для этого нам нужно создать свой интерфейс и наследоваться от вышеописанных. А чтобы Spring знал, что ему нужно использовать этот интерфейс, добавить аннотацию Repository.
  4. Если нам нужно будет написать метод для работы с базой данных, которого нет, то это тоже не проблема — напишем. Покажу, что и How там делать.
В рамках этой статьи мы проведем работу с добавлением по всему пути TelegramUser и покажем на его примере эту часть. Остальное уже будем расширять на других задачах. То есть при выполнении команды /start мы будем записывать в базу данных нашего пользователя active = true. Это будет означать, что пользователь пользуется ботом. Если пользователь уже есть в БД, будем обновлять поле active = true. При выполнении команды /stop мы не будем удалять пользователя, а только обновим поле active на meaning false, чтобы если пользователь опять захочет использовать бота, он мог его запустить и продолжить с того момента, где остановился. А чтобы при тестировании было видно, что что-то происходит, мы создадим команду /stat: она будет отображать количество активных пользователей. Создаем repository пакет рядом с пакетами bot, command, service. В этом пакете создаем еще один — entity. В пакете entity создаем класс 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 пакета. Это общие аннотации, которые используются для всех реализаций ORM. По умолчанию в Spring Data Jpa используется Hibernate, хотя можно использовать и другие реализации. Вот перечень аннотаций, которые мы используем:
  • Entity —говорит о том, что это сущность для работы с БД;
  • Table — здесь мы определяем Name таблицы;
  • Id — annotation говорит, Howое поле будет Primary Key в таблице;
  • Column — определяем Name поля из таблицы.
Далее создаем интерфейс для работы с базой данных. Обычно имена у таких интерфейсов пишутся по шаблону — 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();
}
Здесь видно, How я добавил метод findAllByActiveTrue(), который нигде не реализую. Но это не помешает ему работать. Spring Data поймет, что нужно получить все записи из таблицы tg_user, у которых поле active = true. Добавляем сервис по работе с сущностью TelegramUser (применяем dependency inversion из SOLID в контексте того, что сервисы других сущностей не могут напрямую общаться с репозиторием другой сущности — только через сервис той сущности). Создаем в пакете service 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);
   }
}
Здесь нужно отметить, что мы используем dependency injection (вводим экземпляр класса) an object TelegramuserRepository при помощи аннотации Autowired, причем на конструкторе. Можно делать это и для переменной, но именно этот подход рекомендует нам Spring Framework команда.

Добавляем статистику для бота

Далее нужно обновить команды /start и /stop. Когда используется команда /start, нужно сохранить в БД нового пользователя и поставить ему active = true. А когда будет /stop, обновить данные пользователя: поставить 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);
   }
}
Здесь мы передаем в конструктор еще и an object TelegramuserService, при помощи которого будем сохранять нового пользователя. Далее, используя прелести 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);
               });
   }
}
В StopCommand точно так же передаем TelegramServiceTest. Дополнительная логика такая: если у нас есть пользователь с таким chat ID, мы его деактивируем, то есть ставим active = false. Как это увидеть собственными глазами? Сделаем новую команду /stat, которая будет отображать статистику бота. На данном этапе это будет простая статистика, доступная всем пользователям. В дальнейшем мы ее ограничим и сделаем доступ только для администраторов. В статистике будет одна запись: количество активных пользователей бота. Для этого добавляем meaning 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));
   }
}
Здесь все просто: мы получаем список всех активных пользователей при помощи метода retrieveAllActiveUsers и получаем размер коллекции. Также теперь нужно обновить классы по восходящей: CommandContainer и JavarushTelegramBot, чтобы они научorсь передавать нужный нам новый сервис. 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);
   }

}
Здесь мы добавor в мапу новую команду и передали через конструктор TelegramUserService. А вот в самом боте изменится только конструктор:
@Autowired
public JavarushTelegramBot(TelegramUserService telegramUserService) {
   this.commandContainer = new CommandContainer(new SendBotMessageServiceImpl(this), telegramUserService);
}
Теперь мы передаем в виде аргумента TelegramUserService, добавляя аннотацию Autowired. Это значит, что ее мы получим из Application Context. Также обновим класс HelpCommand, чтобы в описании появилась еще и новая команда по статистике.

Мануальное тестирование

Запустим базу данных из docker-compose-test.yml и main метод в классе JavarushTelegramBotApplication. Далее пишем набор команд:
  • /stat — ожидаем, что при пустой базе данных человек, использующих этот бот, будет ноль;
  • /start — запускаем бота;
  • /stat — теперь ожидаем, что бота будет использовать 1 человек;
  • /stop — останавливаем бота;
  • /stat — ожидаем, что опять будет 0 человек использовать.
"Java-проект от А до Я": Добавляем все, что связано с БД. Часть 2 - 2Если у вас в результате будет так же, можно сказать, что функционал отработал верно и бот исправный. Если что-то пойдет не так — не беда: перезапускаем main метод в режиме дебага и проходим четко по всему пути, чтобы найти, в чем была ошибка.

Пишем и обновляем тесты

Так How мы изменor конструкторы, нужно будет обновить и тестовые классы. В классе 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);
   }
}
Это из простого. Теперь поговорим о том, How мы будем тестировать работу с базой данных. Все, что мы делали до этого, это были юнит-тесты. В интеграционном тесте проверяется интеграция между несколькими частями applications. Например, applications и базы данных. Здесь все будет сложнее, потому что для тестирования нам нужна развернутая база данных. Поэтому когда мы будем локально прогонять наши тесты, у нас должна быть запущена база данных из docker-compose-test.yml. Whatбы прогнать этот тест, нужно запустить все SpringBoot приложение. Для тестового класса есть annotation SpringBootTest, которая запустит приложение. Но этот подход нам не подойдет, потому что при запуске applications будет запускаться и телеграм-бот. Но здесь есть противоречие. Тесты будут запускаться How локально у нас на машине, так и публично, через GitHub Actions. Whatбы тесты прошли с запуском всего applications, мы должны их запускать с валидными данными по телеграм-боту: то есть, по его имени и токену… Поэтому у нас два варианта:
  1. Таки сделать публичными Name и токен бота и надеяться, что все будет хорошо, никто его не будет использовать и мешать нам.
  2. Придумать другой путь.
Я выбрал второй вариант. В тестировании SpringBoot есть annotation DataJpaTest, которая создана, чтобы при тестировании базы данных мы использовали только нужные нам классы и не трогали другие. Но нам это подходит, потому что телеграм-бот вовсе не будет запускаться. А значит, не нужно передавать ему валидное Name и токен!))) Получим тест, в котором проверим, что методы, которые Spring Data нам реализует, работают так, How мы ожидаем. Здесь важно отметить, что мы при помощи аннотации @ActiveProfiles("test") задаем использование профиля test. А это нам How раз и нужно, чтобы мы считали правильные проперти для нашей базы данных. Хорошо бы иметь подготовленную базу данных перед запуском наших тестов. Для этого дела есть такой подход: добавить к тесту annotation Sql и передать ей коллекцию имен скриптов, которые нужно запустить перед началом теста:
@Sql(scripts = {"/sql/clearDbs.sql", "/sql/telegram_users.sql"})
У нас они буду лежать по пути ./src/test/resources/ + путь, указанный в аннотации. Вот How они выглядят:
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);
Вот How в результате будет выглядеть наш тест TelegramUserRepositoryIT (How видите, и Name для интеграционного тестирования будет другое — добавляем не Test, а 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());
   }
}
Тесты написали, но возникает вопрос: а что будет с запуском нашего CI процесса на GitHub? У него же не будет базы данных. На данный момент действительно будет просто красный билд. Для этого у нас есть GitHub actions, в котором мы можем настроить запуск нашего билда. До запуска тестов нужно добавить запуск базы данных с нужными настройками. Как оказалось, на просторах интернета не так уж и много примеров, поэтому советую сохранить себе где-то это. Обновим файл .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. В нем добавляем MySQL к нашему CI процессу, попутно определяя нужные для нас переменные. Теперь уже все, что хотели, мы добавor. Последний этап — запушить изменения и посмотреть, что билд пройдет и будет зеленый.

Обновляем documentацию

Обновим версию проекта с 0.3.0-SNAPSHOT на 0.4.0-SNAPSHOT в pom.xml и добавим в RELEASE_NOTES также:
## 0.4.0-SNAPSHOT

*   JRTB-1: added repository layer.
После всего этого создаем коммит, пуш и пулл-реквест. И самое главное — наш билд зеленый!"Java-проект от А до Я": Добавляем все, что связано с БД. Часть 2 - 3

Полезные ссылки:

Все изменения можно увидеть вот здесь в созданном пулл-реквесте. Всем спасибо за прочтение.

Список всех материалов серии в начале этой статьи.

Mga komento
TO VIEW ALL COMMENTS OR TO MAKE A COMMENT,
GO TO FULL VERSION