JavaRush /בלוג Java /Random-HE /אנחנו מוסיפים כל מה שקשור למסד הנתונים. (חלק 2) - "פרויקט...
Roman Beekeeper
רָמָה

אנחנו מוסיפים כל מה שקשור למסד הנתונים. (חלק 2) - "פרויקט ג'אווה מא' עד ת'"

פורסם בקבוצה
שלום לכולם. הרשו לי להזכיר לכם: בחלק הראשון הוספנו את Flyway. בוא נמשיך.

הוספת מסד נתונים ל-docker-compose.yml

השלב הבא הוא הגדרת עבודה עם מסד הנתונים ב-docer-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 באותו אופן כמו לבוט הטלגרם - דרך משתני סביבה. עשיתי זאת כדי שיהיה לנו רק מקום אחד בו אנו מגדירים את הערכים של שם המשתמש של מסד הנתונים והסיסמה שלו. אנחנו מעבירים אותם לתמונת docker של האפליקציה שלנו ולמיכל docker של מסד הנתונים שלנו. בשלב הבא עלינו לעדכן את ה-Dockerfile כדי ללמד את SpringBoot שלנו לקבל משתנים עבור מסד הנתונים.
FROM adoptopenjdk/openjdk11:ubi
ARG JAR_FILE=target/*.jar
ENV BOT_NAME=test.javarush_community_bot
ENV BOT_TOKEN=1375780501:AAE4A6Rz0BSnIGzeu896OjQnjzsMEG6_uso
ENV BOT_DB_USERNAME=jrtb_db_user
ENV BOT_DB_PASSWORD=jrtb_db_password
COPY ${JAR_FILE} app.jar
ENTRYPOINT ["java","-Dspring.datasource.password=${BOT_DB_PASSWORD}", "-Dbot.username=${BOT_NAME}", "-Dbot.token=${BOT_TOKEN}", "-Dspring.datasource.username=${BOT_DB_USERNAME}", "-jar", "app.jar"]
כעת אנו מוסיפים משתני מסד נתונים ל- Dockerfile:
ENV BOT_DB_USERNAME=jrtb_db_user
ENV BOT_DB_PASSWORD=jrtb_db_password
ערכי המשתנים יהיו שונים. אלה שנעביר ל-Dockerfile, לעומת זאת, דורשים ערכי ברירת מחדל, אז הכנסתי כמה. את השורה האחרונה נרחיב בשני אלמנטים, בעזרתם נעביר את שם המשתמש והסיסמה של DB להפעלת האפליקציה:
"-Dspring.datasource.password=${BOT_DB_PASSWORD}", "-Dbot.username=${BOT_NAME}"
השורה האחרונה ב-Dockerfile (שמתחילה ב-ENTRYPOINT) חייבת להיות ללא גלישה. אם תבצע העברה, הקוד הזה לא יעבוד. השלב האחרון הוא עדכון הקובץ start.sh כדי להעביר משתנים למסד הנתונים.
#!/bin/bash

# Pull new changes
git pull

# Prepare Jar
mvn clean
mvn package

# Ensure, that docker-compose stopped
docker-compose stop

# Add environment variables
export BOT_NAME=$1
export BOT_TOKEN=$2
export BOT_DB_USERNAME='prod_jrtb_db_user'
export BOT_DB_PASSWORD='Pap9L9VVUkNYj99GCUCC3mJkb'

# Start new deployment
docker-compose up --build -d
אנחנו כבר יודעים להוסיף משתני סביבה לפני הפעלת docker-compose. כדי לעשות זאת, אתה רק צריך לבצע export var_name=var_value. לכן, אנו מוסיפים רק שתי שורות:
export BOT_DB_USERNAME='prod_jrtb_db_user'
export BOT_DB_PASSWORD='Pap9L9VVUkNYj99GCUCC3mJkb'
כאן אנו מגדירים את שם המשתמש והסיסמה של מסד הנתונים. כמובן, ניתן יהיה להעביר את המשתנים הללו בעת הפעלת סקריפט ה-bash, כפי שאנו עושים עבור השם והאסימון של הבוט. אבל נראה לי שזה מיותר. כדי לגשת בפועל למסד הנתונים, עליך לדעת את ה-IP של השרת עליו ייפרס מסד הנתונים, ולהימצא ברשימת כתובות ה-IP המותרות לבקשה. מבחינתי, זה כבר מספיק. הבסיס הונח: עכשיו אתה יכול לעשות דברים מובנים יותר עבור מפתח - לכתוב קוד. לפני כן, עשינו מה שעושים מהנדסי DevOps - הקמת הסביבה.

הוספת שכבת מאגר

בדרך כלל לאפליקציה יש שלוש שכבות:
  1. בקרים הם נקודות הכניסה לאפליקציה.
  2. שירותים הם המקום שבו ההיגיון העסקי עובד. זה כבר יש לנו חלקית: SendMessageService הוא נציג מפורש של ההיגיון העסקי.
  3. מאגרים הם מקום לעבוד עם מסד נתונים. במקרה שלנו, מדובר בבוט טלגרם.
כעת נוסיף שכבה שלישית - מאגרים. כאן נשתמש בפרויקט מהאקוסיסטם של Spring - Spring Data. אתה יכול לקרוא על מה זה במאמר זה על Habré . עלינו לדעת ולהבין מספר נקודות:
  1. לא נצטרך לעבוד עם JDBC: נעבוד ישירות עם הפשטות גבוהות יותר. כלומר, אחסן POJOs התואמים לטבלאות במסד הנתונים. נכנה מחלקות כאלה entity , כפי שהם נקראים רשמית ב- Java Persistence API (זהו סט נפוץ של ממשקים לעבודה עם מסד נתונים דרך ORM, כלומר הפשטה על פני עבודה עם JDBC). תהיה לנו מחלקה של entity שנשמור במסד הנתונים, והם ייכתבו בדיוק לטבלה שאנחנו צריכים. נקבל את אותם אובייקטים בעת חיפוש במסד הנתונים.
  2. Spring Data מציע להשתמש במערך הממשקים שלהם: JpaRepository , CrudRepository וכו'... יש ממשקים נוספים: רשימה מלאה ניתן למצוא כאן . היופי הוא שאפשר להשתמש בשיטות שלהם מבלי ליישם אותן(!). יתרה מכך, קיימת תבנית מסוימת באמצעותה ניתן לכתוב שיטות חדשות בממשק, והן יוטמעו אוטומטית.
  3. האביב מפשט את ההתפתחות שלנו ככל שהוא יכול. לשם כך, עלינו ליצור ממשק משלנו ולרשת מאלה שתוארו לעיל. וכדי ש-Spring ידע שהוא צריך להשתמש בממשק הזה, הוסף את הערת Repository.
  4. אם צריך לכתוב שיטה לעבודה עם מסד נתונים שלא קיים אז גם זו לא בעיה – נכתוב אותה. אני אראה לך מה ואיך לעשות שם.
במאמר זה, נעבוד עם הוספה לאורך כל הנתיב של TelegramUser ונראה את החלק הזה כדוגמה. את השאר נרחיב במשימות אחרות. כלומר, כאשר אנו מבצעים את הפקודה /start, נכתוב active = true למסד הנתונים של המשתמש שלנו. זה אומר שהמשתמש משתמש בבוט. אם המשתמש כבר נמצא במסד הנתונים, נעדכן את השדה active = true. בעת ביצוע הפקודה /stop, לא נמחק את המשתמש, אלא רק נעדכן את השדה הפעיל ל-false, כך שאם המשתמש ירצה להשתמש שוב בבוט, הוא יוכל להפעיל אותו ולהמשיך מאיפה שהפסיק. וכדי שבבדיקה נוכל לראות שמשהו קורה, ניצור פקודת /stat: היא תציג את מספר המשתמשים הפעילים. אנו יוצרים חבילת מאגר ליד חבילות הבוט, הפקודה, השירות. בחבילה זו אנו יוצרים ישות אחת נוספת . בחבילת 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, אם כי ניתן להשתמש ביישומים אחרים. להלן רשימה של הערות שאנו משתמשים בהן:
  • ישות - מציין שמדובר בישות לעבודה עם מסד הנתונים;
  • טבלה - כאן אנו מגדירים את שם הטבלה;
  • Id - ההערה אומרת איזה שדה יהיה המפתח הראשי בטבלה;
  • עמודה - קבע את שם השדה מהטבלה.
לאחר מכן, אנו יוצרים ממשק לעבודה עם מסד הנתונים. בדרך כלל, השמות של ממשקים כאלה נכתבים באמצעות התבנית - EntiryNameRepository. יהיה לנו מאגר Telegramuser:
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 שהשדה הפעיל שלה = true . אנו מוסיפים שירות לעבודה עם הישות TelegramUser (אנו משתמשים בהיפוך תלות מ-SOLID בהקשר ששירותים של גורמים אחרים אינם יכולים לתקשר ישירות עם המאגר של ישות אחרת - רק באמצעות השירות של אותו ישות). אנו יוצרים שירות 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);
   }
}
כאן יש לציין שאנו משתמשים בהזרקת תלות (הכנסת מופע מחלקה) של האובייקט TelegramuserRepository באמצעות הערת Autowired , ועל הבנאי. אתה יכול לעשות זאת עבור משתנה, אבל זו הגישה שצוות Spring Framework ממליץ לנו.

הוספת סטטיסטיקה עבור הבוט

בשלב הבא עליך לעדכן את הפקודות /start ו-/stop. כאשר נעשה שימוש בפקודה /start, עליך לשמור את המשתמש החדש במסד הנתונים ולהגדיר אותו ל-active = true. וכאשר יש /stop, עדכן את נתוני המשתמש: set active = false. בוא נתקן את המחלקה StartCommand :
package com.github.javarushcommunity.jrtb.command;

import com.github.javarushcommunity.jrtb.repository.entity.TelegramUser;
import com.github.javarushcommunity.jrtb.service.SendBotMessageService;
import com.github.javarushcommunity.jrtb.service.TelegramUserService;
import org.telegram.telegrambots.meta.api.objects.Update;

/**
* Start {@link Command}.
*/
public class StartCommand implements Command {

