Skip to content

[refactor] 순위 공정성 문제 해결#215

Open
MADUPAL wants to merge 1 commit intodevfrom
refactor/213-judge0-채점-시간-공정성-문제-해결

Hidden character warning

The head ref may contain hidden characters: "refactor/213-judge0-\ucc44\uc810-\uc2dc\uac04-\uacf5\uc815\uc131-\ubb38\uc81c-\ud574\uacb0"
Open

[refactor] 순위 공정성 문제 해결#215
MADUPAL wants to merge 1 commit intodevfrom
refactor/213-judge0-채점-시간-공정성-문제-해결

Conversation

@MADUPAL
Copy link
Copy Markdown
Collaborator

@MADUPAL MADUPAL commented Apr 15, 2026

현재 BattleAcService.handleAc()에서 finishTime을 LocalDateTime.now()로 기록한다.
이 시각은 Judge0 결과를 polling으로 감지한 시각이므로, 아래 두 가지 노이즈가 포함된다:

  • Judge0 큐 대기 시간 (서버 부하에 따라 사용자마다 다름)
  • 1초 polling 간격 오차 (최대 1초 랜덤 지연)

결과적으로 "더 빨리 맞는 코드를 제출한 사람"이 아닌 "Judge0가 더 빨리 처리해준 사람"이 유리해질 수 있다.

목표: finishTime 기준을 Judge0 결과 감지 시각 → AC 제출 요청 시각(Submission.createdAt)으로 변경


변경 파일 목록

파일 변경 내용
src/main/java/com/back/global/judge/BattleAcService.java 메서드 시그니처 변경, LocalDateTime.now() 제거
src/main/java/com/back/global/judge/JudgeService.java handleAc() 호출 시 submission.getCreatedAt() 전달
src/test/java/com/back/global/judge/BattleAcServiceTest.java 테스트 handleAc() 호출부 파라미터 추가
src/test/java/com/back/global/judge/JudgeServiceTest.java Mock verify 파라미터 수정

상세 변경 계획

  1. BattleAcService.java
// 변경 전
public void handleAc(Long roomId, Long memberId) {
    ...
    participant.complete(LocalDateTime.now());
    ...
}

// 변경 후
public void handleAc(Long roomId, Long memberId, LocalDateTime submittedAt) {
    ...
    participant.complete(submittedAt);
    ...
}
  • import java.time.LocalDateTime 이미 존재하므로 추가 불필요

  1. JudgeService.java

submission은 라인 78에서 이미 조회된 상태 → 추가 DB 쿼리 없음

// 변경 전 (라인 94)
if (judgeResult == SubmissionResult.AC) {
    battleAcService.handleAc(roomId, memberId);
}

// 변경 후
if (judgeResult == SubmissionResult.AC) {
    battleAcService.handleAc(roomId, memberId, submission.getCreatedAt());
}

  1. BattleAcServiceTest.java

테스트에서는 JPA Auditing이 동작하지 않으므로 LocalDateTime.now()를 직접 생성해서 전달

// 변경 전 (라인 67, 106, 129)
withAfterCommit(() -> battleAcService.handleAc(ROOM_ID, MEMBER_ID));

// 변경 후
LocalDateTime submittedAt = LocalDateTime.now();
withAfterCommit(() -> battleAcService.handleAc(ROOM_ID, MEMBER_ID, submittedAt));

- finishTime이 submittedAt으로 기록되는지 검증하는 assertion 추가 가능
assertThat(participant.getFinishTime()).isEqualTo(submittedAt);

  1. JudgeServiceTest.java
// 변경 전 (라인 165)
verify(battleAcService, never()).handleAc(any(), any());

// 변경 후
verify(battleAcService, never()).handleAc(any(), any(), any());

// 변경 전 (라인 176)
verify(battleAcService).handleAc(ROOM_ID, MEMBER_ID);

// 변경 후 — 테스트에서 submission.getCreatedAt()은 null (JPA Auditing 미동작)
verify(battleAcService).handleAc(eq(ROOM_ID), eq(MEMBER_ID), isNull());
  • isNull() import: import static org.mockito.ArgumentMatchers.isNull;

