결과: 통합 테스트 8개 + 실제 Redis 테스트 3개, 전체 11개 통과. 5회 반복 실행 모두 성공. 실행:
cd backend && ./gradlew test(Redis가localhost:6379에 없으면 Redis 테스트 3개는 자동 skip)
이 프로젝트에는 처음에 테스트가 하나도 없었습니다(src/test 디렉터리 없음). 그동안의 검증은 브라우저 탭 두 개를 띄워 눈으로 확인하는 방식이었고, 그 결과 다음 문제들이 오래 남아 있었습니다.
| 눈으로는 못 찾은 문제 | 왜 안 보였나 |
|---|---|
| 늦은 입장자에게 1:1 메시지가 도착하지 않음 | 예외도 로그도 없이 조용히 버려짐 |
| 같은 세션의 이동 순서 역전 | 혼자 로컬에서 천천히 움직이면 거의 발생하지 않음 |
| 퇴장 직후 flush로 플레이어 부활 | 수 ms 사이의 타이밍에서만 발생 |
cook 역할로 입장 불가 |
테스트할 때 늘 server로만 입장 |
src/test/java/com/snowpeak/game/
├── repository/
│ ├── InMemoryPlayerRepository.java # 테스트용 저장소 (Redis 대체)
│ └── RedisPlayerRepositoryTest.java # 실제 Redis로 Lua·직렬화 검증
└── sync/
└── GameSyncIntegrationTest.java # 실제 서버 + STOMP 클라이언트 E2E
@SpringBootTest(webEnvironment = RANDOM_PORT, properties = "snowpeak.sync.flush-interval-ms=50")
class GameSyncIntegrationTest {
@TestConfiguration
static class InMemoryStoreConfig {
@Bean @Primary
PlayerRepository inMemoryPlayerRepository() { return new InMemoryPlayerRepository(); }
}
...
stompClient = new WebSocketStompClient(
new SockJsClient(List.of(new WebSocketTransport(new StandardWebSocketClient()))));
/topic/room.1, /user/queue/sync, /user/queue/errors, /user/queue/pong을 구독하고, 받은 메시지를 BlockingQueue에 쌓습니다.awaitType(queue, "MOVE")처럼 원하는 타입의 메시지가 올 때까지 최대 3초 기다립니다. 고정된 sleep에 의존하지 않아 느린 환경에서도 안정적입니다.PlayerRepository 인터페이스 덕분에 메모리 구현으로 교체했습니다. 그래서 Redis 없이도 빠르게 돌아갑니다.메모리 구현으로는 Lua Script와 직렬화 호환성을 검증할 수 없습니다. 그래서 RedisPlayerRepository는 실제 Redis에 붙여 따로 테스트합니다.
assumeTrue(redisUp, "Redis is not running on localhost:6379");
test-{nanoTime} 키를 써서 실제 데이터와 섞이지 않게 했습니다.| 테스트 | 검증 |
|---|---|
saveIfAbsentRejectsDuplicateNickname |
HSETNX로 두 번째 저장이 거절되고 첫 값이 유지됨 |
updateAllIfPresentOnlyTouchesExistingPlayersAndStaysReadable |
Lua가 존재하는 필드만 갱신(1개 갱신, 1개 무시). Lua로 저장한 JSON을 HVALS로 다시 객체로 읽을 수 있음 |
deleteReportsWhetherPlayerExisted |
HDEL 반환값으로 퇴장 여부를 판단할 수 있음 |