JavaRush /Java блогы /Random-KK /Біз топтағы мақалаларға жазылуды алып тастаймыз - «Java ж...
Roman Beekeeper
Деңгей

Біз топтағы мақалаларға жазылуды алып тастаймыз - «Java жобасы А-дан Я-ға дейін»

Топта жарияланған
Баршаңызға сәлем, қымбатты достарым, болашақ аға бағдарламалық жасақтама инженерлері. Біз телеграмма ботын дамытуды жалғастырамыз. Жобамыздың бұл қадамында біз бағдарлама мәнінен гөрі көрінетін мәнге ие үш тапсырманы қарастырамыз. Белгілі бір топтан жаңа мақалаларға жазылуды қалай жою керектігін үйренуіміз керек: ботты өшіру үшін /stop пәрменін пайдаланыңыз және оны белсендіру үшін /start пәрменін пайдаланыңыз. Сонымен қатар, барлық сұраулар мен жаңартулар тек белсенді бот пайдаланушыларына қатысты. Әдеттегідей, барлық өзгерістерді алу және жаңасын жасау үшін негізгі тармақты жаңартамыз : STEP_7_JRTB-7. Бұл бөлімде біз жазылымды жою туралы сөйлесеміз және оқиғалардың 5 нұсқасын қарастырамыз - бұл қызықты болады.

JRTB-7: топтан жаңа мақалаларға жазылуды жою

Жаңа мақалалар туралы хабарландыруларды алмау үшін барлық пайдаланушылар жазылымын жойғысы келетіні анық. Оның логикасы жазылымды қосу логикасына өте ұқсас болады. Егер біз тек бір пәрменді жіберсек, жауап ретінде біз нені жою керектігін түсіну үшін пайдаланушы бұрыннан жазылған топтардың тізімін және олардың идентификаторларын аламыз. Ал егер пайдаланушы топтың идентификаторын командамен бірге жіберсе, біз жазылымды жоямыз. Сондықтан, осы пәрменді телеграмма бот жағынан әзірлейік.
  1. Жаңа пәрменнің атын қосамыз - /deleteGroupSub , ал CommandName ішінде - жол:

    DELETE_GROUP_SUB("/deleteGroupSub")

  2. Содан кейін DeleteGroupSubCommand пәрменін жасайық :

    package com.github.javarushcommunity.jrtb.command;
    
    import com.github.javarushcommunity.jrtb.repository.entity.GroupSub;
    import com.github.javarushcommunity.jrtb.repository.entity.TelegramUser;
    import com.github.javarushcommunity.jrtb.service.GroupSubService;
    import com.github.javarushcommunity.jrtb.service.SendBotMessageService;
    import com.github.javarushcommunity.jrtb.service.TelegramUserService;
    import org.springframework.util.CollectionUtils;
    import org.telegram.telegrambots.meta.api.objects.Update;
    
    import javax.ws.rs.NotFoundException;
    import java.util.List;
    import java.util.Optional;
    import java.util.stream.Collectors;
    
    import static com.github.javarushcommunity.jrtb.command.CommandName.DELETE_GROUP_SUB;
    import static com.github.javarushcommunity.jrtb.command.CommandUtils.getChatId;
    import static com.github.javarushcommunity.jrtb.command.CommandUtils.getMessage;
    import static java.lang.String.format;
    import static org.apache.commons.lang3.StringUtils.SPACE;
    import static org.apache.commons.lang3.StringUtils.isNumeric;
    
    /**
    * Delete Group subscription {@link Command}.
    */
    public class DeleteGroupSubCommand implements Command {
    
       private final SendBotMessageService sendBotMessageService;
       private final TelegramUserService telegramUserService;
       private final GroupSubService groupSubService;
    
       public DeleteGroupSubCommand(SendBotMessageService sendBotMessageService, GroupSubService groupSubService,
                                    TelegramUserService telegramUserService) {
           this.sendBotMessageService = sendBotMessageService;
           this.groupSubService = groupSubService;
           this.telegramUserService = telegramUserService;
       }
    
       @Override
       public void execute(Update update) {
           if (getMessage(update).equalsIgnoreCase(DELETE_GROUP_SUB.getCommandName())) {
               sendGroupIdList(getChatId(update));
               return;
           }
           String groupId = getMessage(update).split(SPACE)[1];
           String chatId = getChatId(update);
           if (isNumeric(groupId)) {
               Optional<GroupSub> optionalGroupSub = groupSubService.findById(Integer.valueOf(groupId));
               if (optionalGroupSub.isPresent()) {
                   GroupSub groupSub = optionalGroupSub.get();
                   TelegramUser telegramUser = telegramUserService.findByChatId(chatId).orElseThrow(NotFoundException::new);
                   groupSub.getUsers().remove(telegramUser);
                   groupSubService.save(groupSub);
                   sendBotMessageService.sendMessage(chatId, format("Удалил подписку на группу: %s", groupSub.getTitle()));
               } else {
                   sendBotMessageService.sendMessage(chatId, "Не нашел такой группы =/");
               }
           } else {
               sendBotMessageService.sendMessage(chatId, "неправильный формат ID группы.\n " +
                       "ID должно быть целым положительным числом");
           }
       }
    
       private void sendGroupIdList(String chatId) {
           String message;
           List<GroupSub> groupSubs = telegramUserService.findByChatId(chatId)
                   .orElseThrow(NotFoundException::new)
                   .getGroupSubs();
           if (CollectionUtils.isEmpty(groupSubs)) {
               message = "Пока нет подписок на группы. Whatбы добавить подписку напиши /addGroupSub";
           } else {
               message = "Whatбы удалить подписку на группу - передай комадну вместе с ID группы. \n" +
                       "Например: /deleteGroupSub 16 \n\n" +
                       "я подготовил список всех групп, на которые ты подписан) \n\n" +
                       "Name группы - ID группы \n\n" +
                       "%s";
    
           }
           String userGroupSubData = groupSubs.stream()
                   .map(group -> format("%s - %s \n", group.getTitle(), group.getId()))
                   .collect(Collectors.joining());
    
           sendBotMessageService.sendMessage(chatId, format(message, userGroupSubData));
       }
    }

