JavaRush /Java Blog /Random-ID /Kami menambahkan segala sesuatu yang berhubungan dengan d...

Kami menambahkan segala sesuatu yang berhubungan dengan database. (Bagian 2) - "Proyek Java dari A sampai Z"

Dipublikasikan di grup Random-ID
Halo semua. Izinkan saya mengingatkan Anda: di bagian pertama kami menambahkan Jalur Terbang. Ayo lanjutkan.

Menambahkan database ke docker-compose.yml

Tahap selanjutnya adalah pengaturan pekerjaan dengan database di docker-compose.yml utama. Mari tambahkan database ke file 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'
Saya juga menambahkan baris ini ke aplikasi kita:
depends_on:
 - jrtb-db
Artinya kita menunggu database dimulai sebelum memulai aplikasi. Selanjutnya, Anda dapat melihat penambahan dua variabel lagi yang kita perlukan untuk bekerja dengan database:
${BOT_DB_USERNAME}
${BOT_DB_PASSWORD}
Kami akan mendapatkannya di docker-compose dengan cara yang sama seperti bot telegram - melalui variabel lingkungan. Saya melakukan ini sehingga kami hanya memiliki satu tempat di mana kami menetapkan nilai nama pengguna database dan kata sandinya. Kami meneruskannya ke gambar buruh pelabuhan di aplikasi kami dan ke wadah buruh pelabuhan di database kami. Selanjutnya kita perlu memperbarui Dockerfile untuk mengajarkan SpringBoot kita menerima variabel untuk 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"]
Sekarang kami menambahkan variabel database ke Dockerfile:
ENV BOT_DB_USERNAME=jrtb_db_user
ENV BOT_DB_PASSWORD=jrtb_db_password
Nilai variabelnya akan berbeda. Namun, nilai yang akan kita masukkan ke Dockerfile memerlukan nilai default, jadi saya memasukkan beberapa. Kami memperluas baris terakhir dengan dua elemen, yang dengannya kami akan meneruskan nama pengguna dan kata sandi DB ke peluncuran aplikasi:
"-Dspring.datasource.password=${BOT_DB_PASSWORD}", "-Dbot.username=${BOT_NAME}"
Baris terakhir di Dockerfile (yang dimulai dengan ENTRYPOINT) harus tanpa pembungkus. Jika Anda melakukan transfer, kode ini tidak akan berfungsi. Langkah terakhir adalah memperbarui file start.sh untuk meneruskan variabel ke 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
Kita sudah mengetahui cara menambahkan variabel lingkungan sebelum menjalankan docker-compose. Untuk melakukan ini, Anda hanya perlu menjalankan ekspor var_name=var_value. Oleh karena itu, kami hanya menambahkan dua baris:
export BOT_DB_USERNAME='prod_jrtb_db_user'
export BOT_DB_PASSWORD='Pap9L9VVUkNYj99GCUCC3mJkb'
Di sinilah kita mengatur nama pengguna dan kata sandi database. Tentu saja, variabel-variabel ini dapat diteruskan saat menjalankan skrip bash, seperti yang kita lakukan untuk nama dan token bot. Namun menurut saya hal ini tidak perlu. Untuk benar-benar mengakses database, Anda perlu mengetahui IP server tempat database akan disebarkan, dan berada dalam daftar alamat IP yang diizinkan untuk permintaan tersebut. Bagi saya, ini sudah cukup. Fondasinya telah diletakkan: sekarang Anda dapat melakukan hal-hal yang lebih mudah dipahami oleh pengembang - menulis kode. Sebelumnya, kami melakukan apa yang dilakukan para insinyur DevOps - menyiapkan lingkungan.

Menambahkan lapisan Repositori

Biasanya suatu aplikasi memiliki tiga lapisan:
  1. Pengontrol adalah titik masuk ke dalam aplikasi.
  2. Layanan adalah tempat logika bisnis bekerja. Kami sudah memiliki sebagian ini: SendMessageService merupakan representasi eksplisit dari logika bisnis.
  3. Repositori adalah tempat untuk bekerja dengan database. Dalam kasus kami, ini adalah bot telegram.
