JavaRush /Java блогу /Random-KY /Биз топтун макалаларына жазылууну алып салабыз - "Java до...
Roman Beekeeper
Деңгээл

Биз топтун макалаларына жазылууну алып салабыз - "Java долбоору Адан Яга чейин"

Группада жарыяланган
Баарыңарга салам, менин кымбаттуу досторум, келечектеги улук программалык камсыздоо инженерлери. Биз телеграмма ботун иштеп чыгууну улантабыз. Долбоорубуздун бул кадамында биз программалык мааниге караганда көрүнүктүү мааниге ээ үч тапшырманы карап чыгабыз. Биз белгилүү бир топтон жаңы макалаларга жазылууну кантип алып салууну үйрөнүшүбүз керек: ботту өчүрүү үчүн /stop командасын колдонуңуз, ал эми аны иштетүү үчүн /start буйругун колдонуңуз. Мындан тышкары, бардык суроо-талаптар жана жаңыртуулар боттун активдүү колдонуучуларына гана тиешелүү. Адаттагыдай эле, бардык өзгөртүүлөрдү алуу жана жаңысын түзүү үчүн негизги бутагын жаңыртабыз : STEP_7_JRTB-7. Бул бөлүктө биз жазылууну жок кылуу жөнүндө сүйлөшөбүз жана окуялардын 5 вариантын карап чыгабыз - бул кызыктуу болот.

JRTB-7: топтон жаңы макалаларга жазылууну алып салуу

Бардык колдонуучулар жаңы макалалар жөнүндө эскертмелерди албаш үчүн жазылуусун жок кылууну каалаары анык. Анын логикасы жазылууну кошуу логикасына абдан окшош болот. Эгерде биз бир гана буйрукту жөнөтсөк, жооп катары биз эмнени жок кылуу керектигин түшүнүү үчүн колдонуучу мурунтан эле жазылган топтордун тизмесин жана алардын идентификаторлорун алабыз. Ал эми колдонуучу топ менен бирге ID жөнөтсө, биз жазылууну жок кылабыз. Ошондуктан, келгиле, телеграмма бот тарабынан бул буйрукту иштеп чыгалы.
  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 an objectи менен иштөөнүн дагы эки ыкмасын кошууга туура келди - ID боюнча маалымат базасынан алуу жана an objectтин өзүн сактоо. Бул ыкмалардын баары жөн гана даяр репозиторий ыкмаларын чакырат. Мен жазылууну жок кылуу жөнүндө өзүнчө айтып берем. Берorштер базасынын схемасында бул көптөн көпкө процесси үчүн жооптуу table жана бул байланышты жок кылуу үчүн андагы жазууну жок кылыш керек. Бул маалымат базасы жагынан жалпы түшүнүктү колдонсок. Бирок биз Жазгы маалыматтарды колдонобуз жана демейки боюнча Күтүү режими бар, ал муну башкача кыла алат. Биз GroupSub an objectисин алабыз, ага байланышкан бардык колдонуучулар тартылат. Колдонуучулардын бул жыйнагынан биз керектүүсүн алып салабыз жана groupSubту кайра маалымат базасына сактайбыз, бирок бул колдонуучу жок. Ошентип жазгы маалыматтар биз каалаган нерсени түшүнүп, жазууну жок кылат. "Java долбоору Адан Яга": Топтогу макалаларга жазылууну алып салуу - 1"Java долбоору Адан Яга чейин": Топтогу макалаларга жазылууну алып салуу - 2Колдонуучуну тез алып салуу үчүн мен EqualsAndHashCode annotationсын TelegramUser үчүн коштум, эч кандай көйгөйлөр болбошу үчүн 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;
}
Натыйжада баары биз каалагандай болуп кетти. Командада бир нече мүмкүн болгон окуялар бар, андыктан алардын ар бири үчүн жакшы тест жазуу сонун идея. Тесттер жөнүндө айтсам: мен аларды жазып жатып, логикада кемчorк таап, аны өндүрүшкө чыгара электе оңдоп койдум. Эгерде эч кандай сыноо болбогондо, ал канчалык тез аныкталмак экени белгисиз. 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 буйругун тапшырганыңызда жана топторго жазылуулар болгондо;
  • жараксыз топ ID өткөндө, мисалы, /deleteGroupSub abc ;
  • күтүлгөндөй баары туура жок кылынган сценарий;
  • топтун ID жарактуу болгон сценарий, бирок мындай топ маалымат базасында жок.