Ол үшін GroupSub нысанымен жұмыс істеудің тағы екі әдісін қосу керек болды - дерекқордан идентификатор бойынша шығарып алу және нысанның өзін сақтау. Барлық осы әдістер жай ғана дайын репозиторий әдістерін шақырады. Жазылымды жою туралы бөлек айтамын. Дерекқор схемасында бұл «көптен көпке» процесіне жауап беретін кесте және бұл қатынасты жою үшін ондағы жазбаны жою керек. Бұл деректер базасы бөлігінде жалпы түсінікті қолданатын болсақ. Бірақ біз көктемгі деректерді пайдаланамыз және әдепкі бойынша күту күйі бар, ол мұны басқаша жасай алады. Біз GroupSub нысанын аламыз, оған байланысты барлық пайдаланушылар тартылады. Осы пайдаланушылар жинағынан біз өзімізге керектісін алып тастаймыз және groupSub дерекқорына қайта сақтаймыз, бірақ бұл пайдаланушысыз. Осылайша Spring Data біздің қалағанымызды түсінеді және жазбаны жояды. «А-дан Я-ға Java жобасы»: Топтан мақалаларға жазылуды жою - 1«А-дан Я-ға Java жобасы»: Топтан мақалаларға жазылуды жою - 2Пайдаланушыны жылдам жою үшін мен TelegramUser үшін EqualsAndHashCode annotationсын қостым, GroupSub тізімін алып тастап, ешқандай проблемалар туындамайды. Бізге қажет пайдаланушысы бар пайдаланушыларды жинау бойынша жою әдісі деп аталады. Бұл TelegramUser үшін келесідей:
@Data
@Entity
@Table(name = "tg_user")
@EqualsAndHashCode(exclude = "groupSubs")
public class TelegramUser {