Sekarang kita akan menambahkan lapisan ketiga - repositori. Di sini kita akan menggunakan proyek dari ekosistem Spring - Spring Data. Anda dapat membaca tentangnya di artikel di Habré ini . Beberapa hal yang perlu kita ketahui dan pahami:
  1. Kami tidak harus bekerja dengan JDBC: kami akan bekerja secara langsung dengan abstraksi yang lebih tinggi. Artinya, menyimpan POJO yang sesuai dengan tabel di database. Kami akan memanggil kelas seperti itu entitas , sebagaimana mereka secara resmi dipanggil di Java Persistence API (ini adalah kumpulan antarmuka umum untuk bekerja dengan database melalui ORM, yaitu abstraksi untuk bekerja dengan JDBC). Kita akan memiliki kelas entitas yang akan kita simpan dalam database, dan kelas tersebut akan ditulis tepat ke tabel yang kita perlukan. Kami akan menerima objek yang sama saat mencari di database.
  2. Spring Data menawarkan untuk menggunakan rangkaian antarmuka mereka: JpaRepository , CrudRepository , dll... Ada antarmuka lain: daftar lengkap dapat ditemukan di sini . Keindahannya adalah Anda dapat menggunakan metode mereka tanpa menerapkannya (!). Selain itu, ada templat tertentu yang dapat digunakan untuk menulis metode baru di antarmuka, dan metode tersebut akan diterapkan secara otomatis.
  3. Musim semi menyederhanakan perkembangan kita semaksimal mungkin. Untuk melakukan ini, kita perlu membuat antarmuka kita sendiri dan mewarisi antarmuka yang dijelaskan di atas. Dan agar Spring mengetahui bahwa ia perlu menggunakan antarmuka ini, tambahkan anotasi Repositori.
  4. Jika kita perlu menulis metode untuk bekerja dengan database yang tidak ada, maka ini juga tidak menjadi masalah - kita akan menulisnya. Saya akan menunjukkan kepada Anda apa dan bagaimana melakukannya di sana.
Pada artikel ini, kami akan bekerja dengan menambahkan seluruh jalur Pengguna Telegram dan menunjukkan bagian ini sebagai contoh. Kami akan memperluas sisanya pada tugas-tugas lain. Artinya, ketika kita menjalankan perintah /start, kita akan menulis active = true ke database pengguna kita. Ini berarti bahwa pengguna menggunakan bot. Jika pengguna sudah ada di database, kami akan memperbarui field active = true. Saat menjalankan perintah /stop, kami tidak akan menghapus pengguna, tetapi hanya akan memperbarui bidang aktif menjadi false, sehingga jika pengguna ingin menggunakan bot lagi, dia dapat memulainya dan melanjutkan dari bagian terakhir yang dia tinggalkan. Dan agar saat pengujian kita dapat melihat ada sesuatu yang terjadi, kita akan membuat perintah /stat: ini akan menampilkan jumlah pengguna aktif. Kami membuat paket repositori di sebelah paket bot, perintah, layanan. Dalam paket ini kita membuat satu entitas lainnya . Dalam paket entitas kami membuat kelas 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;
}
Di sini Anda dapat melihat bahwa kami memiliki semua anotasi dari paket javax.persistence. Ini adalah anotasi umum yang digunakan untuk semua implementasi ORM. Secara default, Spring Data Jpa menggunakan Hibernate, meskipun implementasi lain dapat digunakan. Berikut adalah daftar anotasi yang kami gunakan:
  • Entitas - menunjukkan bahwa ini adalah entitas untuk bekerja dengan database;
  • Tabel - di sini kita menentukan nama tabel;
  • Id - anotasi menyatakan bidang mana yang akan menjadi Kunci Utama dalam tabel;
  • Kolom - tentukan nama bidang dari tabel.
Selanjutnya, kita membuat antarmuka untuk bekerja dengan database. Biasanya nama antarmuka tersebut ditulis menggunakan templat - EntiryNameRepository. Kami akan memiliki Repositori pengguna Telegram:
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();
}
Di sini Anda dapat melihat bagaimana saya menambahkan metode findAllByActiveTrue() yang tidak saya terapkan di mana pun. Namun hal itu tidak akan menghentikannya untuk bekerja. Spring Data akan memahami bahwa ia perlu mendapatkan semua catatan dari tabel tg_user yang bidang aktifnya = true . Kami menambahkan layanan untuk bekerja dengan entitas TelegramUser (kami menggunakan inversi ketergantungan dari SOLID dalam konteks bahwa layanan entitas lain tidak dapat berkomunikasi secara langsung dengan repositori entitas lain - hanya melalui layanan entitas tersebut). Kami membuat layanan TelegramUserService dalam paket, yang untuk saat ini akan memiliki beberapa metode: menyimpan pengguna, mendapatkan pengguna berdasarkan ID-nya dan menampilkan daftar pengguna aktif. Pertama kita membuat antarmuka 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);
}
Dan faktanya, implementasi 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);
   }
}
Di sini perlu dicatat bahwa kita menggunakan injeksi ketergantungan (memperkenalkan instance kelas) dari objek TelegramuserRepository menggunakan anotasi Autowired , dan pada konstruktor. Anda dapat melakukan ini untuk sebuah variabel, tetapi ini adalah pendekatan yang direkomendasikan oleh tim Spring Framework kepada kami.

Menambahkan statistik untuk bot

