๋ณธ๋ฌธ ๋ฐ”๋กœ๊ฐ€๊ธฐ
๐Ÿ–ฅ๏ธ ์ตœ์ข…ํ”„๋กœ์ ํŠธ/โœ๏ธ TIL

[ TIL ] ์ตœ์ข… ํ”„๋กœ์ ํŠธ_Day 30

by carrot0911 2025. 3. 11.

์˜ค๋Š˜ ์ง„ํ–‰ํ•œ ๋‚ด์šฉ๋“ค ๐Ÿง 

ํ…Œ์ŠคํŠธ ์ฝ”๋“œ ์ž‘์„ฑํ•˜๊ธฐ

searchHistoryTaskHendler

๋”๋ณด๊ธฐ
package com.project.cheerha.domain.searchhistory.scheduler;

import com.project.cheerha.domain.searchhistory.entity.SearchHistory;
import com.project.cheerha.domain.searchhistory.repository.SearchHistoryRepository;
import com.project.cheerha.domain.user.entity.User;
import com.project.cheerha.domain.user.service.UserFindByService;
import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.DisplayName;
import org.junit.jupiter.api.Test;
import org.junit.jupiter.api.extension.ExtendWith;
import org.mockito.ArgumentCaptor;
import org.mockito.Captor;
import org.mockito.InjectMocks;
import org.mockito.Mock;
import org.mockito.junit.jupiter.MockitoExtension;
import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.data.redis.core.ZSetOperations;

import java.util.Collections;
import java.util.HashSet;
import java.util.List;
import java.util.Set;

import static org.assertj.core.api.Assertions.assertThat;
import static org.mockito.Mockito.*;

@ExtendWith(MockitoExtension.class)
public class SearchHistoryTaskHandlerTest {

    @InjectMocks
    private SearchHistoryTaskHandler searchHistoryTaskHandler;

    @Mock
    private UserFindByService userFindByService;

    @Mock
    private SearchHistoryRepository searchHistoryRepository;

    @Mock
    private RedisTemplate<String, String> redisTemplate;

    @Mock
    private ZSetOperations<String, String> zSetOperations;

    @Captor
    private ArgumentCaptor<List<SearchHistory>> searchHistoryCaptor;

    @BeforeEach
    void setUp() {
        when(redisTemplate.opsForZSet()).thenReturn(zSetOperations);
    }

    @Test
    @DisplayName("Redis์— ๊ฒ€์ƒ‰ ๊ธฐ๋ก์ด ์กด์žฌํ•˜๊ณ  ์ค‘๋ณต๋˜์ง€ ์•Š์€ ๊ฒ€์ƒ‰์–ด๊ฐ€ ์žˆ์œผ๋ฉด DB์— ์ €์žฅ")
    void Redis์—_์ค‘๋ณต๋˜์ง€_์•Š์€_๊ฒ€์ƒ‰์–ด_์žˆ์œผ๋ฉด_DB_์ €์žฅ() {
        // given
        Long userId = 1L;
        String key = "user:" + userId + ":search_history";
        User mockUser = User.toEntity("test@gmail.com", "์‚ฌ์šฉ์ž", 27, 1, "password123");

        Set<String> redisSearchTermSet = new HashSet<>(Set.of("Elasticsearch", "Docker"));
        Set<String> existinfSearchTermSet = new HashSet<>(Set.of("Elasticsearch"));

        when(redisTemplate.keys("user:*:search_history")).thenReturn(Set.of(key));
        when(userFindByService.findById(userId)).thenReturn(mockUser);
        when(zSetOperations.range(key, 0, -1)).thenReturn(redisSearchTermSet);
        when(searchHistoryRepository.findNamesByUserId(userId)).thenReturn(existinfSearchTermSet);

        // when
        searchHistoryTaskHandler.handle(Collections.emptyMap());

        // then
        verify(searchHistoryRepository, times(1)).saveAll(searchHistoryCaptor.capture());

        List<SearchHistory> capturedList = searchHistoryCaptor.getValue();

        assertThat(capturedList).hasSize(1);
        assertThat(capturedList.get(0).getName()).isEqualTo("Docker");
    }

