Skip to content

Commit 43d49a1

Browse files
committed
test: RankingIntegrationTest 클래스 추가 및 랭킹 관련 기능 통합 테스트 구현
1 parent 81c7deb commit 43d49a1

1 file changed

Lines changed: 195 additions & 0 deletions

File tree

Lines changed: 195 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,195 @@
1+
package com.loopers.domain.ranking;
2+
3+
import com.loopers.utils.RedisCleanUp;
4+
import org.junit.jupiter.api.*;
5+
import org.springframework.beans.factory.annotation.Autowired;
6+
import org.springframework.boot.test.context.SpringBootTest;
7+
import org.springframework.data.redis.core.RedisTemplate;
8+
9+
import java.time.LocalDate;
10+
11+
import static org.assertj.core.api.Assertions.assertThat;
12+
13+
@SpringBootTest
14+
@TestInstance(TestInstance.Lifecycle.PER_CLASS)
15+
class RankingIntegrationTest {
16+
17+
@Autowired
18+
private RankingService rankingService;
19+
20+
@Autowired
21+
private RankingWeight rankingWeight;
22+
23+
@Autowired
24+
private RedisTemplate<String, String> redisTemplate;
25+
26+
@Autowired
27+
private RedisCleanUp redisCleanUp;
28+
29+
private final LocalDate testDate = LocalDate.of(2025, 1, 15);
30+
31+
@BeforeEach
32+
void setUp() {
33+
redisCleanUp.truncateAll();
34+
}
35+
36+
@AfterAll
37+
void tearDown() {
38+
redisCleanUp.truncateAll();
39+
}
40+
41+
@Nested
42+
@DisplayName("랭킹 점수 누적 테스트")
43+
class ScoreAccumulation {
44+
45+
@Test
46+
@DisplayName("동일 상품에 대한 여러 이벤트가 누적된다")
47+
void shouldAccumulateScoresForSameProduct() {
48+
// given
49+
Long productId = 100L;
50+
51+
// when
52+
rankingService.incrementViewScore(productId, testDate);
53+
rankingService.incrementViewScore(productId, testDate);
54+
rankingService.updateLikeScore(productId, true, testDate);
55+
56+
// then
57+
String key = RankingKey.daily(testDate);
58+
Double score = redisTemplate.opsForZSet().score(key, productId.toString());
59+
60+
// 예상: view(0.1) * 2 + like(0.2) * 1 = 0.4
61+
assertThat(score).isNotNull();
62+
assertThat(score).isGreaterThanOrEqualTo(0.4);
63+
}
64+
65+
@Test
66+
@DisplayName("좋아요 취소 시 점수가 감소한다")
67+
void shouldDecreaseScoreWhenUnliked() {
68+
// given
69+
Long productId = 100L;
70+
rankingService.updateLikeScore(productId, true, testDate);
71+
72+
String key = RankingKey.daily(testDate);
73+
Double beforeScore = redisTemplate.opsForZSet().score(key, productId.toString());
74+
75+
// when
76+
rankingService.updateLikeScore(productId, false, testDate);
77+
78+
// then
79+
Double afterScore = redisTemplate.opsForZSet().score(key, productId.toString());
80+
assertThat(afterScore).isLessThan(beforeScore);
81+
}
82+
}
83+
84+
@Nested
85+
@DisplayName("키 TTL 테스트")
86+
class KeyTtl {
87+
88+
@Test
89+
@DisplayName("새로운 랭킹 키 생성 시 TTL이 설정된다")
90+
void shouldSetTtlWhenKeyCreated() {
91+
// given
92+
Long productId = 100L;
93+
String key = RankingKey.daily(testDate);
94+
95+
// when
96+
rankingService.incrementViewScore(productId, testDate);
97+
98+
// then
99+
Long ttl = redisTemplate.getExpire(key);
100+
assertThat(ttl).isNotNull();
101+
assertThat(ttl).isGreaterThan(0);
102+
}
103+
}
104+
105+
@Nested
106+
@DisplayName("Carry-Over 테스트")
107+
class CarryOver {
108+
109+
@Test
110+
@DisplayName("전날 점수의 일부가 다음날로 복사된다")
111+
void shouldCopyScoresToNextDay() {
112+
// given
113+
Long productId = 100L;
114+
LocalDate today = testDate;
115+
LocalDate tomorrow = testDate.plusDays(1);
116+
117+
// 오늘 점수 생성
118+
rankingService.incrementOrderScore(productId, 10, today);
119+
120+
String todayKey = RankingKey.daily(today);
121+
Double todayScore = redisTemplate.opsForZSet().score(todayKey, productId.toString());
122+
123+
// when
124+
rankingService.carryOverScores(today, tomorrow, 0.1);
125+
126+
// then
127+
String tomorrowKey = RankingKey.daily(tomorrow);
128+
Double tomorrowScore = redisTemplate.opsForZSet().score(tomorrowKey, productId.toString());
129+
130+
assertThat(tomorrowScore).isNotNull();
131+
assertThat(tomorrowScore).isCloseTo(todayScore * 0.1, org.assertj.core.api.Assertions.within(0.01));
132+
}
133+
134+
@Test
135+
@DisplayName("이미 데이터가 있는 키에는 carry-over 하지 않는다")
136+
void shouldNotOverwriteExistingData() {
137+
// given
138+
Long productId = 100L;
139+
LocalDate today = testDate;
140+
LocalDate tomorrow = testDate.plusDays(1);
141+
142+
// 오늘 점수 생성
143+
rankingService.incrementOrderScore(productId, 10, today);
144+
145+
// 내일 키에 미리 데이터 생성
146+
rankingService.incrementViewScore(productId, tomorrow);
147+
String tomorrowKey = RankingKey.daily(tomorrow);
148+
Double existingScore = redisTemplate.opsForZSet().score(tomorrowKey, productId.toString());
149+
150+
// when
151+
rankingService.carryOverScores(today, tomorrow, 0.1);
152+
153+
// then (점수가 변경되지 않아야 함)
154+
Double afterScore = redisTemplate.opsForZSet().score(tomorrowKey, productId.toString());
155+
assertThat(afterScore).isEqualTo(existingScore);
156+
}
157+
}
158+
159+
@Nested
160+
@DisplayName("동적 가중치 테스트")
161+
class DynamicWeight {
162+
163+
@Test
164+
@DisplayName("Redis에서 가중치를 동적으로 업데이트하고 조회할 수 있다")
165+
void shouldUpdateAndRetrieveWeightsDynamically() {
166+
// given
167+
double newViewWeight = 0.15;
168+
double newLikeWeight = 0.25;
169+
double newOrderWeight = 0.6;
170+
171+
// when
172+
rankingWeight.updateAllWeights(newViewWeight, newLikeWeight, newOrderWeight);
173+
174+
// then
175+
assertThat(rankingWeight.getViewWeight()).isEqualTo(newViewWeight);
176+
assertThat(rankingWeight.getLikeWeight()).isEqualTo(newLikeWeight);
177+
assertThat(rankingWeight.getOrderWeight()).isEqualTo(newOrderWeight);
178+
}
179+
180+
@Test
181+
@DisplayName("가중치 초기화 시 기본값으로 복원된다")
182+
void shouldResetToDefaultWeights() {
183+
// given
184+
rankingWeight.updateAllWeights(0.5, 0.5, 0.5);
185+
186+
// when
187+
rankingWeight.resetToDefault();
188+
189+
// then
190+
assertThat(rankingWeight.getViewWeight()).isEqualTo(0.1);
191+
assertThat(rankingWeight.getLikeWeight()).isEqualTo(0.2);
192+
assertThat(rankingWeight.getOrderWeight()).isEqualTo(0.7);
193+
}
194+
}
195+
}

0 commit comments

Comments
 (0)