비동기 코드를 테스트 하는 방법

Choi Wontak·2025년 5월 23일

아이쿠MSA

목록 보기
10/12

난이도 ⭐️
작성 날짜 2025.05.23

고민 내용

아이쿠 프로젝트에서는 포인트의 증감과 관련된 모든 메서드에서의 동일한 로직 호출로 발생하는 문제를 해결하기 위해 이를 이벤트 Pub/Sub으로 처리하고 있다.

@Async
@TransactionalEventListener(phase = TransactionPhase.AFTER_COMMIT)
public void pointChangeEvent(PointChangeEvent event) {
        pointChangeFacade.makePointChange(event.getMemberId(), event.getSign(), event.getPointAmount(), event.getReason(), event.getReasonId());
}

포인트 증감을 발생시키는 이벤트를 Listener로 구독하여 포인트 증감 이벤트 발생 시 해야하는 로직이 담긴 Facade 클래스의 메서드를 실행한다.

테스트 코드는 다음과 같다.

handler.pointChangeEvent(
          new PointChangeEvent(member.getId(), PointChangeType.PLUS, 100, PointChangeReason.EVENT, 11L)
);

assertThat(member.getPoint()).isEqualTo(100);

그런데 여기서 문제가 발생했다!

알고보니 @Async의 문제였다.
호출되는 리스너에 달려있는 Async 어노테이션을 주석처리하니 테스트가 통과하였다.

테스트는 분리된 스레드 작업이 끝나기를 기다려주지 않기 때문에 포인트 증가 로직이 반영되기 전에 테스트를 진행해버린다!

🤔 비동기는 테스트를 어떻게 해야될까?


찾아보기

1. 테스트 환경설정 변경하기

테스트의 실행환경에서는 비동기가 필요 없다.
AsyncConfigurer를 통해 비동기를 처리하는 방식을 동기로 처리하도록 바꿔준다.

@Configuration
public class TestAsyncConfig implements AsyncConfigurer {
    @Override
    public Executor getAsyncExecutor() {
        return new SyncTaskExecutor();
    }
}
// 테스트 클래스
@Import(TestAsyncConfig.class)

잘 통과한다!

2. Async가 정말 필요한가?

사실 1번 방법이 번거롭기도 해서, Async일 필요성을 다시 확인해보았다.

  1. 응답 시간을 줄이기 위한 목적

포인트 증가에서 비관적 락을 사용하기 때문에 약간의 딜레이가 있을 수 있지만, 성능에 크게 영향을 주지는 않는다.
로그를 남기는 부분도 마찬가지로 I/O 부하는 크게 없다.

  1. 실패해도 주 로직에는 영향이 없어야 할 때

이 부분이 핵심 내용인 것 같다!
다른 서비스에서는 SAGA 패턴을 통해 롤백할 정도로 트랜잭션을 강제로 붙이고 있다.
다시 말해서, 포인트의 증감 로직은 이벤트를 발생시키는 Pub 로직에서도 Sub 로직의 영향을 받아야 한다.

+) 추가로 Async를 사용했을 때 테스트와 디버깅 비용이 더 발생한다는 문제도 있다.

결론 : 현재 상황에선 Async가 필요하지 않다!


결론

Async 때문에 발생한 테스트 문제 덕분에 Async의 필요성을 돌아보게 되는 계기가 되었다.

결론
기술을 쓰기 전에... 항상 의심하는 습관을 갖자!


profile
백엔드 주니어 주니어 개발자

0개의 댓글