   private final SendBotMessageService sendBotMessageService;
   private final TelegramUserService telegramUserService;

   public final static String START_MESSAGE = "Привет. Я Javarush Telegram Bot. Я помогу тебе быть в курсе последних " +
           "статей тех авторов, котрые тебе интересны. Я еще маленький и только учусь.";

   public StartCommand(SendBotMessageService sendBotMessageService, TelegramUserService telegramUserService) {
       this.sendBotMessageService = sendBotMessageService;
       this.telegramUserService = telegramUserService;
   }

   @Override
   public void execute(Update update) {
       String chatId = update.getMessage().getChatId().toString();

       telegramUserService.findByChatId(chatId).ifPresentOrElse(
               user -> {
                   user.setActive(true);
                   telegramUserService.save(user);
               },
               () -> {
                   TelegramUser telegramUser = new TelegramUser();
                   telegramUser.setActive(true);
                   telegramUser.setChatId(chatId);
                   telegramUserService.save(telegramUser);
               });

       sendBotMessageService.sendMessage(chatId, START_MESSAGE);
   }
}
כאן אנו מעבירים גם את אובייקט TelegramuserService לבנאי, איתו נשמור את המשתמש החדש. יתר על כן, באמצעות התענוגות של Optional ב-Java, ההיגיון הבא עובד: אם יש לנו משתמש במסד הנתונים, אנחנו פשוט הופכים אותו לפעיל, אם לא, אנחנו יוצרים משתמש פעיל חדש. 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 באותו אופן. ההיגיון הנוסף הוא כזה: אם יש לנו משתמש עם מזהה צ'אט כזה, אנחנו מבטלים אותו, כלומר, קבענו אקטיבי = שקר. איך אתה יכול לראות את זה במו עיניך? בואו ניצור פקודה חדשה /stat, שתציג את הנתונים הסטטיסטיים של הבוט. בשלב זה, אלו יהיו נתונים סטטיסטיים פשוטים הזמינים לכל המשתמשים. בעתיד, נגביל אותו ונאפשר גישה רק למנהלי מערכת. בסטטיסטיקה תהיה ערך אחד: מספר משתמשי הבוט הפעילים. לשם כך, הוסף את הערך STAT("/stat") ל-CommandName. לאחר מכן, צור את המחלקה StatCommand :
package com.github.javarushcommunity.jrtb.command;