    @Test
    @DisplayName("Redis์— ๊ฒ€์ƒ‰ ๊ธฐ๋ก์ด ์žˆ์ง€๋งŒ ๋ชจ๋‘ ์ค‘๋ณต๋œ ๊ฒฝ์šฐ DB์— ์ €์žฅํ•˜์ง€ ์•Š์Œ")
    void Redis์—_๊ฒ€์ƒ‰_๊ธฐ๋ก์ด_์žˆ์ง€๋งŒ_์ค‘๋ณต๋œ_๊ฒฝ์šฐ_DB์—_์ €์žฅํ•˜์ง€_์•Š์Œ() {
        // given
        Long userId = 2L;
        String key = "user:" + userId + ":search_history";
        User mockUser = User.toEntity("test2@gmail.com", "์‚ฌ์šฉ์ž2", 25, 3, "password123");

        Set<String> redisSearchTermSet = new HashSet<>(Set.of("Elasticsearch", "Spring"));
        Set<String> existingSearchTermSet = new HashSet<>(Set.of("Elasticsearch", "Spring"));

        when(redisTemplate.keys("user:*:search_history")).thenReturn(Set.of(key));
        when(userFindByService.findById(userId)).thenReturn(mockUser);
        when(zSetOperations.range(key, 0, -1)).thenReturn(redisSearchTermSet);
        when(searchHistoryRepository.findNamesByUserId(userId)).thenReturn(existingSearchTermSet);

        // when
        searchHistoryTaskHandler.handle(Collections.emptyMap());

        // then
        verify(searchHistoryRepository, never()).saveAll(any());
    }
}

์ตœ์ข… ํ”„๋กœ์ ํŠธ ๋ธŒ๋กœ์…” ์ž‘์„ฑํ•˜๊ธฐ

๋‚ด๊ฐ€ ์ง„ํ–‰ํ–ˆ๋˜ ๋ถ€๋ถ„์„ ๋ธŒ๋กœ์…”์— ์ฑ„์›Œ๋„ฃ์—ˆ๋‹ค!
๋ฌธ์„œํ™”๋ฅผ ๋‹ค ํ•ด๋†จ์–ด๋„ ๊ธ€์„ ์ž‘์„ฑํ•˜๋Š” ๊ฒƒ์ด ์ด๋ ‡๊ฒŒ ์–ด๋ ต๋‹ค๋Š” ๊ฒƒ์„ ๋‹ค์‹œ ํ•œ๋ฒˆ ๊นจ๋‹ซ๋Š” ์ˆœ๊ฐ„์ด์—ˆ๋‹ค..

 

์˜ค๋Š˜ ํ•˜๋ฃจ ์ •๋ฆฌ ๐Ÿ™„

์˜ค๋žœ๋งŒ์— ์ ์–ด๋ณด๋Š” ํ•˜๋ฃจ ์ •๋ฆฌ..

์ตœ๊ทผ์— ์ตœ์ข… ํ”„๋กœ์ ํŠธ๋ฅผ ์ง„ํ–‰ํ•˜๋ฉด์„œ ๋„ˆ๋ฌด ์ •์‹ ์ด ์—†์—ˆ๊ณ , ํ•˜๋ฃจ ์ •๋ฆฌํ•˜๋Š” ๋ง์„ ์ ์„ ์‹œ๊ฐ„๋„ ์—†์—ˆ๋˜ ๊ฒƒ ๊ฐ™๋‹ค.. (์‚ฌ์‹ค ์ ์„ ์‹œ๊ฐ„์ด ์—†์—ˆ๋‹ค๋Š” ๊ฑด ํ•‘๊ณ„์ผ ์ˆ˜๋„ ์žˆ๋‹ค..ใ…Žใ…Ž)
์ตœ์ข… ํ”„๋กœ์ ํŠธ๋ฅผ ์ง„ํ–‰ํ•˜๋ฉด์„œ ์ข‹์€ ํŒ€์›๋ถ„๋“ค๊ณผ ํ•จ๊ป˜ ํ˜‘์—…ํ•˜๋ฉด์„œ ๋‚˜ ์Šค์Šค๋กœ๋„ ๋งŽ์ด ์„ฑ์žฅํ•œ ์‹œ๊ฐ„์ด์—ˆ๋˜ ๊ฒƒ ๊ฐ™๋‹ค.
CRUD๋งŒ ๊ตฌํ˜„ํ•  ์ค„ ์•Œ๋˜ ๋‚ด๊ฐ€ Elasticsearch๋ฅผ ์‚ฌ์šฉํ•˜๊ณ  ์žˆ๊ณ , ํ•„์š”ํ•œ ๊ธฐ๋Šฅ์„ ์–ด๋–ป๊ฒŒ๋“  ๊ตฌํ˜„ํ•ด๋‚ด๊ณ  ์žˆ์œผ๋‹ˆ๊นŒ!!
๋ฌผ๋ก  ์•„์ง ๋ถ€์กฑํ•œ ์ ์ด ๋งŽ๋‹ค๊ณ  ์ƒ๊ฐํ•œ๋‹ค. AWS ๊ด€๋ จ ์ง€์‹๋„ ๊ฑฐ์˜ ์—†๊ณ , CI/CD์— ๋Œ€ํ•œ ๊ฐœ๋…๋„ ์—†๋‹ค. ์ด๋Ÿฐ ๋ถ€๋ถ„์€ ์ตœ์ข… ํ”„๋กœ์ ํŠธ๊ฐ€ ๋๋‚˜๊ณ  ๋‚˜๋ฉด ํ•™์Šตํ•˜๋Š” ์‹œ๊ฐ„์„ ๋”ฐ๋กœ ๋งŒ๋“ค์–ด์„œ ๊ณต๋ถ€ํ•ด์•ผ ํ•  ๊ฒƒ ๊ฐ™๋‹ค.

