JavaRush /Blog Java /Random-PL /Dodajemy wszystko co związane z bazą danych. (Część 2) - ...
Roman Beekeeper
Poziom 35

Dodajemy wszystko co związane z bazą danych. (Część 2) - „Projekt Java od A do Z”

Opublikowano w grupie Random-PL
Cześć wszystkim. Przypomnę: w pierwszej części dodaliśmy Flyway. Kontynuujmy.

Dodanie bazy danych do pliku docker-compose.yml

Kolejnym etapem jest ustawienie pracy z bazą danych w głównym pliku docker-compose.yml. Dodajmy bazę danych do pliku 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'
Dodałem także tę linię do naszej aplikacji:
depends_on:
 - jrtb-db
Oznacza to, że przed uruchomieniem aplikacji czekamy na uruchomienie bazy danych. Następnie można zauważyć dodanie dwóch kolejnych zmiennych, które są nam potrzebne do pracy z bazą danych:
${BOT_DB_USERNAME}
${BOT_DB_PASSWORD}
Pozyskamy je w docker-compose w taki sam sposób, jak w przypadku bota telegramu - poprzez zmienne środowiskowe. Zrobiłem to tak, że mamy tylko jedno miejsce, w którym ustawiamy wartości nazwy użytkownika bazy danych i jego hasła. Przekazujemy je do obrazu dokującego naszej aplikacji oraz do kontenera dokowanego naszej bazy danych. Następnie musimy zaktualizować plik Dockerfile, aby nauczyć naszego SpringBoota akceptowania zmiennych dla bazy danych.
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"]
Teraz dodajemy zmienne bazy danych do pliku Dockerfile:
ENV BOT_DB_USERNAME=jrtb_db_user
ENV BOT_DB_PASSWORD=jrtb_db_password
Wartości zmiennych będą inne. Te, które przekażemy do pliku Dockerfile, wymagają jednak wartości domyślnych, więc je wprowadziłem. Ostatnią linię rozwijamy o dwa elementy, za pomocą których przekażemy nazwę użytkownika i hasło DB do uruchomienia aplikacji:
"-Dspring.datasource.password=${BOT_DB_PASSWORD}", "-Dbot.username=${BOT_NAME}"
Ostatnia linia pliku Dockerfile (zaczynająca się od ENTRYPOINT) musi być bez zawijania. Jeśli wykonasz przelew, ten kod nie zadziała. Ostatnim krokiem jest aktualizacja pliku start.sh w celu przekazania zmiennych do bazy danych.
#!/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
Wiemy już, jak dodać zmienne środowiskowe przed uruchomieniem docker-compose. Aby to zrobić, wystarczy wykonać eksport nazwa_zmiennej=wartość_zmiennej.. Dlatego dodajemy tylko dwie linie:
export BOT_DB_USERNAME='prod_jrtb_db_user'
export BOT_DB_PASSWORD='Pap9L9VVUkNYj99GCUCC3mJkb'
Tutaj ustawiamy nazwę użytkownika i hasło bazy danych. Oczywiście możliwe byłoby przekazanie tych zmiennych podczas uruchamiania skryptu bash, tak jak robimy to w przypadku nazwy i tokena bota. Wydaje mi się jednak, że jest to niepotrzebne. Aby faktycznie uzyskać dostęp do bazy danych, należy znać adres IP serwera, na którym baza danych będzie wdrożona oraz znajdować się na liście dozwolonych adresów IP dla żądania. Jak dla mnie to już wystarczy. Fundamenty zostały położone: teraz możesz robić rzeczy bardziej zrozumiałe dla programisty - pisać kod. Wcześniej robiliśmy to, co robią inżynierowie DevOps – konfigurowaliśmy środowisko.

Dodanie warstwy Repozytorium

Zazwyczaj aplikacja składa się z trzech warstw:
  1. Kontrolery są punktami wejścia do aplikacji.
  2. Usługi to miejsce, w którym działa logika biznesowa. Częściowo już to mamy: SendMessageService jest wyraźnym przedstawicielem logiki biznesowej.
  3. Repozytoria to miejsce pracy z bazą danych. W naszym przypadku jest to bot telegramowy.