import com.github.javarushcommunity.jrtb.service.SendBotMessageService;
import com.github.javarushcommunity.jrtb.service.TelegramUserService;
import org.springframework.beans.factory.annotation.Autowired;
import org.telegram.telegrambots.meta.api.objects.Update;

/**
* Statistics {@link Command}.
*/
public class StatCommand implements Command {

   private final TelegramUserService telegramUserService;
   private final SendBotMessageService sendBotMessageService;

   public final static String STAT_MESSAGE = "Javarush Telegram Bot использует %s человек.";

   @Autowired
   public StatCommand(SendBotMessageService sendBotMessageService, TelegramUserService telegramUserService) {
       this.sendBotMessageService = sendBotMessageService;
       this.telegramUserService = telegramUserService;
   }

   @Override
   public void execute(Update update) {
       int activeUserCount = telegramUserService.retrieveAllActiveUsers().size();
       sendBotMessageService.sendMessage(update.getMessage().getChatId().toString(), String.format(STAT_MESSAGE, activeUserCount));
   }
}
הכל פשוט כאן: אנו מקבלים רשימה של כל המשתמשים הפעילים בשיטת 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);
}
כעת אנו מעבירים את TelegramUserService כארגומנט, ומוסיפים את ההערה Autowired. המשמעות היא שנקבל אותו מהקשר של היישום. נעדכן גם את המחלקה HelpCommand כך שתופיע פקודת סטטיסטיקה חדשה בתיאור.