CRUD๋งŒ ๊ตฌํ˜„ํ•˜๋˜ ํ”„๋กœ์ ํŠธ๋“ค์„ ํ–ˆ์„ ๋•Œ๋Š” "์ด๊ฒƒ๋งŒ ๊ตฌํ˜„ํ•˜๋ฉด ๋˜๋Š”๊ตฌ๋‚˜!" ๋ผ๊ณ  ์ƒ๊ฐํ–ˆ์—ˆ๋Š”๋ฐ ์šฐ๋ฌผ ์•ˆ ๊ฐœ๊ตฌ๋ฆฌ์˜€๋‹ค..
๊ทธ ๋’ค์—๋Š” ์—„์ฒญ๋‚˜๊ฒŒ ๋งŽ์€ ๊ธฐ์ˆ  ์Šคํƒ๊ณผ ๊ฐœ๋…๋“ค์ด ์ˆจ์–ด์žˆ์—ˆ๋‹ค.

ํ”„๋กœ์ ํŠธ๋ฅผ ์ง„ํ–‰ํ•˜๋ฉด์„œ ๊ธฐ๋กํ•ด๋‘” "5๋ถ„ ๊ธฐ๋ก ๋ณด๋“œ"์™€ "๊ฐœ๋ฐœ ์ผ์ง€"๋ฅผ ํ™œ์šฉํ•ด์„œ ํ”„๋กœ์ ํŠธ๊ฐ€ ๋๋‚˜๋ฉด ๋‹ค์‹œ ํ•œ๋ฒˆ ๋‚ด๊ฐ€ ํ•œ ๋‚ด์šฉ๋“ค์„ ๋ณต์Šตํ•˜๊ณ  ์ •๋ฆฌํ•˜๋Š” ์‹œ๊ฐ„๋„ ๊ฐ€์ ธ์•ผ ํ•  ๊ฒƒ ๊ฐ™๋‹ค.
๊ฐ€์žฅ ์ค‘์š”ํ•œ ๊ฒƒ์€ ๋‚ด๊ฐ€ ํ•œ ๋‚ด์šฉ์„ ๊นŒ๋จน์ง€ ์•Š๋Š” ๊ฒƒ์ด๋‹ค. ๊ธฐ์–ต๋ ฅ์ด ์ตœ๋Œ€ํ•œ ์‚ด์•„์žˆ์„ ๋•Œ ํ•œ๋ฒˆ ๋” ์ •๋ฆฌํ•˜๋ฉด์„œ ๋‚ด์šฉ๋“ค์„ ๋ณต์Šตํ•˜๊ณ  ์ •๋ฆฌํ•˜๋ฉด ๋จธ๋ฆฌ์— ๋” ์ž˜ ๋‚จ์„ ๊ฒƒ์ด๋‹ค.