Teraz dodamy trzecią warstwę - repozytoria. Tutaj wykorzystamy projekt z ekosystemu Spring – Spring Data. O tym, co to jest, możesz przeczytać w tym artykule na temat Habré . Musimy poznać i zrozumieć kilka punktów:
  1. Nie będziemy musieli pracować z JDBC: będziemy pracować bezpośrednio z wyższymi abstrakcjami. Oznacza to, że przechowuj POJO odpowiadające tabelom w bazie danych. Takie klasy będziemy nazywać encja , tak jak się je oficjalnie nazywa w Java Persistence API (jest to powszechny zestaw interfejsów do pracy z bazą danych poprzez ORM, czyli abstrakcja w stosunku do pracy z JDBC). Będziemy mieli klasę encji, którą zapiszemy w bazie danych i zostaną one zapisane dokładnie w tej tabeli, której potrzebujemy. Te same obiekty otrzymamy podczas wyszukiwania w bazie danych.
  2. Spring Data sugeruje użycie ich zestawu interfejsów: JpaRepository , CrudRepository itp. Istnieją inne interfejsy: pełną listę można znaleźć tutaj . Piękno polega na tym, że można używać ich metod bez ich wdrażania(!). Ponadto istnieje pewien szablon, za pomocą którego można pisać nowe metody w interfejsie, a będą one wdrażane automatycznie.
  3. Wiosna maksymalnie ułatwia nam rozwój. Aby to zrobić musimy stworzyć własny interfejs i dziedziczyć z tych opisanych powyżej. I żeby Spring wiedział, że musi skorzystać z tego interfejsu, dodaj adnotację Repozytorium.
  4. Jeśli będziemy musieli napisać metodę pracy z bazą danych, która nie istnieje, to również nie stanowi to problemu - napiszemy to. Pokażę Ci co i jak tam zrobić.
W tym artykule zajmiemy się dodawaniem na całej ścieżce TelegramUser i pokażemy tę część jako przykład. Resztę rozwiniemy o inne zadania. Oznacza to, że wykonując polecenie /start, zapiszemy active = true do bazy danych naszego użytkownika. Będzie to oznaczać, że użytkownik korzysta z bota. Jeśli użytkownik znajduje się już w bazie, zaktualizujemy pole active = true. Wykonując komendę /stop nie usuniemy użytkownika, a jedynie zaktualizujemy aktywne pole do wartości false, dzięki czemu jeśli użytkownik będzie chciał ponownie skorzystać z bota, będzie mógł go uruchomić i kontynuować od miejsca, w którym przerwał. Abyśmy podczas testowania mogli zobaczyć, że coś się dzieje, stworzymy komendę /stat: wyświetli ona liczbę aktywnych użytkowników. Tworzymy pakiet repozytorium obok pakietów bota, poleceń, usług. W tym pakiecie tworzymy kolejny jednojednostkowy podmiot . W pakiecie encji tworzymy klasę 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;
}
Tutaj możesz zobaczyć, że mamy wszystkie adnotacje z pakietu javax.persistence. Są to typowe adnotacje używane we wszystkich implementacjach ORM. Domyślnie Spring Data Jpa używa Hibernacji, chociaż można zastosować inne implementacje. Oto lista adnotacji, których używamy:
  • Jednostka - wskazuje, że jest to jednostka do pracy z bazą danych;
  • Tabela - tutaj definiujemy nazwę tabeli;
  • Id - adnotacja informuje, które pole będzie kluczem podstawowym w tabeli;
  • Kolumna - określ nazwę pola z tabeli.
Następnie tworzymy interfejs do pracy z bazą danych. Zazwyczaj nazwy takich interfejsów zapisywane są przy użyciu szablonu - EntiryNameRepository. Będziemy mieć 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();
}
Tutaj możesz zobaczyć, jak dodałem metodę findAllByActiveTrue() , której nigdzie nie implementuję. Ale to nie przeszkodzi mu w pracy. Spring Data zrozumie, że musi pobrać wszystkie rekordy z tabeli tg_user, której aktywne pole = true . Dodajemy usługę współpracy z jednostką TelegramUser (inwersję zależności z SOLID stosujemy w kontekście, że usługi innych podmiotów nie mogą bezpośrednio komunikować się z repozytorium innego podmiotu - jedynie poprzez usługę tego podmiotu). W pakiecie tworzymy usługę TelegramUserService, która na razie będzie miała kilka metod: zapisanie użytkownika, pobranie użytkownika po jego ID i wyświetlenie listy aktywnych użytkowników. Najpierw tworzymy interfejs 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);
}
I w rzeczywistości implementacja 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);
   }
}
Tutaj należy zaznaczyć, że używamy wstrzykiwania zależności (wprowadzamy instancję klasy) obiektu TelegramuserRepository za pomocą adnotacji Autowired oraz na konstruktorze. Możesz to zrobić dla zmiennej, ale jest to podejście, które zaleca nam zespół Spring Framework.

Dodawanie statystyk dla bota