בדיקה ידנית

בואו נפעיל את מסד הנתונים מ- docker-compose-test.yml ואת השיטה הראשית במחלקה JavarushTelegramBotApplication. לאחר מכן נכתוב קבוצה של פקודות:
  • /stat - אנו מצפים שאם מסד הנתונים ריק, לא יהיו אנשים שמשתמשים בבוט הזה;
  • /start - הפעל את הבוט;
  • /stat - כעת אנו מצפים שהבוט ישמש אדם אחד;
  • /stop - עצור את הבוט;
  • /stat - אנו מצפים ששוב יהיו 0 אנשים שמשתמשים בו.
"פרויקט ג'אווה מא' עד ת'": הוספת כל מה שקשור למסד הנתונים.  חלק 2 - 2אם התוצאה זהה עבורך, אנו יכולים לומר שהפונקציונליות עבדה כמו שצריך והבוט עובד כמו שצריך. אם משהו משתבש, זה לא משנה: אנו מפעילים מחדש את השיטה הראשית במצב ניפוי באגים ועוברים בבירור את כל הנתיב כדי למצוא מה הייתה השגיאה.

אנו כותבים ומעדכנים מבחנים

מכיוון ששינינו את הבנאים, נצטרך לעדכן גם את מחלקות הבדיקה. במחלקה AbstractCommandTest , עלינו להוסיף שדה אחד נוסף - מחלקת TelegramUserService , הנחוצה לשלוש פקודות:
protected TelegramUserService telegramUserService = Mockito.mock(TelegramUserService.class);
לאחר מכן, בואו נעדכן את שיטת init() ב-CommandContainer :
@BeforeEach
public void init() {
   SendBotMessageService sendBotMessageService = Mockito.mock(SendBotMessageService.class);
   TelegramUserService telegramUserService = Mockito.mock(TelegramUserService.class);
   commandContainer = new CommandContainer(sendBotMessageService, telegramUserService);
}
ב-StartCommand עליך לעדכן את שיטת getCommand() :
@Override
Command getCommand() {
   return new StartCommand(sendBotMessageService, telegramUserService);
}
גם ב-StopCommand:
@Override
Command getCommand() {
   return new StopCommand(sendBotMessageService, telegramUserService);
}
לאחר מכן, בואו נסתכל על המבחנים החדשים. בואו ניצור מבחן טיפוסי עבור StatCommand :
package com.github.javarushcommunity.jrtb.command;

import static com.github.javarushcommunity.jrtb.command.CommandName.STAT;
import static com.github.javarushcommunity.jrtb.command.StatCommand.STAT_MESSAGE;

public class StatCommandTest extends AbstractCommandTest {
   @Override
   String getCommandName() {
       return STAT.getCommandName();
   }

   @Override
   String getCommandMessage() {
       return String.format(STAT_MESSAGE, 0);
   }

   @Override
   Command getCommand() {
       return new StatCommand(sendBotMessageService, telegramUserService);
   }
}
זה פשוט. עכשיו בואו נדבר על איך נבדוק את העבודה עם מסד הנתונים. כל מה שעשינו קודם זה בדיקות יחידה. מבחן אינטגרציה בודק אינטגרציה בין חלקים מרובים של אפליקציה. למשל, יישומים ומסדי נתונים. כאן הכל יהיה יותר מסובך, כי לצורך בדיקה אנחנו צריכים מסד נתונים פרוס. לכן, כאשר אנו מריצים את הבדיקות שלנו באופן מקומי, אנו חייבים שבסיס הנתונים יפעל מ-docker-compose-test.yml. כדי להפעיל בדיקה זו, עליך להפעיל את כל יישום SpringBoot. לשיעור המבחן יש הערת SpringBootTest שתתחיל את האפליקציה. אבל הגישה הזו לא תעבוד עבורנו, כי כאשר האפליקציה תושק, גם בוט הטלגרם יופעל. אבל יש כאן סתירה. הבדיקות יופעלו הן באופן מקומי במחשב שלנו והן באופן ציבורי באמצעות GitHub Actions. על מנת שהבדיקות יעברו עם השקת האפליקציה כולה, עלינו להריץ אותן עם נתונים תקפים בבוט הטלגרם: כלומר לפי שמו והאסימון שלו... לכן יש לנו שתי אפשרויות:
  1. אז הפוך את השם והאסימון של הבוט לפומבי ותקווה שהכל יהיה בסדר, אף אחד לא ישתמש בזה ויפריע לנו.
  2. תמצא דרך אחרת.
