Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

refactor: 웹 소켓 로직 및 테스트 개선 #537

Open
wants to merge 23 commits into
base: develop
Choose a base branch
from

Conversation

coli-geonwoo
Copy link
Contributor

@coli-geonwoo coli-geonwoo commented Sep 15, 2024

🚩 연관 이슈

close #536


📝 작업 내용

#482 와 겹치는 부분이 있어 리뷰 범위는 다음과 같습니다.


웹 소켓 로직 개선

기존에 WebSocketController와 EtaSocketService로 나뉘어져 있던 구조에서 메세지 sending 역할을 담당할 SocketMessageSender로 역할을 분담시켜주었습니다.

SocketMessageSender가 담당하는 역할은 다음과 같습니다.

  • 즉시 메시지 발송 : sendMessage
  • 메시지 예약 : reserveMessage
public class SocketMessageSender {

    public void reserveMessage(String destination, LocalDateTime triggerTime) {
        //메세지 예약
    }

    public void sendMessage(String destination) {
        //메세지 즉시 발송
    }
}

또한 TimeCache 객체를 만들어 캐싱을 담당할 책임을 분담시켜주었습니다. 추후 테스트 용이성을 위한 분리이기도 합니다.


테스트

웹 소켓 관련 테스트는 총 3가지로 각 테스트의 목적이 다릅니다.

  • EtaSocketControllerTest : E2E 테스트로 실제 stompSession으로 client가 소켓 연결부터 호출까지를 진행하며 그 결과를 확인합니다.
  • EtaSocketServiceTest : SocketService의 내부 동작을 확인합니다
  • EtaSocketConcurrencyTest : 여러명의 client가 service를 동시 호출했을 때 예약 호출이 1번만 진행되는지 테스트합니다.

ControllerTest 동작 원리

ControllerTest의 구조를 이해하기 위해서는 CompletableFuture의 동작 원리를 이해하여야 합니다.

웹 소켓 통신은 test thread와 별개의 thread에서 동작합니다.
비동기 작업이기에 언제 작업이 끝나는지 test thread 입장에서는 알 수 없습니다.

이를 해결하기 위해 MessageFrameHandler가 웹 소켓 요청을 보내는 client가 message를 받았을 때 처리할 함수(handleFrame)를 구현하고 있습니다. 즉, 클라이언트가 메시지를 받는 시점(서버 측에서 확실히 작업이 끝난 시점)을 알 수 있도록 구현하였습니다.

public class MessageFrameHandler<T> implements StompFrameHandler {

    private final CompletableFuture<T> completableFuture = new CompletableFuture<>();
    ....

    @Override
    public void handleFrame(StompHeaders headers, Object payload) {
        if(completableFuture.complete((T)payload)){
            System.out.println("끝남");
        }
    }
}

handleFrame method는 서버로부터 Message(header/ payLoad)가 왔을 시 completableFuture.complete로 비동기 작업의 끝을 알려주고 있습니다.

CompletableFuture의 get 함수는 result가 초기화될 때까지 기다리다가 초기화되면 그 값을 반환하는 함수로 비동기 작업의 끝을 아는데 사용됩니다.

public T get() throws InterruptedException, ExecutionException {
        Object r;
        if ((r = result) == null) 
            r = waitingGet(true); //없으면 비동기 작업이 완료될 때까지 기다림
        return (T) reportGet(r); // 값이 들어오면 반환
    }

