๐ฑ Redis๋ฅผ ๋์ ํด์ ์บ์ฑ ์์ ์ ์งํํด๋ณด์!!! [3ํ]
๐ค Intro
์ค๋ ์์๋ณผ ๊ฒ์, ์ฐ๋ฆฌ๊ฐ ๊ตฌ์ฑํ Redis๋ฅผ ์ด์ฉํ Service ์ฝ๋๋ฅผ ์ด๋ป๊ฒ ํ์ฉํด์ผ ํ ์ง๋ฅผ test code๋ก ์์๋ณด๊ณ ์ ํ๋ค
๊ทธ์ ํจ๊ป, test code๊ฐ ์๋ ํ๊ฒฝ์์ ๊ฐ๋ฐํ ๋ ์ฐ๋ฆฌ๊ฐ ์ด ๊ตฌ์กฐ๋ฅผ ์ด๋ป๊ฒ ํ์ฉํด์ ์๋น์ค๋ฅผ ๊ตฌ์ฑํ ์ ์์์ง๋ฅผ ๋ฏธ๋ฆฌ ์ดํด๋ณด์.
๐ฉถ Start
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
/*
ํ์ฌ ์ฐ๋ฆฌ๋ docker container ์์ redis๊ฐ ์์ผ๋ฏ๋ก, docker๋ฅผ ์๋์ผ๋ก ์คํํด์ test๋ฅผ ์งํํ๋๋ก ํ์.
์บ์ฑ ๊ธฐ๋ฅ & ์บ์ ํํธ ๋ฐ ๋ฏธ์ค๊ฐ ์ ์์ ์ผ๋ก ๋์ํ๋ ๊ฒ์ ๋ณผ ์ ์๋ค.
*/
@Testcontainers
@SpringBootTest
@Slf4j
class MemberServiceCacheTest {
private static final String REDIS_PASSWORD = System.getenv().get("REDIS_PASSWORD");
@Container
static GenericContainer<?> redis = new GenericContainer<>("redis:7")
.withExposedPorts(6379)
.withCommand("redis-server", "--requirepass", REDIS_PASSWORD);
@DynamicPropertySource
static void redisProps(DynamicPropertyRegistry r) {
r.add("spring.data.redis.host", () -> redis.getHost());
r.add("spring.data.redis.port", () -> redis.getMappedPort(6379));
r.add("spring.data.redis.password", () -> REDIS_PASSWORD);
}
@Autowired
MemberService memberService;
@MockitoBean
MemberRepository memberRepository; // DB ๋์ ๋ชฉ
@Autowired
RedisTemplate<String, Object> redisTemplate; // ๋ด๊ฐ ์ ์ํ ์ปค์คํ
ํ
ํ๋ฆฟ ์ด์ฉ
@BeforeEach
void flush() {
redisTemplate.getConnectionFactory().getConnection().serverCommands().flushDb();
}
@Test
void redis_actually_stores_cache() {
Long id = 1L;
Mockito.when(memberRepository.findBalanceByMemberId(id)).thenReturn(7000);
// Redis๊ฐ ๋น์ด์๋์ง ํ์ธ
Set<String> keysBefore = redisTemplate.keys("*");
log.info("Redis keys before: {}", keysBefore);
// ์ฒซ ๋ฒ์งธ ํธ์ถ - ์บ์ ์ ์ฅ๋จ
int balance = memberService.getBalance(id);
assertThat(balance).isEqualTo(7000);
// Redis์ ํค๊ฐ ์๊ฒผ๋์ง ํ์ธ
Set<String> keysAfter = redisTemplate.keys("*");
log.info("Redis keys after: {}", keysAfter);
// ์ ์ฅ๋ ๊ฐ๋ค ์ถ๋ ฅํด๋ณด๊ธฐ
for (String key : keysAfter) {
Object value = redisTemplate.opsForValue().get(key);
Long ttl = redisTemplate.getExpire(key);
log.info("Key : {}, Value : {}, TTL : {} seconds",key,value,ttl);
}
// Repository๋ 1๋ฒ๋ง ํธ์ถ๋จ (์๋๋ฉด.. ์บ์ฑ์ด ๋์๊ธฐ ๋๋ฌธ์ด๋ค.)
Mockito.verify(memberRepository, Mockito.times(1)).findBalanceByMemberId(id);
}
@Test
void redis_stores_different_cache_types() {
//test์ ์ฌ์ฉํ๊ธฐ ์ํ ๊ฐ์ฒด๋ค
Long id = 1L;
MemberCachingDto dto = MemberCachingDto.builder()
.name("ํผ์ฉํฌ")
.creditLimit(100000)
.build();
int balance = 5000;
String hobby = "์ถ๊ตฌ";
// Repository Mock ์ค์
Mockito.when(memberRepository.findBalanceByMemberId(id)).thenReturn(balance);
Mockito.when(memberRepository.findHobbyByMemberId(id)).thenReturn(hobby);
Mockito.when(memberRepository.findDtoByMemberId(id)).thenReturn(dto);
System.out.println("=== ์บ์ ์ ์ฅ ์ ===");
Set<String> keysBefore = redisTemplate.keys("*");
log.info("Redis keys: {}",keysBefore);
// ์ฌ๋ฌ ์ข
๋ฅ ์บ์ ์ ์ฅ
balance = memberService.getBalance(id);
hobby = memberService.getHobby(id);
dto = memberService.getSnapshot(id);
System.out.println("=== ์บ์ ์ ์ฅ ํ ===");
log.info("Balance: {}, Hobby: {}, dto : {}",balance,hobby,dto);
Set<String> keysAfter = redisTemplate.keys("*");
log.info("Redis keys: {}",keysAfter);
// ๋ชจ๋ ์ ์ฅ๋ ๋ฐ์ดํฐ ์ถ๋ ฅ
for (String key : keysAfter) {
Object value = redisTemplate.opsForValue().get(key);
Long ttl = redisTemplate.getExpire(key);
log.info("Key: {}",key);
log.info("Value: {}",value);
log.info("TTL: {} seconds",ttl);
log.info("Type: {}",redisTemplate.type(key)); //int, String, dto ๋์์ผ ํ๋ค.
}
assertThat(keysAfter.size()).isGreaterThanOrEqualTo(3); // balance, hobby, dto ์ต์ 3๊ฐ
}
@Test
void redis_cache_hit_verification() {
Long id = 1L;
Mockito.when(memberRepository.findBalanceByMemberId(id)).thenReturn(9999);
System.out.println("=== ์ฒซ ๋ฒ์งธ ํธ์ถ (์บ์ ๋ฏธ์ค) ===");
int result1 = memberService.getBalance(id);
Set<String> keys1 = redisTemplate.keys("*");
log.info("Result: {}",result1);
log.info("Redis keys: {}", keys1);
// Repository Mock์ ๋ค๋ฅธ ๊ฐ์ผ๋ก ๋ณ๊ฒฝ (์บ์ ํํธ๋ฉด ์ด ๊ฐ์ด ์๋์์ผ ํจ...๋ฐ๋๊ธฐ ์ ๊ฐ์ด ์์ผ ํ๋๊น)
Mockito.when(memberRepository.findBalanceByMemberId(id)).thenReturn(1111);
System.out.println("=== ๋ ๋ฒ์งธ ํธ์ถ (์บ์ ํํธ) ===");
int result2 = memberService.getBalance(id);
Set<String> keys2 = redisTemplate.keys("*");
log.info("Result: {}",result2);
log.info("Redis keys: {}", keys2);
// ์บ์์์ ๊ฐ์ ธ์จ ๊ฐ์ด๋ฏ๋ก ์ฒซ ๋ฒ์งธ์ ๊ฐ์์ผ ํจ (9999)
assertThat(result2).isEqualTo(9999);
assertThat(result2).isNotEqualTo(1111); // Mock์ ์ ๊ฐ์ด ์๋
// Repository๋ 1๋ฒ๋ง ํธ์ถ๋จ (์บ์ ํํธ)
Mockito.verify(memberRepository, Mockito.times(1)).findBalanceByMemberId(id);
log.info("์บ์ ํํธ ์ฑ๊ณต! Repository๋ {}์ด๊ณ , {}๋ฒ๋ง ํธ์ถ",result2,Mockito.mockingDetails(memberRepository).getInvocations().size());
}
}
์ ์ฒด test code๋ ๋ค์๊ณผ ๊ฐ๋ค. ์ฌ๊ธฐ์ ์ด์ ๊ฐ๊ฐ์ ํ ์คํธ method๊ฐ ๋ฌด์์ ์๋ฏธํ๋์ง, ๊ฐ ์ค์ ์ ๋ฌด์์ ์๋ฏธํ๊ณ ์ ๋ฌ์๋๋์ง๋ฅผ ์์ธํ ์์๋ณด๊ณ ์ ํ๋ค.
Test code 0_๋ ๋์ค ํ ์คํธ ํ๊ฒฝ ์ค์
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
private static final String REDIS_PASSWORD = System.getenv().get("REDIS_PASSWORD");
@Container
static GenericContainer<?> redis = new GenericContainer<>("redis:7")
.withExposedPorts(6379)
.withCommand("redis-server", "--requirepass", REDIS_PASSWORD);
@DynamicPropertySource
static void redisProps(DynamicPropertyRegistry r) {
r.add("spring.data.redis.host", () -> redis.getHost());
r.add("spring.data.redis.port", () -> redis.getMappedPort(6379));
r.add("spring.data.redis.password", () -> REDIS_PASSWORD);
}
@Autowired
MemberService memberService;
@MockitoBean
MemberRepository memberRepository; // DB ๋์ ๋ชฉ
@Autowired
RedisTemplate<String, Object> redisTemplate; // ๋ด๊ฐ ์ ์ํ ์ปค์คํ
ํ
ํ๋ฆฟ ์ด์ฉ
@BeforeEach
void flush() {
redisTemplate.getConnectionFactory().getConnection().serverCommands().flushDb();
}
์ด ๋ถ๋ถ์ด ๋ฐ๋ก redis password๋ฅผ ์ฃผ์ ํ๊ณ docker container๋ฅผ test ํ๊ฒฝ์์ ๋์ธ ์ ์๋๋ก ํ๋ ๋ถ๋ถ์ด๋ค. ๋ํ, ์์ํ ํ๊ฒฝ์ test๋ฅผ ์ํด REDIS ์ ์ฅ์๋ฅผ ํ ๋ฒ ๋น์์ฃผ๋ ์ญํ ์ ํ๊ธฐ๋ ํ๋ค.
ํ๋์ฉ ๋ฏ์ด๋ณด์.
1
2
3
4
5
6
private static final String REDIS_PASSWORD = System.getenv().get("REDIS_PASSWORD");
@Container
static GenericContainer<?> redis = new GenericContainer<>("redis:7")
.withExposedPorts(6379)
.withCommand("redis-server", "--requirepass", REDIS_PASSWORD);
๋์ ๊ฒฝ์ฐ๋, .env ์์ ๋ชจ๋ ์ค์ ํ์ผ์ ๋ณ์๋ฅผ ๋นผ๋์๋๋ฐ, ์ด๋ฅผ ๊ฐ์ ธ์์ ์ฐ๊ธฐ ์ํจ์ด๋ค. System.getenv()๋ฅผ ํตํด env๋ฅผ ๊ฐ์ ธ์์ ๊ทธ ์์ ์๋ REDIS_PASSWORD๋ฅผ ์ฌ์ฉํ๋ค.
GenericContainer<>(โredis:7โ)์ redis:7 ์ด๋ฏธ์ง๋ฅผ ๊ธฐ๋ฐ์ผ๋ก ์ปจํ ์ด๋๋ฅผ ์์ฑํ๋ ๊ฒ์ด๋ค. .withExposedPorts(6379)๋ฅผ ํตํด์ ์ปจํ ์ด๋์ 6379 ํฌํธ๋ฅผ ํธ์คํธ์ ๋๋ค ๊ฐ์ฉ ํฌํธ๋ก ๋งคํํ๋๋ฐ, ์ฐ๋ฆฌ๊ฐ docker๋ก redis ์ด๋ฏธ์ง๋ฅผ ๋์ธ๋ 6379 ํฌํธ๋ฅผ ์ฌ์ฉํ์ผ๋ฏ๋ก ๋ง์ถฐ์ค๋ค.
.withCommand(โredis-serverโ, โโrequirepassโ, REDIS_PASSWORD)๋ฅผ ์ด์ฉํด์ ๊ธฐ๋ณธ ์ํธ๋ฆฌํฌ์ธํธ๋ฅผ ๋ฎ์ด์จ์ ๋น๋ฒ์ด ๊ฑธ๋ฆฐ Redis๋ก ์คํํ ์ ์๊ฒ ๋๋ค. ์ฌ๊ธฐ์ ์ฌ์ฉํ๋ ํจ์ค์๋๋ ํ๊ฒฝ๋ณ์๋ก ๋น ์ ธ ์๋ REDIS_PASSWORD์ด๊ณ , โredis-serverโ, โโrequirepassโ ์ด ๋ช ๋ น์ด๋ redis๋ฅผ ๋น๋ฐ๋ฒํธ๋ก ์คํํ๋ ๋ฐฉ๋ฒ์๋, cmd๋ก ์ ๋ ฅํ๋ ๋ฐฉ๋ฒ๊ณผ ๋์ ์ค์ต ์ฒ๋ผ .conf๋ก ์ ๋ ฅํ๋ ๋ฐฉ๋ฒ์ด ์๋๋ฐ ๊ทธ ์ค cli๋ก ์ ๋ ฅํ๋ ๋ฐฉ๋ฒ์ ์ฌ์ฉํ ๊ฒ์ด๋ค.
redis-server --requirepass mypassword
์ค์ ๋ก ์ด๋ฐ ๋๋์ผ๋ก ์คํ๋๋ค.
1
2
3
4
5
6
@DynamicPropertySource
static void redisProps(DynamicPropertyRegistry r) {
r.add("spring.data.redis.host", () -> redis.getHost());
r.add("spring.data.redis.port", () -> redis.getMappedPort(6379));
r.add("spring.data.redis.password", () -> REDIS_PASSWORD);
}
@DynamicPropertySource๋ฅผ ํตํด ํ ์คํธ ์ปจํ ์ด๋์์ ์ป์ ๋์ ๊ฐ (ํฌํธ, ํธ์คํธ ๋ฑ)์ Spring Boot ์ค์ (application.yml)์ ๋ฑ๋กํด์ค๋ค. ์ฆ, ์ปจํ ์ด๋๋ฅผ ๋์ฐ๋ ์ฝ๋๋ฅผ ์์ฑํ์ผ๋, ์ด๋ฅผ ํตํด ์ค์ ํ์ผ์ ๋ฑ๋กํ๋ค.
host๋ฅผ ํตํด host์ ์ฐ๊ฒฐํ๊ณ , port๋ฅผ ํตํด ๋๋ค ๊ฐ์ฉ ํฌํธ๋ฅผ ๋ถํธ๋ก ์ฐ๊ฒฐํ๋ค.(์ปจํ ์ด๋ ๋ด๋ถ ํฌํธ๊ฐ 6379์ธ ๊ฒ์ด๊ณ , ํธ์คํธ๋ ๋๋ค ๊ฐ์ฉ ํฌํธ๋ก ์ฐ๊ฒฐ๋๋ค.) ์ด๋ ๊ฒ ํ๋ฉด RedisTemplate๊ฐ ์ ๋๋ก ์ฐ๊ฒฐ๋ ์ ์๋ค.
๊ทธ๋ฆฌ๊ณ ๋ง์ง๋ง์ผ๋ก๋ password๋ฅผ ํตํด REDIS_PASSWORD๋ฅผ ์ฃผ์ ๋ฐ๋๋ค. ์ด๋ ๊ฒ ํด์ ์ปจํ ์ด๋์ ์๋ ์ ๋ณด๋ฅผ ์คํ๋ง ๋ถํธ์ ์ฐ๊ฒฐํด์ค๋ค.
1
2
3
4
5
6
7
8
9
10
11
12
13
@Autowired
MemberService memberService;
@MockitoBean
MemberRepository memberRepository; // DB ๋์ ๋ชฉ
@Autowired
RedisTemplate<String, Object> redisTemplate; // ๋ด๊ฐ ์ ์ํ ์ปค์คํ
ํ
ํ๋ฆฟ ์ด์ฉ
@BeforeEach
void flush() {
redisTemplate.getConnectionFactory().getConnection().serverCommands().flushDb();
}
@MockitoBean๋ฅผ ํตํด์ MemberRepository
์ Mockito ๋ชฉ ๊ฐ์ฒด๋ฅผ ๋ฑ๋กํ๋ค. ์ค์ JPA ๋ฆฌํฌ์งํ ๋ฆฌ ๋น์ ๋์ /๋์ฒดํด์ ์ฃผ์
๋๋ฏ๋ก DB๋ฅผ ์ ํ ํ์ง ์๋๋ค๋ ํน์ง์ด ์๋ค.
RedisTemplate<String, Object> redisTemplate์ ํตํด ๋ด๊ฐ ์ ์ํ ์ปค์คํ ํ ํ๋ฆฟ์ ๊ฐ์ ธ์์ ์ฌ์ฉํ๋๋ก ํ๋ค.
Test code 1_๋ ๋์ค ์บ์ฑ ํ ์คํธ
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
@Test
void redis_actually_stores_cache() {
Long id = 1L;
Mockito.when(memberRepository.findBalanceByMemberId(id)).thenReturn(7000);
// Redis๊ฐ ๋น์ด์๋์ง ํ์ธ
Set<String> keysBefore = redisTemplate.keys("*");
log.info("Redis keys before: {}", keysBefore);
// ์ฒซ ๋ฒ์งธ ํธ์ถ - ์บ์ ์ ์ฅ๋จ
int balance = memberService.getBalance(id);
assertThat(balance).isEqualTo(7000);
// Redis์ ํค๊ฐ ์๊ฒผ๋์ง ํ์ธ
Set<String> keysAfter = redisTemplate.keys("*");
log.info("Redis keys after: {}", keysAfter);
// ์ ์ฅ๋ ๊ฐ๋ค ์ถ๋ ฅํด๋ณด๊ธฐ
for (String key : keysAfter) {
Object value = redisTemplate.opsForValue().get(key);
Long ttl = redisTemplate.getExpire(key);
log.info("Key : {}, Value : {}, TTL : {} seconds",key,value,ttl);
}
// Repository๋ 1๋ฒ๋ง ํธ์ถ๋จ (์๋๋ฉด.. ์บ์ฑ์ด ๋์๊ธฐ ๋๋ฌธ์ด๋ค.)
Mockito.verify(memberRepository, Mockito.times(1)).findBalanceByMemberId(id);
}
์ฐ์ , redisTemplate.keys(โ*โ);์ ํตํด ํ์ฌ ์ฐ๊ฒฐ๋ redis์ ํค๊ฐ ์กด์ฌํ๋์ง๋ฅผ ํ์ธํ๋ค. ์์ง ์บ์ฑํ์ง ์์์ผ๋ฏ๋ก ํค๋ ์กด์ฌํ์ง ์์ ๊ฒ์ด๋ค.
memberService.getBalance(id)๋ฅผ ํตํด ๋ฃ์ผ๋ฉด, ๋ด๋ถ ๋ก์ง์์ ์บ์ฑ์ด ๋์ด ์์ง ์์ ๊ฒฝ์ฐ db์์ ๊ฐ์ ์ฐพ์์ ์ ์ฅํ๋ ๋ถ๋ถ์ด ๊ตฌํ๋์ด ์์ผ๋ฏ๋ก cache์ ์ ์ฅํ ํ ๊ฐ์ ๊ฐ์ ธ์ฌ ์ ์๋ค.
redisTemplate์์๋ redisTemplate.opsForValue().get(key); ์ด๊ฑธ ํตํด value๊ฐ์ Object ํ์ ์ผ๋ก ๊ฐ์ ธ์ฌ ์ ์๋ค. (get์ ํตํด key๋ฅผ ์ฌ์ฉํด์) ์ด๋ ๊ฒ ๊ฐ์ ธ์จ ๊ฐ์ ๊บผ๋ด๋ณด๋ฉด, ์ฐ๋ฆฌ๊ฐ ๋ฃ์ด์ค ๊ฐ์ธ 7000์ด๋ผ๋ balance ๊ฐ์ด ์ ๋ค์ด๊ฐ ์๋ ๊ฒ์ ๋ณผ ์ ์๋ค. memberRepository๋, ์ด๋ฐ์ ์บ์๊ฐ ์์ ๋ ํ ๋ฒ๋ง ํธ์ถ๋๋ฏ๋ก, ์ด ํธ์ถํ์๋ ํ ๋ฒ์ด ๋์ด test๋ฅผ ํต๊ณผํ ์ ์๋ค.
1
2
3
2025-09-05T19:55:51.692+09:00 INFO 1920 --- [ Test worker] s.r.service.MemberServiceCacheTest : Redis keys before: []
2025-09-05T19:55:51.704+09:00 INFO 1920 --- [ Test worker] s.r.service.MemberServiceCacheTest : Redis keys after: [v1:bal:user:1]
2025-09-05T19:55:51.712+09:00 INFO 1920 --- [ Test worker] s.r.service.MemberServiceCacheTest : Key : v1:bal:user:1, Value : 7000, TTL : 180 seconds
Test code 2_๋ ๋์ค ์บ์ฑ ํ ์คํธ - ๊ฐ๊ธฐ ๋ค๋ฅธ ํํ์ ๊ฐ์ฒด๊ฐ ์ ์ ์ฅ ๋๋์ง์ ๊ดํ์ฌ
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
@Test
void redisStoresDifferentCacheTypes() {
//test์ ์ฌ์ฉํ๊ธฐ ์ํ ๊ฐ์ฒด๋ค
Long id = 1L;
MemberCachingDto dto = MemberCachingDto.builder()
.name("ํผ์ฉํฌ")
.creditLimit(100000)
.build();
int balance = 5000;
String hobby = "์ถ๊ตฌ";
// Repository Mock ์ค์
Mockito.when(memberRepository.findBalanceByMemberId(id)).thenReturn(balance);
Mockito.when(memberRepository.findHobbyByMemberId(id)).thenReturn(hobby);
Mockito.when(memberRepository.findDtoByMemberId(id)).thenReturn(dto);
System.out.println("=== ์บ์ ์ ์ฅ ์ ===");
Set<String> keysBefore = redisTemplate.keys("*");
log.info("Redis keys: {}",keysBefore);
// ์ฌ๋ฌ ์ข
๋ฅ ์บ์ ์ ์ฅ
balance = memberService.getBalance(id);
hobby = memberService.getHobby(id);
dto = memberService.getSnapshot(id);
System.out.println("=== ์บ์ ์ ์ฅ ํ ===");
log.info("Balance: {}, Hobby: {}, dto : {}",balance,hobby,dto);
Set<String> keysAfter = redisTemplate.keys("*");
log.info("Redis keys: {}",keysAfter);
// ๋ชจ๋ ์ ์ฅ๋ ๋ฐ์ดํฐ ์ถ๋ ฅ
for (String key : keysAfter) {
Object value = redisTemplate.opsForValue().get(key);
Long ttl = redisTemplate.getExpire(key);
log.info("Key: {}",key);
log.info("Value: {}",value);
log.info("TTL: {} seconds",ttl);
log.info("Type: {}",redisTemplate.type(key)); //int, String, dto ๋์์ผ ํ๋ค.
}
assertThat(keysAfter.size()).isGreaterThanOrEqualTo(3); // balance, hobby, dto ์ต์ 3๊ฐ
}
์ฐ๋ฆฌ์ ๊ฒฝ์ฐ๋ cache์ ๋ฃ๋ ๊ฐ๋ค์ ๋ค ๋ค๋ฅธ ํ์ ์ผ๋ก ์ ์ํ๊ธฐ ๋๋ฌธ์ ๊ฐ๊ฐ์ด ์ ์ ์ฅ๋์๋์ง ์ดํด๋ด์ผ ํ๋ค.
balance = memberService.getBalance(id); hobby = memberService.getHobby(id); dto = memberService.getSnapshot(id);
ํด๋น get ๊ฐ๋ค์ ์ฐ์ ์บ์์ ์์ผ๋ฉด db์์ ์บ์์ ์ ์ฅํ๋ค์ return ํ๋๋ก ์ค์ ํ๊ธฐ ๋๋ฌธ์, ์ฐ๋ฆฌ๊ฐ ์ํ๋ value ๊ฐ๋ค์ด ๋์ฌ ๊ฒ์ด๋ค.
for๋ฌธ์ ํตํด redisTemplate์์ key๋ก ๊ฐ์ ธ์จ value ๊ฐ์ ํ์ธํด์, ๊ฐ๊ฐ์ value๋ค์ด ์ ๋์ค๋์ง๋ฅผ ์ดํด๋ณด์.
๊ฒฐ๊ณผ๋ฅผ ๋ณด๋ฉด, ์ฐ๋ฆฌ๊ฐ ์ํ๋ ๋๋ก ๊ฐ๊ฐ์ด ๋ค๋ฅธ value ํํ๋ก ์ ๋์ค๋ ๊ฒ์ ๋ณผ ์ ์๋ค. (๋ง์ง๋ง์ dto ํ์ ์ผ๋ก ๋ฐ๋ก ๋์ค๋๋ก ๊ตฌ์ฑํ๋ค.)
1
2
3
4
=== ์บ์ ์ ์ฅ ์ ===
2025-09-05T19:55:51.049+09:00 INFO 1920 --- [ Test worker] s.r.service.MemberServiceCacheTest : Redis keys: []
=== ์บ์ ์ ์ฅ ํ ===
2025-09-05T19:55:51.359+09:00 INFO 1920 --- [ Test worker] s.r.service.MemberServiceCacheTest : Balance: 5000, Hobby: ์ถ๊ตฌ, dto : MemberCachingDto(name=ํผ์ฉํฌ, creditLimit=100000)
Test code 3_๋ ๋์ค ์บ์ฑ ํ ์คํธ - ์บ์ํํธ ํ ์คํธ
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
@Test
void redisCacheHitVerification() {
Long id = 1L;
Mockito.when(memberRepository.findBalanceByMemberId(id)).thenReturn(9999);
System.out.println("=== ์ฒซ ๋ฒ์งธ ํธ์ถ (์บ์ ๋ฏธ์ค) ===");
int result1 = memberService.getBalance(id);
Set<String> keys1 = redisTemplate.keys("*");
log.info("Result: {}",result1);
log.info("Redis keys: {}", keys1);
// Repository Mock์ ๋ค๋ฅธ ๊ฐ์ผ๋ก ๋ณ๊ฒฝ (์บ์ ํํธ๋ฉด ์ด ๊ฐ์ด ์๋์์ผ ํจ...๋ฐ๋๊ธฐ ์ ๊ฐ์ด ์์ผ ํ๋๊น)
Mockito.when(memberRepository.findBalanceByMemberId(id)).thenReturn(1111);
System.out.println("=== ๋ ๋ฒ์งธ ํธ์ถ (์บ์ ํํธ) ===");
int result2 = memberService.getBalance(id);
Set<String> keys2 = redisTemplate.keys("*");
log.info("Result: {}",result2);
log.info("Redis keys: {}", keys2);
// ์บ์์์ ๊ฐ์ ธ์จ ๊ฐ์ด๋ฏ๋ก ์ฒซ ๋ฒ์งธ์ ๊ฐ์์ผ ํจ (9999)
assertThat(result2).isEqualTo(9999);
assertThat(result2).isNotEqualTo(1111); // Mock์ ์ ๊ฐ์ด ์๋
// Repository๋ 1๋ฒ๋ง ํธ์ถ๋จ (์บ์ ํํธ)
Mockito.verify(memberRepository, Mockito.times(1)).findBalanceByMemberId(id);
log.info("์บ์ ํํธ ์ฑ๊ณต! Repository๋ {}์ด๊ณ , {}๋ฒ๋ง ํธ์ถ",result2,Mockito.mockingDetails(memberRepository).getInvocations().size());
}
์ฒซ๋ฒ์งธ ํธ์ถ์ ๊ฒฝ์ฐ๋, mock์ ์ ์ฅ๋ ํ ์บ์ฑ๋ ๊ฐ์ด ์์ง ์๊ธฐ ๋๋ฌธ์ ์บ์ ๋ฏธ์ค๊ฐ ๋ฐ์ํ ๊ฒ์ด๋ค.
์บ์ ๋ฏธ์ค๊ฐ ๋ฐ์ํ ๊ฒฝ์ฐ, db์์ ๊ฐ์ ธ์จ ์ ๋ณด์ธ 9999๋ฅผ ์ ์ฅํ๊ฒ ๋์ด 9999๋ผ๋ ๊ฐ์ด ์ ์ฅ๋๋ค
์บ์ ํํธ ํ ์คํธ๋, ์บ์์ ๊ฐ์ด ์ด๋ฏธ ์์ด์ db์์ ๊ฐ์ ๋ฐ๊ฟ๋ ์บ์์ ๊ฐ์ด ๋จ์์๋ ๊ฒ์ ํ์ธํ๊ธฐ ์ํจ์ด๋ค.
Mockito.when(memberRepository.findBalanceByMemberId(id)).thenReturn(1111);๋ฅผ ํตํด mock์ ๊ฐ์ 1111๋ก ๋ฐ๊ฟจ์ง๋ง, ์์ง ์บ์ ์์ TTL์ด ๋ง๋ฃ๋์ง ์์๊ธฐ ๋๋ฌธ์ assertThat(result2).isEqualTo(9999);๊ฐ true๋ก ๋์ค๊ณ , assertThat(result2).isNotEqualTo(1111);๊ฐ true๋ก ๋์จ๋ค.
์ค์ ๋ก Mockito.mockingDetails(memberRepository).getInvocations().size()๋ฅผ ์ฐ์ด๋ณด๋ฉด, ์ฒ์ ์บ์ ๋ฏธ์ค์ผ๋ ๋ฑ ํ๋ฒ๋ง ํธ์ถ๋ ๊ฒ์ ๋ณผ ์ ์๋ค.
1
2
3
4
5
6
7
=== ์ฒซ ๋ฒ์งธ ํธ์ถ (์บ์ ๋ฏธ์ค) ===
2025-09-05T19:55:51.644+09:00 INFO 1920 --- [ Test worker] s.r.service.MemberServiceCacheTest : Result: 9999
2025-09-05T19:55:51.645+09:00 INFO 1920 --- [ Test worker] s.r.service.MemberServiceCacheTest : Redis keys: [v1:bal:user:1]
=== ๋ ๋ฒ์งธ ํธ์ถ (์บ์ ํํธ) ===
2025-09-05T19:55:51.654+09:00 INFO 1920 --- [ Test worker] s.r.service.MemberServiceCacheTest : Result: 9999
2025-09-05T19:55:51.654+09:00 INFO 1920 --- [ Test worker] s.r.service.MemberServiceCacheTest : Redis keys: [v1:bal:user:1]
2025-09-05T19:55:51.669+09:00 INFO 1920 --- [ Test worker] s.r.service.MemberServiceCacheTest : ์บ์ ํํธ ์ฑ๊ณต! Repository๋ 9999์ด๊ณ , 1๋ฒ๋ง ํธ์ถ
์ ์ฒด test ๊ฒฐ๊ณผ
ํ ์คํธ๋ ์ด์๊ฒ ํต๊ณผ๋ ๊ฒ์ ๋ณผ ์ ์๋ค:)