בחרתי באפשרות השנייה. לבדיקת SpringBoot יש את הערת DataJpaTest , שנוצרה כך שכאשר בודקים מסד נתונים, אנו משתמשים רק במחלקות שאנו צריכים ומשאירים אחרים בשקט. אבל זה מתאים לנו, כי בוט הטלגרם לא יופעל כלל. זה אומר שאין צורך להעביר לו שם ואסימון תקפים!))) נקבל בדיקה בה נבדוק שהשיטות ש-Spring Data מיישמת עבורנו עובדות כפי שאנו מצפים. חשוב לציין כאן שאנו משתמשים בביאור @ActiveProfiles("test") כדי לציין את השימוש בפרופיל הבדיקה. וזה בדיוק מה שאנחנו צריכים כדי שנוכל לספור את המאפיינים הנכונים עבור מסד הנתונים שלנו. זה יהיה נחמד להכין מסד נתונים לפני הפעלת הבדיקות שלנו. יש גישה כזו לעניין הזה: הוסף ביאור SQL למבחן והעביר לו אוסף של שמות סקריפטים שצריך להפעיל לפני שמתחילים בבדיקה:
@Sql(scripts = {"/sql/clearDbs.sql", "/sql/telegram_users.sql"})
מבחינתנו הם ימוקם לאורך הנתיב ./src/test/resources/ + הנתיב שצוין בהערה. כך הם נראים:
clearDbs.sql:
DELETE FROM tg_user;

telegram_users.sql:
INSERT INTO tg_user VALUES ("123456789", 1);
INSERT INTO tg_user VALUES ("123456788", 1);
INSERT INTO tg_user VALUES ("123456787", 1);
INSERT INTO tg_user VALUES ("123456786", 1);
INSERT INTO tg_user VALUES ("123456785", 1);
INSERT INTO tg_user VALUES ("123456784", 0);
INSERT INTO tg_user VALUES ("123456782", 0);
INSERT INTO tg_user VALUES ("123456781", 0);
כך תיראה מבחן TelegramUserRepositoryIT שלנו כתוצאה מכך (כפי שאתה יכול לראות, השם לבדיקת אינטגרציה יהיה שונה - אנו מוסיפים IT, לא 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());
   }
}
כתבנו את הבדיקות, אבל נשאלת השאלה: מה יקרה עם השקת תהליך ה-CI שלנו ב-GitHub? לא יהיה לו מסד נתונים. לעת עתה באמת יהיה רק ​​מבנה אדום. לשם כך, יש לנו פעולות GitHub, שבהן נוכל להגדיר את ההשקה של ה-build שלנו. לפני הפעלת הבדיקות, עליך להוסיף השקת מסד נתונים עם ההגדרות הדרושות. כפי שמתברר, אין הרבה דוגמאות באינטרנט, אז אני ממליץ לך לשמור את זה איפשהו. בואו נעדכן את הקובץ .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 שלנו, ובו זמנית מגדירים את המשתנים שאנו צריכים. עכשיו הוספנו את כל מה שרצינו. השלב האחרון הוא לדחוף את השינויים ולראות שהמבנה יעבור ויהיה ירוק.

עדכון התיעוד

בואו נעדכן את גרסת הפרויקט מ-0.3.0-SNAPSHOT ל-0.4.0-SNAPSHOT ב-pom.xml וגם נוסיף ל-RELEASE_NOTES:
## 0.4.0-SNAPSHOT

*   JRTB-1: added repository layer.
אחרי כל זה, אנו יוצרים בקשת התחייבות, דחיפה ומשיכה. והכי חשוב, המבנה שלנו ירוק!"פרויקט ג'אווה מא' עד ת'": הוספת כל מה שקשור למסד הנתונים.  חלק 2 - 3

קישורים שימושיים:

ניתן לראות את כל השינויים כאן בבקשת המשיכה שנוצרה . תודה לכולם על הקריאה.

רשימה של כל החומרים בסדרה נמצאת בתחילת מאמר זה.

הערות
TO VIEW ALL COMMENTS OR TO MAKE A COMMENT,
GO TO FULL VERSION