그럼 다음 테스트를 기반으로 어떤 방식의 동작인지 이해해보면 다음과 같습니다.

    @DisplayName("오픈 동시 요청에 대해 위치 호출 함수가 예약된다")
    @Test
    void callEtaMethodWhenStartConnection() throws InterruptedException {
        Mockito.when(timeCache.exists(anyLong())).thenReturn(false);
        Mockito.when(meetingService.findById(anyLong())).thenReturn(Fixture.ODY_MEETING);

        subscribeTopic("coordinates"); //client가 coordinates 주제를 구독함
        stompSession.send("/publish/open/1", ""); // open 요청과 동시에 서버는 1초 뒤 coordinates 구독자들에게 위치 요청 함수를 호출함

        CompletableFuture<Object> completableFuture = handler.getCompletableFuture(); 
        assertThatCode(() -> completableFuture.get(10, TimeUnit.SECONDS)) // 10초 안에 메세지를 수신하여 CompletableFuture가 초기화 되고, 10초 이후에도 아무런 초기화 로직이 없으면 TimeoutException
                .doesNotThrowAnyException();
    }

이 테스트가 통과하였다는 것은 서버측으로부터 coordinates 구독자가 메시지를 수신했다는 것을 의미하며, open 요청과 동시에 위치 호출 함수 예약이 정상적으로 진행되었음을 의미합니다.


동시성 테스트 이해하기

웹 소켓 통신 시 2가지 상황에서 동시 요청이 옵니다.

image

  • 첫째로, 약속 참여자 모두가 동시에 open 요청을 통해 첫번째 위치 호출 예약을 시도하며
  • 둘째로, 약속 참여자 모두가 동시에 10초 마다 다음 위치 호출 예약을 위해 etaUpdate를 진행합니다

동시성 테스트는 위의 맥락을 반영하여 두가지를 테스트합니다

  • open 요청을 동시에 10명이 진행하여도 위치 호출 예약은 1번만 된다
  • etaUpdate 요청을 동시에 10명이 진행하여도 다음 위치 호출 예약은 1번만 된다

이를 위해 다음 method를 통해 동시성 테스트를 진행합니다.

private void runJobConcurrently(int threadNums, Runnable runnable) throws InterruptedException {
        ExecutorService executorService = Executors.newFixedThreadPool(threadNums); //스레드 수만큼 pool 만들기
        CountDownLatch signal = new CountDownLatch(threadNums); // 스레드 동작 동기화 (카운트가 0이 될 때까지 기다림)

        for (int i = 0; i < threadNums; i++) {
            executorService.execute(runnable); // 동시 요청
            signal.countDown(); // 요청 하나 할 때마다 1씩 countDown
        }

        signal.await(); //count가 0이 되면 넘어감
        executorService.shutdown();
    }

다만 이 경우에도 각 작업은 멀티 스레드 환경에서 진행되기 때문에 테스트 스레드와 별개의 완료지점을 가집니다. 즉, test 가 검증단계 까지 왔지만 아직 각 스레드의 작업은 완료되지 않을 수 있습니다.

따라서 동시성 테스트 각각에 1초간 Thread.sleep()을 통해 멀티 스레드 작업이 모두 끝날 때까지의 pause를 주는 방식으로 구현하였습니다.

노력했지만 테스트가 아직 복잡한데요. 이해 안되는 부분이나 개선하면 좋을만한 아쉬운 부분들 남겨주시면 최대한 반영토록 노력해보겠습니다!!


🏞️ 스크린샷 (선택)


🗣️ 리뷰 요구사항 (선택)

Copy link

github-actions bot commented Sep 15, 2024

Test Results

149 tests  +10   142 ✅ +10   44s ⏱️ +40s
 44 suites + 3     7 💤 ± 0 
 44 files   + 3     0 ❌ ± 0 

Results for commit 0c519dd. ± Comparison against base commit e4df144.