Następnie musisz zaktualizować polecenia /start i /stop. W przypadku użycia polecenia /start należy zapisać nowego użytkownika w bazie danych i ustawić dla niego wartość aktywny = prawda. A gdy jest /stop, zaktualizuj dane użytkownika: set active = false. Naprawmy klasę 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);
   }
}
Tutaj również przekazujemy konstruktorowi obiekt TelegramuserService, za pomocą którego zapiszemy nowego użytkownika. Co więcej, korzystając z uroków Opcjonalnego w Javie, działa następująca logika: jeśli mamy użytkownika w bazie danych, po prostu go aktywujemy, jeśli nie, tworzymy nowego aktywnego. Polecenie zatrzymania:
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);
               });
   }
}
W ten sam sposób przekazujemy TelegramServiceTest do StopCommand. Dodatkowa logika jest taka: jeśli mamy użytkownika z takim ID czatu, dezaktywujemy go, czyli ustawiamy active = false. Jak możesz to zobaczyć na własne oczy? Stwórzmy nowe polecenie /stat, które wyświetli statystyki bota. Na tym etapie będą to proste statystyki dostępne dla wszystkich użytkowników. W przyszłości ograniczymy to i udostępnimy jedynie administratorom. W statystykach będzie jeden wpis: liczba aktywnych użytkowników bota. Aby to zrobić, dodaj wartość STAT("/stat") do CommandName. Następnie utwórz klasę 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));
   }
}
Tutaj wszystko jest proste: za pomocą metody retrieveAllActiveUsers uzyskujemy listę wszystkich aktywnych użytkowników i uzyskujemy wielkość kolekcji. Musimy teraz także zaktualizować klasy rosnące: CommandContainer i JavarushTelegramBot , aby nauczyły się przenosić nową usługę, której potrzebujemy. Kontener poleceń:
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);
   }

}
Tutaj dodaliśmy nowe polecenie do mapy i przekazaliśmy je przez konstruktor TelegramUserService. Ale w samym bocie zmieni się tylko konstruktor:
@Autowired
public JavarushTelegramBot(TelegramUserService telegramUserService) {
   this.commandContainer = new CommandContainer(new SendBotMessageServiceImpl(this), telegramUserService);
}
Teraz przekazujemy TelegramUserService jako argument, dodając adnotację Autowired. Oznacza to, że otrzymamy go z Kontekstu Aplikacji. Zaktualizujemy także klasę HelpCommand tak, aby w opisie pojawiło się nowe polecenie statystyczne.

Testowanie ręczne

Uruchommy bazę danych z pliku docker-compose-test.yml i metody głównej w klasie JavarushTelegramBotApplication. Następnie piszemy zestaw poleceń:
  • /stat - spodziewamy się, że jeśli baza danych będzie pusta, nie będzie żadnych osób korzystających z tego bota;
  • /start – uruchamia bota;
  • /stat - teraz oczekujemy, że z bota będzie korzystać 1 osoba;
  • /stop – zatrzymuje bota;
  • /stat - spodziewamy się, że ponownie będzie z niego korzystać 0 osób.
„Projekt Java od A do Z”: Dodanie wszystkiego co związane z bazą danych.  Część 2 - 2Jeśli wynik jest dla Ciebie taki sam, możemy powiedzieć, że funkcjonalność działała poprawnie, a bot działa poprawnie. Jeśli coś pójdzie nie tak, nie ma to znaczenia: ponownie uruchamiamy metodę główną w trybie debugowania i wyraźnie przechodzimy całą ścieżkę, aby dowiedzieć się, na czym polegał błąd.

Piszemy i aktualizujemy testy