๋ญ”๊ฐ€ ์š”์ฆ˜ ํ˜„ํƒ€๋„ ๊ณ„์† ์ฐพ์•„์˜ค๊ณ  ๋ฌด๊ธฐ๋ ฅํ•œ ๋‚ ๋“ค์ด ๊ณ„์†๋˜๊ณ  ์žˆ๋‹ค..
๋ญ”๊ฐ€ ์ž์กด๊ฐ๋„ ๋‚ฎ์•„์ง€๊ณ .. ๋‚ด๊ฐ€ ๊ณผ์—ฐ ์ทจ์—…์„ ํ•  ์ˆ˜ ์žˆ์„๊นŒ?? ํ•˜๋Š” ์˜๋ฌธ์ด ๊ฐ€๋“ํ•ด์ง€๊ณ  ์žˆ๋‹ค.
๊ต์œก์„ ๊ฐ™์ด ๋“ฃ๋Š” ์‚ฌ๋žŒ๋“ค์€ "๋‚˜์—๊ฒŒ ์„ฑ์‹คํ•˜๋‹ˆ๊นŒ ์ž˜ํ•  ๊ฒƒ์ด๋‹ค.", "์ง€๊ธˆ๋„ ์ถฉ๋ถ„ํžˆ ์ž˜ํ•˜๊ณ  ์žˆ๋‹ค.", "์ง€๊ธˆ์ฒ˜๋Ÿผ ๊พธ์ค€ํžˆ ๋…ธ๋ ฅํ•œ๋‹ค๋ฉด ๋ฌด์กฐ๊ฑด ๊ฐ€๋Šฅํ•˜๋‹ค." ๋ผ๋Š” ๋ง์„ ํ•ด์ฃผ์‹œ์ง€๋งŒ ์ •์ž‘ ๋‚˜๋Š” ๊ณ„์† ์ž์‹ ๊ฐ..? ์ž์กด๊ฐ..? ์ด ๋‚ฎ์•„์ง€๋Š” ๊ฒƒ ๊ฐ™๋‹ค.
์–ผ๋ฅธ ๊ธฐ์šด์„ ์ฐจ๋ฆฌ๊ณ  ๋‹ค์‹œ ์—ด์‹ฌํžˆ ํ•ด์•ผ ํ•˜๋Š”๋ฐ.. ์ƒ๊ฐ๋ณด๋‹ค ์‰ฝ์ง€ ์•Š์€ ๊ฒƒ ๊ฐ™๋‹ค์œผ์•„์•„์•„..

๋…ธ๋ž˜๋ฅผ ๋“ค์œผ๋ฉด์„œ ์ ๋‹ค ๋ณด๋‹ˆ๊นŒ ๊ฐ์„ฑ์ ์œผ๋กœ ๋ณ€ํ•ด์„œ ๋ญ”๊ฐ€ ๋” ์ ์€ ๊ฒƒ ๊ฐ™์€๋ฐ..
์–ผ๋ฅธ ๋‹ค์‹œ ํž˜์„ ๋‚ด๊ณ  ๊พธ์ค€ํ•˜๊ฒŒ ๊ณต๋ถ€ํ•  ์ˆ˜ ์žˆ๋Š” ํ™˜๊ฒฝ์„ ๋งŒ๋“ค์–ด์•ผ๊ฒ ๋‹ค.
ํ”„๋กœ์ ํŠธ๊ฐ€ ๋๋‚˜๊ฐ„๋‹ค๊ณ  ์•ˆ์‹ฌํ•˜๊ณ  ์žˆ์œผ๋ฉด ์•ˆ๋œ๋‹ค!!

์ทจ์—…ํ•˜๊ฒŒ ๋˜๋Š” ๊ทธ๋‚ ๊นŒ์ง€.. ํž˜์„ ๋‚ด๋ณด์ž!! ํŒŒ์ดํŒ…!!!

 

๋‚ด์ผ ๊ณ„ํš โฐ

  • ๋ธŒ๋กœ์…” ์ž‘์„ฑํ•˜๊ธฐ

+ ์ถ”๊ฐ€ ๊ณ„ํš์ด ์ƒ๊ธธ ์ˆ˜๋„ ์žˆ์Šต๋‹ˆ๋‹ค~_~