   @Id
   @Column(name = "chat_id")
   private String chatId;

   @Column(name = "active")
   private boolean active;

   @ManyToMany(mappedBy = "users", fetch = FetchType.EAGER)
   private List<GroupSub> groupSubs;
}
Нәтижесінде бәрі біз қалағандай болды. Командада бірнеше ықтимал оқиғалар болуы мүмкін, сондықтан олардың әрқайсысы үшін жақсы сынақ жазу тамаша идея. Тесттер туралы айтатын болсақ: мен оларды жазып жатқанда, мен логикадан ақау тауып, оны өндіріске жібермес бұрын түзетіп алдым. Егер сынақ болмаса, оның қаншалықты тез анықталатыны белгісіз. DeleteGroupSubCommandTest:
package com.github.javarushcommunity.jrtb.command;

import com.github.javarushcommunity.jrtb.repository.entity.GroupSub;
import com.github.javarushcommunity.jrtb.repository.entity.TelegramUser;
import com.github.javarushcommunity.jrtb.service.GroupSubService;
import com.github.javarushcommunity.jrtb.service.SendBotMessageService;
import com.github.javarushcommunity.jrtb.service.TelegramUserService;
import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.DisplayName;
import org.junit.jupiter.api.Test;
import org.mockito.Mockito;
import org.telegram.telegrambots.meta.api.objects.Update;

import java.util.ArrayList;
import java.util.Optional;

import static com.github.javarushcommunity.jrtb.command.AbstractCommandTest.prepareUpdate;
import static com.github.javarushcommunity.jrtb.command.CommandName.DELETE_GROUP_SUB;
import static java.util.Collections.singletonList;

@DisplayName("Unit-level testing for DeleteGroupSubCommand")
class DeleteGroupSubCommandTest {

   private Command command;
   private SendBotMessageService sendBotMessageService;
   GroupSubService groupSubService;
   TelegramUserService telegramUserService;


   @BeforeEach
   public void init() {
       sendBotMessageService = Mockito.mock(SendBotMessageService.class);
       groupSubService = Mockito.mock(GroupSubService.class);
       telegramUserService = Mockito.mock(TelegramUserService.class);

       command = new DeleteGroupSubCommand(sendBotMessageService, groupSubService, telegramUserService);
   }

   @Test
   public void shouldProperlyReturnEmptySubscriptionList() {
       //given
       Long chatId = 23456L;
       Update update = prepareUpdate(chatId, DELETE_GROUP_SUB.getCommandName());

       Mockito.when(telegramUserService.findByChatId(String.valueOf(chatId)))
               .thenReturn(Optional.of(new TelegramUser()));

       String expectedMessage = "Пока нет подписок на группы. Whatбы добавить подписку напиши /addGroupSub";

       //when
       command.execute(update);

       //then
       Mockito.verify(sendBotMessageService).sendMessage(chatId.toString(), expectedMessage);
   }

   @Test
   public void shouldProperlyReturnSubscriptionLit() {
       //given
       Long chatId = 23456L;
       Update update = prepareUpdate(chatId, DELETE_GROUP_SUB.getCommandName());
       TelegramUser telegramUser = new TelegramUser();
       GroupSub gs1 = new GroupSub();
       gs1.setId(123);
       gs1.setTitle("GS1 Title");
       telegramUser.setGroupSubs(singletonList(gs1));
       Mockito.when(telegramUserService.findByChatId(String.valueOf(chatId)))
               .thenReturn(Optional.of(telegramUser));

       String expectedMessage = "Whatбы удалить подписку на группу - передай комадну вместе с ID группы. \n" +
               "Например: /deleteGroupSub 16 \n\n" +
               "я подготовил список всех групп, на которые ты подписан) \n\n" +
               "Name группы - ID группы \n\n" +
               "GS1 Title - 123 \n";

       //when
       command.execute(update);

       //then
       Mockito.verify(sendBotMessageService).sendMessage(chatId.toString(), expectedMessage);
   }