This pull request removes 6 and adds 16 tests. Note that renamed tests count towards both.
com.ody.auth.JwtTokenProviderTest$validateAccessToken ‑ [1] accessToken=com.ody.auth.token.AccessToken@4042399c
com.ody.auth.JwtTokenProviderTest$validateAccessToken ‑ [2] accessToken=com.ody.auth.token.AccessToken@5dcb5eb6
com.ody.common.validator.FutureOrPresentDateTimeValidatorTest ‑ [1] date=2024-09-12, time=07:53:55.548551274, expected=false
com.ody.common.validator.FutureOrPresentDateTimeValidatorTest ‑ [2] date=2024-09-12, time=08:53:55.548582101, expected=true
com.ody.common.validator.FutureOrPresentDateTimeValidatorTest ‑ [3] date=2024-09-12, time=06:53:55.548569007, expected=false
com.ody.common.validator.FutureOrPresentDateTimeValidatorTest ‑ [4] date=2024-09-13, time=07:53:55.548551274, expected=true
com.ody.auth.JwtTokenProviderTest$validateAccessToken ‑ [1] accessToken=com.ody.auth.token.AccessToken@511da605
com.ody.auth.JwtTokenProviderTest$validateAccessToken ‑ [2] accessToken=com.ody.auth.token.AccessToken@26bb13ca
com.ody.common.validator.FutureOrPresentDateTimeValidatorTest ‑ [1] date=2024-09-17, time=17:34:48.601330968, expected=false
com.ody.common.validator.FutureOrPresentDateTimeValidatorTest ‑ [2] date=2024-09-17, time=18:34:48.601367606, expected=true
com.ody.common.validator.FutureOrPresentDateTimeValidatorTest ‑ [3] date=2024-09-17, time=16:34:48.601353810, expected=false
com.ody.common.validator.FutureOrPresentDateTimeValidatorTest ‑ [4] date=2024-09-18, time=17:34:48.601330968, expected=true
com.ody.eta.controller.EtaSocketControllerTest ‑ /topic/etas/{meetingId}에 구독한 사람들이 요청을 받는다
com.ody.eta.controller.EtaSocketControllerTest ‑ 약속 시간 이후의 상태 목록 조회 호출 시 disconnect 트리거를 당긴다.
com.ody.eta.controller.EtaSocketControllerTest ‑ 오픈 동시 요청에 대해 위치 호출 함수가 예약된다
com.ody.eta.controller.EtaSocketControllerTest ‑ 호출 한지 10초가 안 되었을 경우, update 요청을 예약하지 않는다.
…

♻️ This comment has been updated with latest results.

Copy link

github-actions bot commented Sep 15, 2024

📝 Test Coverage Report

Overall Project 79.88% -0.67%
Files changed 89.6% 🍏

File Coverage
EtaSocketController.java 100% 🍏
WebSocketTrigger.java 100% 🍏
TimeCache.java 100% 🍏
SocketMessageSender.java 100% 🍏
EtaSocketService.java 100% 🍏
WebSocketConfig.java 100% 🍏
WebSocketArgumentResolver.java 0%

Copy link
Contributor

@eun-byeol eun-byeol left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

야무진🥦 테스트 짜느라 고생 많았어요👏
최대한 설명을 잘 해주셨는데, 익숙지 않다보니 오디톡 있으면 좋을 것 같아요!

전체 테스트 통과까지 아주 오랜 시간이 걸리는 점이 우려스러운데,
56초 걸리는데 콜리 인내심왕 인정
카키가 제안한대로, 10초 스케줄링 -> 더 시간을 줄이면 더 개선될 수 있을 것 같아요.
1초까지 슬립하지 않아도 되는 다른 방법도 찾아볼게요!

질문 몇개 남겼는데, 답변 놓칠 것같아 RC 남길게요!
즐추즐추🌕