Ponieważ zmieniliśmy konstruktory, będziemy musieli zaktualizować także klasy testowe. W klasie AbstractCommandTest musimy dodać jeszcze jedno pole - klasę telegramUserService , która jest potrzebna dla trzech poleceń:
protected TelegramUserService telegramUserService = Mockito.mock(TelegramUserService.class);
Następnie zaktualizujmy metodę init() w CommandContainerze :
@BeforeEach
public void init() {
   SendBotMessageService sendBotMessageService = Mockito.mock(SendBotMessageService.class);
   TelegramUserService telegramUserService = Mockito.mock(TelegramUserService.class);
   commandContainer = new CommandContainer(sendBotMessageService, telegramUserService);
}
W StartCommand musisz zaktualizować metodę getCommand() :
@Override
Command getCommand() {
   return new StartCommand(sendBotMessageService, telegramUserService);
}
Również w StopCommand:
@Override
Command getCommand() {
   return new StopCommand(sendBotMessageService, telegramUserService);
}
Następnie przyjrzyjmy się nowym testom. Stwórzmy typowy test dla 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);
   }
}
To jest proste. Porozmawiajmy teraz o tym, jak przetestujemy pracę z bazą danych. Wszystko, co zrobiliśmy wcześniej, to testy jednostkowe. Test integracyjny sprawdza integrację pomiędzy wieloma częściami aplikacji. Na przykład aplikacje i bazy danych. Tutaj wszystko będzie bardziej skomplikowane, ponieważ do testów potrzebujemy wdrożonej bazy danych. Dlatego też, gdy uruchamiamy nasze testy lokalnie, baza danych musi działać w pliku docker-compose-test.yml. Aby uruchomić ten test, musisz uruchomić całą aplikację SpringBoot. Klasa testowa posiada adnotację SpringBootTest , która uruchomi aplikację. Ale to podejście nie będzie dla nas skuteczne, ponieważ po uruchomieniu aplikacji uruchomi się także bot telegramu. Ale jest tu sprzeczność. Testy będą przeprowadzane zarówno lokalnie na naszej maszynie, jak i publicznie poprzez GitHub Actions. Aby testy przebiegły pomyślnie wraz z uruchomieniem całej aplikacji, musimy je uruchomić z prawidłowymi danymi na bocie telegramowym: czyli po jego nazwie i tokenie... W związku z tym mamy dwie opcje:
  1. Zatem upublicznij nazwę i token bota i miej nadzieję, że wszystko będzie dobrze, nikt nie będzie z niego korzystał i nam przeszkadzał.
  2. Wymyśl inny sposób.
Wybrałem drugą opcję. Testowanie SpringBoot posiada adnotację DataJpaTest , która została stworzona po to, abyśmy testując bazę danych korzystali tylko z tych klas, których potrzebowaliśmy, a inne zostawialiśmy w spokoju. Ale to nam odpowiada, ponieważ bot telegramu w ogóle się nie uruchomi. Oznacza to, że nie trzeba podawać mu prawidłowej nazwy i tokenu!))) Dostaniemy test, w którym sprawdzimy, czy metody, które implementuje dla nas Spring Data, działają tak, jak tego oczekujemy. Należy tutaj zauważyć, że używamy adnotacji @ActiveProfiles("test") , aby określić użycie profilu testowego. I właśnie tego potrzebujemy, abyśmy mogli policzyć prawidłowe właściwości dla naszej bazy danych. Byłoby miło mieć przygotowaną bazę danych przed uruchomieniem naszych testów. Jest na to takie podejście: dodaj do testu adnotację Sql i przekaż mu zbiór nazw skryptów, które należy uruchomić przed rozpoczęciem testu:
@Sql(scripts = {"/sql/clearDbs.sql", "/sql/telegram_users.sql"})
U nas będą one zlokalizowane wzdłuż ścieżki ./src/test/resources/ + ścieżka podana w adnotacji. Oto jak wyglądają:
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);
Tak w rezultacie będzie wyglądał nasz test TelegramUserRepositoryIT (jak widać, dla testów integracyjnych nazwa będzie inna - dodajemy IT, a nie Test):
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());
   }
}
Pisaliśmy testy, ale pojawia się pytanie: co będzie z uruchomieniem naszego procesu CI na GitHubie? Nie będzie mieć bazy danych. Na razie będzie tylko wersja czerwona. W tym celu mamy akcje GitHub, w których możemy skonfigurować uruchomienie naszego buildu. Przed uruchomieniem testów należy dodać uruchomienie bazy danych z niezbędnymi ustawieniami. Jak się okazuje, przykładów w Internecie nie ma zbyt wiele, dlatego radzę gdzieś to zapisać. Zaktualizujmy plik .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
Teraz pojawił się nowy blok Konfiguruj MySQL . Dodajemy w nim MySQL do naszego procesu CI, jednocześnie definiując potrzebne nam zmienne. Teraz dodaliśmy wszystko, co chcieliśmy. Ostatnim etapem jest wypchnięcie zmian i sprawdzenie, czy kompilacja przejdzie i będzie ekologiczna.

Aktualizacja dokumentacji

Zaktualizujmy wersję projektu z 0.3.0-SNAPSHOT do 0.4.0-SNAPSHOT w pom.xml i dodajmy także do RELEASE_NOTES:
## 0.4.0-SNAPSHOT

*   JRTB-1: added repository layer.
Po tym wszystkim tworzymy żądanie zatwierdzenia, wypychania i ściągania. A co najważniejsze, nasza konstrukcja jest ekologiczna!„Projekt Java od A do Z”: Dodanie wszystkiego co związane z bazą danych.  Część 2 - 3

Przydatne linki:

Wszystkie zmiany można zobaczyć tutaj w utworzonym żądaniu ściągnięcia . Dziękuję wszystkim za przeczytanie.

Lista wszystkich materiałów wchodzących w skład serii znajduje się na początku artykułu.

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