   @Test
   public void shouldRejectByInvalidGroupId() {
       //given
       Long chatId = 23456L;
       Update update = prepareUpdate(chatId, String.format("%s %s", DELETE_GROUP_SUB.getCommandName(), "groupSubId"));
       TelegramUser telegramUser = new TelegramUser();
       GroupSub gs1 = new GroupSub();
       gs1.setId(123);
       gs1.setTitle("GS1 Title");
       telegramUser.setGroupSubs(singletonList(gs1));
       Mockito.when(telegramUserService.findByChatId(String.valueOf(chatId)))
               .thenReturn(Optional.of(telegramUser));

       String expectedMessage = "неправильный формат ID группы.\n " +
               "ID должно быть целым положительным числом";

       //when
       command.execute(update);

       //then
       Mockito.verify(sendBotMessageService).sendMessage(chatId.toString(), expectedMessage);
   }

   @Test
   public void shouldProperlyDeleteByGroupId() {
       //given

       /// prepare update object
       Long chatId = 23456L;
       Integer groupId = 1234;
       Update update = prepareUpdate(chatId, String.format("%s %s", DELETE_GROUP_SUB.getCommandName(), groupId));


       GroupSub gs1 = new GroupSub();
       gs1.setId(123);
       gs1.setTitle("GS1 Title");
       TelegramUser telegramUser = new TelegramUser();
       telegramUser.setChatId(chatId.toString());
       telegramUser.setGroupSubs(singletonList(gs1));
       ArrayList<TelegramUser> users = new ArrayList<>();
       users.add(telegramUser);
       gs1.setUsers(users);
       Mockito.when(groupSubService.findById(groupId)).thenReturn(Optional.of(gs1));
       Mockito.when(telegramUserService.findByChatId(String.valueOf(chatId)))
               .thenReturn(Optional.of(telegramUser));

       String expectedMessage = "Удалил подписку на группу: GS1 Title";

       //when
       command.execute(update);

       //then
       users.remove(telegramUser);
       Mockito.verify(groupSubService).save(gs1);
       Mockito.verify(sendBotMessageService).sendMessage(chatId.toString(), expectedMessage);
   }

   @Test
   public void shouldDoesNotExistByGroupId() {
       //given
       Long chatId = 23456L;
       Integer groupId = 1234;
       Update update = prepareUpdate(chatId, String.format("%s %s", DELETE_GROUP_SUB.getCommandName(), groupId));


       Mockito.when(groupSubService.findById(groupId)).thenReturn(Optional.empty());

       String expectedMessage = "Не нашел такой группы =/";

       //when
       command.execute(update);

       //then
       Mockito.verify(groupSubService).findById(groupId);
       Mockito.verify(sendBotMessageService).sendMessage(chatId.toString(), expectedMessage);
   }
}
Мұнда әрбір сынақ жеке сценарийді тексереді және еске сала кетейін, олардың тек бесеуі бар:
  • /deleteGroupSub пәрменін жай ғана тапсырған кезде және топтық жазылымдар болмаған кезде;
  • /deleteGroupSub пәрменін жай ғана тапсырған кезде және топтарға жазылулар болған кезде;
  • жарамсыз топ идентификаторы жіберілгенде, мысалы, /deleteGroupSub abc ;
  • күткендей бәрі дұрыс жойылатын сценарий;
  • топ идентификаторы жарамды, бірақ мұндай топ дерекқорда болмаған кездегі сценарий.