검증 방법

  1. ./gradlew test --tests "*.BattleAcServiceTest" 통과 확인
  2. ./gradlew test --tests "*.JudgeServiceTest" 통과 확인
  3. 로컬 배틀 시나리오 수동 테스트:
    • 두 유저가 동시에 AC 제출
    • finishTime이 제출 시각 기준으로 기록되는지 DB 직접 확인
    • 더 빨리 제출한 유저가 더 낮은 score를 받는지 확인

@MADUPAL MADUPAL self-assigned this Apr 15, 2026
@MADUPAL MADUPAL linked an issue Apr 15, 2026 that may be closed by this pull request
2 tasks
@github-actions
Copy link
Copy Markdown

📊 테스트 커버리지 리포트

Overall: 🟡 Line 70.4% | Branch 52.3%


📦 패키지별 커버리지

패키지 Line Branch
🟢 problem/enums 100% N/A
🟢 global/judge 100% 100%
🟢 testcase/entity 100% N/A
🟢 tag/entity 100% N/A
🟢 global/jwt 100% 75%
🟢 tag/service 100% 50%
🟢 tag/controller 100% N/A
🟢 global/security 100% N/A
🟢 queue/service 100% 66.7%
🟢 global/aspect 100% 50%
🟢 problemtagconnect/entity 100% N/A
🟢 languageprofile/entity 100% N/A
🟢 reviewschedule/controller 100% 100%
🟢 global/globalExceptionHandler 100% 0%
🟢 jpa/entity 100% N/A
🟢 profile/service 100% 62.5%
🟢 dashboard/repository 100% 50%
🟢 member/entity 100% 50%
🟢 queue/model 100% 50%
🟢 pick/service 100% 87.5%
🟢 member/service 100% 50%
🟢 global/init 100% N/A
🟢 profile/entity 100% 50%
🟢 websocket/pubsub 100% 66.7%
🟢 battleroom/entity 100% 0%
🟢 global/enums 100% N/A
🟢 global/rq 100% 0%
🟢 submission/entity 100% N/A
🟢 queue/store 100% 50%
🟢 store/redis 100% 66.7%
🟢 result/event 100% N/A
🟢 battleparticipant/entity 100% N/A
🟢 reviewschedule/entity 100% N/A
🟢 reviewschedule/service 100% 60%
🟢 result/service 100% 100%
🟢 problem/controller 100% 50%
🟢 member/controller 100% 50%
🟢 global/exception 100% 0%
🟢 battleroom/service 100% 0%
🟢 problem/entity 100% 50%
🟢 global/websocket 100% 75%
🟢 global/async 100% N/A
🟢 submission/entity 100% 50%
🟢 problem/util 100% 100%
🟢 global/rsData 100% 50%
🟢 solve/entity 100% N/A
🟢 dashboard/service 96.4% 50%
🟢 problem/service 91.7% 50%
🟢 global/redis 87.5% 50%
🟢 pick/repository 85.7% 50%
🟢 rating/policy 85.7% 83.3%
🟡 dashboard/controller 75% 50%
🔴 global/scheduler 33.3% 50%
🔴 submission/controller 0% N/A
🔴 run/controller 0% N/A
🔴 submission/service 0% 0%
🔴 submission/controller 0% N/A
🔴 queue/controller 0% 0%
🔴 battleroom/controller 0% N/A
🔴 submission/service 0% N/A
🔴 judge/event 0% N/A
🔴 run/service 0% N/A
🔴 run/controller 0% N/A
🔴 auth/controller 0% 0%
🔴 result/controller 0% N/A
🔴 run/service 0% 0%
🔴 solve/repository N/A N/A
🔴 profile/repository N/A N/A
🔴 tag/repository N/A N/A
🔴 translation/repository N/A N/A
🔴 languageprofile/repository N/A N/A
🔴 problem/repository N/A N/A
🔴 member/repository N/A N/A
🔴 battleparticipant/repository N/A N/A
🔴 submission/repository N/A N/A
🔴 reviewschedule/repository N/A N/A
🔴 submission/repository N/A N/A
🔴 translation/entity N/A N/A
🔴 battleroom/repository N/A N/A
🔴 testcase/repository N/A N/A
🔴 problemtagconnect/repository N/A N/A

🟢 80% 이상   🟡 50~79%   🔴 50% 미만

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

[refactor] judge0 채점 시간 공정성 문제 해결

3 participants