Көрүнүп тургандай, бул сценарийлердин бардыгын сыноолор менен жабуу керек. Мен жазып жатып, жакшыраак тесттерди жазуу үчүн тестирлөө курстарынан өтүү керектигин түшүндүм. Бул ар кандай варианттарды туура издөөгө жардам берет деп ойлойм. Туура, келечекке ойлор. Андан кийин, сиз азыр жазылууну жок кыла турган / 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 Жана IDEA аркылуу SpringBootту ишке киргизебиз. Мен бот менен кат алышууну толугу менен тазалап, кайра баштайм. Мен бул команда менен иштөөдө пайда болушу мүмкүн болгон бардык варианттарды карап чыгам. "Java долбоору Адан Яга": Топтогу макалаларга жазылууну алып салуу - 4Скриншоттон көрүнүп тургандай, бардык варианттар өтүп, ийгorктүү болду.
Достор! Долбоордун жаңы codeу качан чыгарылганын дароо билгиңиз келеби? Жаңы макала качан чыгат? Менин Telegram каналыма кошулуңуз . Ал жерде мен өзүмдүн макалаларымды, ойлорумду жана ачык булактарды иштеп чыгууларды чогуу чогултам.
Долбоорубуздун versionсын 0.6.0-SNAPSHOT versionсына жаңыртабыз. Биз RELEASE_NOTES.md жаңыртып, жаңы versionда жасалган иштердин сүрөттөмөсүн кошобуз:
## 0.6.0-SNAPSHOT * JRTB-7: топко жазылууну жок кылуу мүмкүнчүлүгү кошулду.
Код иштейт, ал үчүн тесттер жазылган: тапшырманы репозиторийге түртүп, тартуу өтүнүчүн түзүүгө убакыт келди."Java долбоору Адан Яга чейин": Топтогу макалаларга жазылууну алып салуу - 5

Аяктоонун ордуна

Биз көптөн бери биздин долбоордук кеңешти карай элекпиз, бирок чоң өзгөрүүлөр болду: "Java долбоору Адан Яга": Топтогу макалаларга жазылууну алып салуу - 65 гана тапшырма калды. Башкача айтканда, биз жолдун эң аягында турабыз. Бир аз калды. Бул макалалардын сериясы сентябрдын ортосунан бери, башкача айтканда, 7 айдан бери иштеп келе жатканын белгилей кетүү өзгөчө кызыктуу!!! Мен бул идеяны ойлоп тапканымда, бул мынчалык көпкө созулат деп күткөн эмесмин. Ошол эле учурда, мен натыйжага абдан ыраазымын! Достор, эгер макалада эмне болуп жатканы түшүнүксүз болсо, комментарийлерде суроолорду бериңиз. Ушундай жол менен мен бир нерсени жакшыраак сүрөттөш керек экенин жана бир нерсени кошумча түшүндүрүү керек экенин билем. Адаттагыдай эле, лайк басыңыз - жазылыңыз - коңгуроону басыңыз, биздин долбоорго жылдыз бериңиз , комментарий жазыңыз жана макаланы баалаңыз! баарына рахмат. Кийинки бөлүгүнө чейин. /stop & /start буйруктары аркылуу ботту өчүрүү жана активдештирүү жана аларды кантип мыкты колдонуу керектиги жөнүндө жакында сүйлөшөбүз . Көрүшкөнчө!

Сериядагы бардык материалдардын тизмеси ушул макаланын башында.

Комментарийлер
TO VIEW ALL COMMENTS OR TO MAKE A COMMENT,
GO TO FULL VERSION