Selanjutnya Anda perlu memperbarui perintah /start dan /stop. Saat perintah /start digunakan, Anda perlu menyimpan pengguna baru di database dan mengaturnya ke aktif = true. Dan bila ada /stop, perbarui data pengguna: set aktif = false. Mari kita perbaiki kelas 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);
   }
}
Di sini kita juga meneruskan objek TelegramuserService ke konstruktor, yang dengannya kita akan menyimpan pengguna baru. Selanjutnya, dengan menggunakan kenikmatan Opsional di Java, logika berikut berfungsi: jika kita memiliki pengguna di database, kita cukup mengaktifkannya, jika tidak, kita membuat pengguna aktif baru. Perintah Berhenti:
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);
               });
   }
}
Kami meneruskan TelegramServiceTest ke StopCommand dengan cara yang sama. Logika tambahannya adalah ini: jika kami memiliki pengguna dengan ID obrolan seperti itu, kami menonaktifkannya, yaitu kami menyetel aktif = salah. Bagaimana Anda bisa melihatnya dengan mata kepala sendiri? Mari buat perintah baru /stat, yang akan menampilkan statistik bot. Pada tahap ini, statistik sederhana akan tersedia untuk semua pengguna. Kedepannya kami akan membatasinya dan memberikan akses hanya untuk administrator. Akan ada satu entri dalam statistik: jumlah pengguna bot aktif. Untuk melakukan ini, tambahkan nilai STAT("/stat") ke CommandName. Selanjutnya, buat kelas 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));
   }
}
Semuanya sederhana di sini: kami mendapatkan daftar semua pengguna aktif menggunakan metode retrieveAllActiveUsers dan mendapatkan ukuran koleksinya. Sekarang kita juga perlu memperbarui kelas-kelas menaik: CommandContainer dan JavarushTelegramBot sehingga mereka belajar mentransfer layanan baru yang kita perlukan. Wadah Perintah:
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);
   }

}
Di sini kami menambahkan perintah baru ke peta dan meneruskannya melalui konstruktor TelegramUserService. Namun di bot itu sendiri, hanya konstruktornya yang akan berubah:
@Autowired
public JavarushTelegramBot(TelegramUserService telegramUserService) {
   this.commandContainer = new CommandContainer(new SendBotMessageServiceImpl(this), telegramUserService);
}
Sekarang kita meneruskan TelegramUserService sebagai argumen, menambahkan anotasi Autowired. Artinya kita akan menerimanya dari Konteks Aplikasi. Kami juga akan memperbarui kelas HelpCommand sehingga perintah statistik baru muncul di deskripsi.

Pengujian manual

Mari kita luncurkan database dari docker-compose-test.yml dan metode utama di kelas JavarushTelegramBotApplication. Selanjutnya kita menulis satu set perintah:
  • /stat - kami berharap jika database kosong, tidak akan ada orang yang menggunakan bot ini;
  • /mulai - memulai bot;
  • /stat - sekarang kami berharap bot akan digunakan oleh 1 orang;
  • /stop - menghentikan bot;
  • /stat - kami perkirakan akan ada 0 orang lagi yang menggunakannya.
"Proyek Java dari A sampai Z": Menambahkan segala sesuatu yang berhubungan dengan database.  Bagian 2 - 2Jika hasilnya sama untuk Anda, kami dapat mengatakan bahwa fungsinya berfungsi dengan benar dan bot berfungsi dengan baik. Jika terjadi kesalahan, tidak masalah: kami memulai ulang metode utama dalam mode debug dan dengan jelas menelusuri seluruh jalur untuk menemukan kesalahannya.

Kami menulis dan memperbarui tes