public void reserveMessage(String destination, LocalDateTime triggerTime) {
Instant startTime = triggerTime.toInstant(KST_OFFSET);
taskScheduler.schedule(() -> template.convertAndSend(destination, BLANK_PAYLOAD), startTime); //TODO payLoad로 비교
log.info("--- schedule 예약 완료 ! - {}, {}", destination, triggerTime);
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

[제안]
다른 스케줄링과 섞였을 때를 위해 웹소켓을 명시하는건 어떤가요?

Suggested change
log.info("--- schedule 예약 완료 ! - {}, {}", destination, triggerTime);
log.info("--- websocket schedule 예약 완료 ! - {}, {}", destination, triggerTime);

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

좋아요! 반영해주었습니다!

@@ -0,0 +1,24 @@
package com.ody.util;
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

[질문]
웹소켓에서만 사용될텐데 util 클래스에 둔 이유가 무엇인가요?
더 범용적으로 사용될 수 있나요?🤔

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

음... 사실 eta 어디 패키지에 두어야 할지 조금 애매해서 그냥 utill에 두긴 했어요 😂

eta에서만 사용되긴 하는데 도착정보 domain이랑 깊게 관련된 책임이 있는 클래스라 보는 건 또 아닌 것 같고... 그래서 우선 util에 두긴 했습니다.

그런데 기능만 제공한다고 하기엔 @component로 빈 등록이 되어 있어서 패키지 위치는 조금 고민해봐야 할 것 같아요. 조조는 어느 패키지가 적당하다고 생각하시나요?

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

EtaSocketService와 같은 계층에 둘것 같아요.
사실 TimeCache로 빈등록해서 관리해야 할 필요성을 이해하지 못했어요. 더 넓은 책임을 준 이유를 공감하지 못했어요. 텍스트로 전달하다보니 제가 이해하지 못한 부분이 많을 것 같은데, 대면에서 더 이야기해보면 좋을 것 같아요!
코드에 대한 제 생각을 말씀드린거라서 변경해야 한다거나 그런건 아닙니다. 다만 제가 이해될때까지 물어볼게요ㅎㅎ

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

웹소켓 Service에서만 사용하는 클래스인데 TimeCache라는 범용적인 이름을 사용해서 패키지 위치가 애매한 것 같은데
좀더 구체적으로 작성해준다면 적절한 패키지 위치가 파악되지 않을까요 ??

  • @component를 사용해 Bean으로 등록한 부분에 대해서 조조가 말한데로 대면에서 이야기해보면 더 좋을꺼같네요 ! 어차피 하루 조금 안남았으니

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

좋아요! 아마 로직상 변화가 있을수도 있을 것 같으니 우선 테스트는 더 건드리진 않을게요 😄

Comment on lines +27 to +28
private final TimeCache lastTriggerTimeCache;
private final TimeCache meetingTimeCache;
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

[질문]
단 1개만을 보장하기 위해 캐시에 static 키워드를 붙이면 좋을 것 같은데, 어떻게 생각하시나요?
여러 스레드에서 동작할 때 static으로 두지 않아도 문제가 없나요? 헷갈리네요🤔

Copy link
Contributor Author

@coli-geonwoo coli-geonwoo Sep 16, 2024

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

좋아요. 그런데 테스트 고민을 조금 해야 할 것 같아요 사실 timeCache 객체를 만들어준게 test 용이성을 위한 부분도 있어서요.

지금은 timeCache mockBean을 통해 호출 시간 stubbing을 통해 동작을 검증하고 있어요.

@DisplayName("호출 한지 10초가 지난 경우, 다시 위치 호출 요청을 예약한다.")
    @Test
    void scheduleTriggerWhenDurationMoreThan10Seconds() {
        Mockito.when(timeCache.get(anyLong()))
                .thenReturn(LocalDateTime.now().plusMinutes(10L)) // meeting 시간 (10분 뒤)
                .thenReturn(LocalDateTime.now().minusSeconds(11L)); //trigger 당긴지 11초 > 새로 예약 O

그런데 의존성 주입이 안되는 static의 경우 어떻게 테스트를 할지 조금 고민이 되어요.


두번째로 저는 context가 뜰 때 빈을 싱글톤으로 관리한다는 걸로 알고 있어서 각 인스턴스는 1개라는 게 보장될 것 같아요. 멀티 스레드 환경에서도 스레드와 관계없이 프로세스 자원(전역변수, heap 메모리)의 경우 공유되기 때문에 context에 뜨는 etaSocketService 인스턴스(heap 메모리에 저장됨)도 스레드들이 모두 공유할 것 같아요.

스레드별로 공유되지 않는 것은 스택 영역이라 알고 있는데, 맞다면 인스턴스 필드의 경우 heap 메모리에 저장되니 괜찮을 것 같다는 생각입니다.

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

싱글톤으로 관리되는건 맞지만.. 그럼 왜 static으로 선언하나요? 자원을 공유하면 static과 차이가 없지 않나요?🤔

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Thread가 공유하는 자원과 static 자체의 개념을 별개로 구분하고 바라볼 필요가 있을 것 같은데

우선 static 자원들은 인스턴스와 무관하게 클래스가 공유하는 자원이고, 이 부분은 JVM Method Area에 저장됩니다. Method Area에 클래스, 메소드, static 자원 같은 정보들이 저장된다고 알고 있어요.

그리고 인스턴스 정보들(각 인스턴스 별 필드들)은 heap memory에 저장됩니다.
image

Thread는 stack memory를 제외한 heap과 method area 정보들을 공유하기 때문에 static으로 선언할 필요가 없다고 생각했던 것 같아요 🤔

실제로 동시성 테스트에서 각 thread별 cache 객체를 찍어보아도 같은 cache 객체를 향하고 있었다는 걸 확인할 수 있었습니다
image

@Override
public void handleFrame(StompHeaders headers, Object payload) {
if(completableFuture.complete((T)payload)){
System.out.println("끝남");
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@Slf4j 적용이 왜 안될까요?ㅜ Logger 직접 선언하면 되긴 하던데, println보다는 나을 것 같습니다!

Comment on lines 29 to 30
@DirtiesContext(classMode = ClassMode.BEFORE_EACH_TEST_METHOD)
public class EtaSocketConcurrencyTest extends BaseServiceTest {
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

[질문]
걸려있는 스케줄링을 삭제하기 위해 @DirtiestContext를 사용한건가요?
찾아보니, 스케줄을 생성할 때 반환값을 저장하고 -> scheduledFuture.cancel()를 호출해야 하더라구요.
프로덕션 로직에 변경이 있을 것 같아 조심스럽지만, 해당 방법은 고려해보면 좋을 것 같아요.

Suggested change
@DirtiesContext(classMode = ClassMode.BEFORE_EACH_TEST_METHOD)
public class EtaSocketConcurrencyTest extends BaseServiceTest {
@DirtiesContext(classMode = ClassMode.BEFORE_EACH_TEST_METHOD)
class EtaSocketConcurrencyTest extends BaseServiceTest {

Copy link
Contributor Author

@coli-geonwoo coli-geonwoo Sep 16, 2024

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

> DirtiesContext를 사용한 이유

첫번째 test에서 사용한 EtaSocketService가 그대로 둘째 테스트에 사용되어서 첫째 테스트에서 cache에 저장된 lasttriggertime이 두번째 테스트에 영향을 주었어요. 그래서 독립된 컨텍스트에서의 동시성 테스트가 필요했습니다.

> taskschedular cancel 문제

이 부분은 제가 제대로 이해한 건지는 모르겠는데 실제 taskSchedular가 호출됨으로써 발생하는 문제로 이해했는데요.

socketMessageSender를 mockbean으로 설정해주어서 taskScehdular가 호출될 일이 없도록 리팩터링하였습니다. 호출 횟수만 검증하면 되니 이 방법도 괜찮다고 생각해요! 그런데 조조가 이야기하는 문제가 이 문제가 맞는지는 확신이 서지 않네요 🤔

> 테스트 접근 제어자 변경

반영해주었습니다

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

용도를 제가 잘못 이해했었네요!

첫째 테스트에서 cache에 저장된 lasttriggertime이 두번째 테스트에 영향을 주었어요.

그럼 더더욱 @DirtiestContext를 사용할 필요가 있을까요? 캐시를 날려주는 작업만 하면 되는데, 부트까지 띄울 필요가 없을 것 같아요.

Copy link
Contributor Author

@coli-geonwoo coli-geonwoo Sep 17, 2024

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

아마 조조의 의견은

@Autowired
TimeCache timecache;


@AfterEach
void tearDown(){
   timeCache.clear() 
}

처럼 캐시를 비우는 로직으로 해석했어요. 그런데 EtaSocketService내에서 TimeCache를 2개 필드로 의존성 주입받고 있어서 reset을 하면 어떤 게 날아가는지 제가 예측할 수가 없었던 것 같아요.

public EtaSocketService(TimeCache meetingTimeCache, TimeCache lastTriggerTimeCache, ......){}

이런 경우가 처음이라 그런데 캐시를 날리면 meetingTimeCache만 날아가는지, lastTriggerTimeCache만 날아가는지, 혹은 둘 다 날아가는지.... 모르겠네요 🥲

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

제가 테스트해봤을 땐 둘이 같은 객체라 둘 다 날아가네요!

Copy link
Contributor

@hyeon0208 hyeon0208 left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

웹소켓을 적용하면서 동시성에 대한 문제로 고려해야할 부분들이 많네요..
그럼에도 열심히 정리해서 공유하고 적용해주신 부분 멎져요 콜리 ~ 👍
간단한 리뷰만 남겨서 approve 드리고 가요
구체적인 부분들은 오프라인에서 말하는게 더 이야기가 잘될 것 같아요 🤔

@@ -0,0 +1,24 @@
package com.ody.util;
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

웹소켓 Service에서만 사용하는 클래스인데 TimeCache라는 범용적인 이름을 사용해서 패키지 위치가 애매한 것 같은데
좀더 구체적으로 작성해준다면 적절한 패키지 위치가 파악되지 않을까요 ??

  • @component를 사용해 Bean으로 등록한 부분에 대해서 조조가 말한데로 대면에서 이야기해보면 더 좋을꺼같네요 ! 어차피 하루 조금 안남았으니

void reserveCoordinatesTriggerOnceWhenEtaUpdate() throws InterruptedException {
Meeting meeting = meetingRepository.save(Fixture.ODY_MEETING);

doNothing().doNothing()
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

[질문]
있는 문법인가요 ??

Suggested change
doNothing().doNothing()
doNothing()

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Mockito에서 doXXX는 호출 한번에 해당하는 행위를 stubbing 하는데, 현재 MessagSender가 open 시 최초 1번, 추후 update시 1번 호출되고 있어요.

실제 messageSender를 호출하지 않도록 2번 호출 행위에 대한 stubbing을 지정해준 것입니다!

signal.countDown();
}

signal.await(); //signal이 0이 되면 넘어감
Copy link
Contributor

@hyeon0208 hyeon0208 Sep 17, 2024

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

[제안]
await()만 사용하게 되면 CountDownLatch의 인자로 설정한 스레드가 다 처리되고 0이 될 때까지 무한정되기하게 돼요
내부 오류로 signal.countDown();이 호출되지 않아 0이되지 않는다면 실패한 테스트일 것임에도 불구하고 무한정대기해야하는 상황에 놓일 수 있으니
await(3 TimeUnit.SECONDS)로 타임아웃 시간을 지정해주면 어떨까요 ?

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

이런거 어케아는데 카키 참잘해..

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

반영 완료!

Copy link
Contributor

@mzeong mzeong left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

테스트 짜느라 고생 많았어요 🥦👍

Comment on lines 29 to 30
@DirtiesContext(classMode = ClassMode.BEFORE_EACH_TEST_METHOD)
public class EtaSocketConcurrencyTest extends BaseServiceTest {
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

제가 테스트해봤을 땐 둘이 같은 객체라 둘 다 날아가네요!

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

Successfully merging this pull request may close these issues.

test: 웹소켓 로직 테스트 개선
4 participants