Көріп отырғаныңыздай, бұл сценарийлердің барлығы сынақтармен қамтылуы керек. Жазу барысында мен жақсы тесттер жазу үшін тестілеу курстарынан өту керек екенін түсіндім. Бұл әртүрлі нұсқаларды дұрыс іздеуге көмектеседі деп ойлаймын. Дұрыс, болашақ туралы ойлар. Одан кейін жазылымды жоюға болатын /help пәрменіне сипаттаманы қосу керек . Оны жазылымдармен жұмыс істеу бөліміне орналастырайық. Әрине, бұл пәрмен жұмыс істеуі үшін оның инициализациясын CommandContainer«А-дан Я-ға Java жобасы»: Топтан мақалаларға жазылуды жою - 3 ішіне қосу керек :
.put(DELETE_GROUP_SUB.getCommandName(),
       new DeleteGroupSubCommand(sendBotMessageService, groupSubService, telegramUserService))
Енді сіз функционалдылықты сынақ ботында тексере аласыз. Біз дерекқорымызды docker-compose-test.yml арқылы іске қосамыз: docker-compose -f docker-compose-test.yml up және SpringBoot бағдарламасын IDEA арқылы іске қосамыз. Мен ботпен хат алмасуды толығымен тазалап, қайтадан бастаймын. Мен осы топпен жұмыс істеу кезінде туындауы мүмкін барлық нұсқаларды қарастырамын. «Java жобасы А-дан Я-ға»: Топтан мақалаларға жазылуды жою - 4Скриншоттан көріп отырғаныңыздай, барлық опциялар сәтті өтті.
Достар! Жобаның жаңа codeы қашан шыққанын бірден білгіңіз келе ме? Жаңа мақала қашан шығады? Менің Telegram каналыма қосылыңыз . Онда мен мақалаларымды, ойларымды және ашық дереккөзді әзірлеуді бірге жинаймын.
Біз жобамыздың нұсқасын 0.6.0-SNAPSHOT нұсқасына жаңартамыз, RELEASE_NOTES.md жаңа нұсқасында жасалған әрекеттердің сипаттамасын қоса отырып, жаңартамыз:
## 0.6.0-SNAPSHOT * JRTB-7: топтық жазылымды жою мүмкіндігі қосылды.
Код жұмыс істейді, ол үшін сынақтар жазылды: тапсырманы репозиторийге енгізу және тарту сұрауын жасау уақыты келді.«А-дан Я-ға Java жобасы»: Топтан мақалаларға жазылуды жою - 5

Аяқтаудың орнына

Біз жобалық тақтаны көптен бері қараған жоқпыз, бірақ үлкен өзгерістер болды: «А-дан Я-ға Java жобасы»: Топтан мақалаларға жазылуды жою - 6бар болғаны 5 тапсырма қалды. Яғни, сіз бен біз қазірдің өзінде жолдың ең соңындамыз. Кішкене қалды. Бұл мақалалар сериясы қыркүйек айының ортасынан бастап, яғни 7 ай бойы жұмыс істеп тұрғанын атап өту өте қызықты!!! Мен бұл идеяны ойлап тапқан кезде, мен оны ұзақ уақыт алады деп ойламадым. Сонымен қатар, мен нәтижеге ризамын! Достар, егер мақалада не болып жатқаны түсініксіз болса, түсініктемелерде сұрақтар қойыңыз. Осылайша мен бір нәрсені жақсырақ сипаттау керек екенін және бір нәрсені қосымша түсіндіру керек екенін білемін. Әдеттегідей, лайк басыңыз - жазылыңыз - қоңырауды басыңыз, жобамызға жұлдызша беріңіз , пікір қалдырыңыз және мақаланы бағалаңыз! Барлығыңа Рақмет. Келесі бөлімге дейін. Жақында біз /stop & /start пәрмендері арқылы ботты өшіруді және белсендіруді қосу және оларды қалай жақсы пайдалану керектігі туралы сөйлесетін боламыз. Көріскенше!

Сериядағы барлық материалдардың тізімі осы мақаланың басында.

Пікірлер
TO VIEW ALL COMMENTS OR TO MAKE A COMMENT,
GO TO FULL VERSION