Karena kami mengubah konstruktornya, kami juga perlu memperbarui kelas pengujian. Di kelas AbstrakCommandTest , kita perlu menambahkan bidang lain - kelas TelegramUserService , yang diperlukan untuk tiga perintah:
protected TelegramUserService telegramUserService = Mockito.mock(TelegramUserService.class);
Selanjutnya, mari perbarui metode init() di CommandContainer :
@BeforeEach
public void init() {
   SendBotMessageService sendBotMessageService = Mockito.mock(SendBotMessageService.class);
   TelegramUserService telegramUserService = Mockito.mock(TelegramUserService.class);
   commandContainer = new CommandContainer(sendBotMessageService, telegramUserService);
}
Di StartCommand Anda perlu memperbarui metode getCommand() :
@Override
Command getCommand() {
   return new StartCommand(sendBotMessageService, telegramUserService);
}
Juga di StopCommand:
@Override
Command getCommand() {
   return new StopCommand(sendBotMessageService, telegramUserService);
}
Selanjutnya, mari kita lihat tes baru. Mari kita buat tes tipikal untuk 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);
   }
}
Ini sederhana. Sekarang mari kita bicara tentang bagaimana kita akan menguji bekerja dengan database. Yang kami lakukan sebelumnya hanyalah pengujian unit. Tes integrasi menguji integrasi antara beberapa bagian aplikasi. Misalnya aplikasi dan database. Di sini semuanya akan menjadi lebih rumit, karena untuk pengujian kita memerlukan database yang dikerahkan. Oleh karena itu, ketika kita menjalankan pengujian secara lokal, kita harus menjalankan database dari docker-compose-test.yml. Untuk menjalankan pengujian ini, Anda perlu menjalankan seluruh aplikasi SpringBoot. Kelas pengujian memiliki anotasi SpringBootTest yang akan memulai aplikasi. Namun pendekatan ini tidak akan berhasil bagi kami, karena saat aplikasi diluncurkan, bot telegram juga akan diluncurkan. Namun ada kontradiksi di sini. Pengujian akan dijalankan secara lokal di mesin kami dan secara publik melalui GitHub Actions. Agar pengujian dapat lulus dengan peluncuran seluruh aplikasi, kita harus menjalankannya dengan data yang valid di bot telegram: yaitu, berdasarkan nama dan tokennya... Oleh karena itu, kami memiliki dua opsi:
  1. Jadi jadikan nama dan token bot tersebut publik dan berharap semuanya baik-baik saja, tidak ada yang akan menggunakannya dan mengganggu kita.
  2. Temukan cara lain.
Saya memilih opsi kedua. Pengujian SpringBoot memiliki anotasi DataJpaTest , yang dibuat agar saat menguji database, kami hanya menggunakan kelas yang kami perlukan dan membiarkan yang lain. Tapi ini cocok untuk kami, karena bot telegram tidak akan diluncurkan sama sekali. Ini berarti tidak perlu memberikan nama dan token yang valid!))) Kami akan mendapatkan tes di mana kami akan memeriksa apakah metode yang diterapkan Spring Data untuk kami berfungsi seperti yang kami harapkan. Penting untuk dicatat di sini bahwa kami menggunakan anotasi @ActiveProfiles("test") untuk menentukan penggunaan profil pengujian. Dan inilah yang kita perlukan agar kita dapat menghitung properti yang benar untuk database kita. Sebaiknya siapkan database sebelum menjalankan pengujian kami. Ada pendekatan seperti itu untuk masalah ini: tambahkan anotasi Sql ke pengujian dan teruskan kumpulan nama skrip yang perlu dijalankan sebelum memulai pengujian:
@Sql(scripts = {"/sql/clearDbs.sql", "/sql/telegram_users.sql"})
Bagi kami, mereka akan ditempatkan di sepanjang jalur ./src/test/resources/ + jalur yang ditentukan dalam anotasi. Berikut tampilannya:
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);
Ini adalah hasil pengujian TelegramUserRepositoryIT kami (seperti yang Anda lihat, nama pengujian integrasi akan berbeda - kami menambahkan IT, bukan Pengujian):
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());
   }
}
Kami menulis pengujiannya, tetapi muncul pertanyaan: apa yang akan terjadi dengan peluncuran proses CI kami di GitHub? Itu tidak akan memiliki database. Untuk saat ini hanya akan ada bangunan berwarna merah. Untuk melakukan ini, kami memiliki tindakan GitHub, di mana kami dapat mengonfigurasi peluncuran build kami. Sebelum menjalankan pengujian, Anda perlu menambahkan peluncuran database dengan pengaturan yang diperlukan. Ternyata, tidak banyak contoh di Internet, jadi saya menyarankan Anda untuk menyimpannya di suatu tempat. Mari perbarui file .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
Sekarang ada blok Set up MySQL baru . Di dalamnya kita menambahkan MySQL ke proses CI kita, sekaligus mendefinisikan variabel yang kita perlukan. Sekarang kami telah menambahkan semua yang kami inginkan. Tahap terakhir adalah mendorong perubahan dan melihat bahwa build akan lolos dan menjadi ramah lingkungan.

Memperbarui dokumentasi

Mari perbarui versi proyek dari 0.3.0-SNAPSHOT ke 0.4.0-SNAPSHOT di pom.xml dan tambahkan juga ke RELEASE_NOTES:
## 0.4.0-SNAPSHOT

*   JRTB-1: added repository layer.
Setelah semua ini, kami membuat permintaan komit, dorong dan tarik. Dan yang terpenting, bangunan kami ramah lingkungan!"Proyek Java dari A sampai Z": Menambahkan segala sesuatu yang berhubungan dengan database.  Bagian 2 - 3

Tautan yang berguna:

Semua perubahan dapat dilihat di sini pada permintaan tarik yang dibuat . Terima kasih semuanya telah membaca.

Daftar semua materi dalam seri ini ada di awal artikel ini.

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