인프런 커뮤니티 질문&답변

Jinyoung Choi님의 프로필 이미지

작성한 질문수

Practical Testing: 실용적인 테스트 가이드

EventListener 단위테스트와, 이벤트가 잘 작동하는지 통합테스트는 어떻게 하는걸까요?

해결된 질문

작성

·

246

·

수정됨

0

강사님, 안녕하세요! Event 테스트 관련해서 질문이 있습니다!

현재 진행하고 있는 프로젝트에서 이벤트를 활용해서 책임을 분리하고자 했는데요.

질문을 드리기 전에, 잠깐 이벤트 적용처에 대해 간략히 설명해보자면 아래와 같습니다.

현재 제 애플리케이션은 회원이 가입하게되면, 해당 유저와 1:1 연관관계를 갖는 메인계좌도 같이 생성을 해주어야합니다.

따라서 회원가입 이벤트가 발생하면 메인계좌를 생성할수 있도록 아래와 같이 이벤트 리스너를 작성해주었습니다.

@Slf4j
@Component
@RequiredArgsConstructor
public class MemberSignupEventListener {

  private final CheckingAccountService checkingAccountService;

  @TransactionalEventListener(phase = BEFORE_COMMIT)
  public void createCheckingAccount(MemberSignupEvent event) {
    log.info("MemberSignupEventListener : 회원 가입 이벤트 발생 수신: 회원아이디 = {}", event.getMemberId());
    checkingAccountService.create(event.getMemberId());
  }
}

첫번째 질문으로는, 이벤트가 publish 되었을 때, 이벤트 리스너가 해당 이벤트를 잘 수신하는 지를 어떻게 테스트할 수 있을 지가 궁금합니다.

제가 아래와 같이 테스트 코드를 작성해보았을 때,

@SpringBootTest
class MemberSignupEventListenerTest {

  @Autowired
  ApplicationEventPublisher eventPublisher;

  @MockBean
  MemberSignupEventListener memberSignupEventListener;

  @Test
  @DisplayName("회원가입이 이루어지면 회원가입 리스너가 이벤트를 수신한다.")
  void when_signup_then_create_checkingAccount() {
    // given
    Long memberId = 1L;
    MemberSignupEvent event = MemberSignupEvent.of(memberId);

    // when
    eventPublisher.publishEvent(event);

    // then
    verify(memberSignupEventListener).createCheckingAccount(any(MemberSignupEvent.class));
  }
}

실패메시지는 아래와 같았습니다.

Wanted but not invoked:
memberSignupEventListener bean.createCheckingAccount(
    <any member.event.MemberSignupEvent>
);
-> at member.event.MemberSignupEventListener.createCheckingAccount(MemberSignupEventListener.java:20)
Actually, there were zero interactions with this mock.

두번째 질문으로는, 회원가입 요청이 들어왔을 때, 메인계좌까지 잘 생성되는 지 통합테스트를 하고자 하는데, 어떻게 할 수 있을지가 궁금합니다.

통합테스트 코드는 아래와 같이 작성해보았는데요.

회원 레포지토지와 메인계좌 레포지토리에 데이터가 생성됐는 지로 검증하려 했습니다.

(http 테스트로는 회원가입 시 회원데이터와 메인계좌가 DB에 생성되는 것을 확인했습니다.)

아래 테스트 같은 경우 메인계좌의 DB 내 존재여부가 false로 나오면서 실패했고,
이벤트 리스너와 메인계좌 생성 메서드 코드 라인을 잘 따라가는 지 디버그를 찍어봐도, 디버그로는 추적할 수가 없었습니다.

@TransactionalEventListener(phase = BEFORE_COMMIT)으로 작성했기 때문에 동기적으로 동작할 것이라 생각했고, 별도의 스레드로 동작하지 않을 것 같은데, 디버그로 왜 추적이 안되는지도 잘 모르겠습니다..

@DisplayName("회원 통합테스트")
public class MemberIntegrationTest extends IntegrationTestSupport {

  private static final String URL = "/member";

  @Autowired
  MemberRepository memberRepository;

  @Autowired
  CheckingAccountRepository checkingAccountRepository;

  @Test
  @DisplayName("회원 가입")
  void signup() throws Exception {

    // given
    MemberSignupRequest request = MemberSignupRequest.builder()
        .username("testUser")
        .password("1234")
        .build();

    // when
    mockMvc.perform(post(URL)
            .contentType(APPLICATION_JSON)
            .content(objectMapper.writeValueAsString(request)))
        .andExpect(status().isCreated())
        .andDo(print());

    // then
    assertThat(memberRepository.existsById(1L)).isTrue();
    assertThat(checkingAccountRepository.existsById(1L)).isTrue();
  }
}

이벤트 관련해서는 어떻게 테스트할 수 있을까요?
실무에서는 보통 어떤 방법으로 진행될까요?


---2024-08-19 수정 내용---
혹시 jpa.hibernate.ddl-auto를 create-drop으로 해서 그런걸까요..?

답변 2

1

박우빈님의 프로필 이미지
박우빈
지식공유자

안녕하세요, Jinyoung Choi 님!

제가 코드를 직접 테스트해본 것은 아니라서, 추측으로라도 한번 말씀드려 보겠습니다.

 

첫번째 질문으로는, 이벤트가 publish 되었을 때, 이벤트 리스너가 해당 이벤트를 잘 수신하는 지를 어떻게 테스트할 수 있을 지가 궁금합니다.

일단, 테스트 하고자 하는 내용에는 두 가지가 있을 것 같은데요.

  • 회원가입 이벤트(MemberSignupEvent)가 들어왔을 때 메인 계좌를 잘 생성하는지

  • 회원가입이 수행된 이후, 메인 계좌를 생성하는 진입점까지 회원가입 이벤트가 잘 전달되는지

아마 두 번째 케이스를 질문 주신 것 같아요.
저는 일단 첫 번째 케이스를 확실하게 단위 or 통합 테스트로 검증하는 것이 중요하다고 말씀 드리고 싶고요.

두 번째 케이스를 검증하기 위해서는 통합 or 인수 테스트가 필요하다고 생각되지만, 첫 번째 케이스를 검증하는 것보다는 중요도가 떨어진다고 생각합니다. (실무 기준)

왜냐하면 내부 이벤트를 전달하는 것은 순수하게 스프링의 기능이거든요.
제 강의에서도 언급드렸지만, 이미 검증된 스프링 프레임워크 자체를 다시 내 테스트 코드로 검증하려고 하는 것이 리소스를 고려하였을 때 효율적으로 테스팅하고 있는 것인가, 에 대해서 생각해볼 필요가 있습니다.

다만, 현재 나의 도메인과 해당 도메인의 핵심 기능을 고려했을 때 중요도가 매우 높은 부분일 수도 있기 때문에, 그런 경우에는 신경 써서 더 풍부한 테스트를 작성해 볼 수도 있겠습니다 ㅎㅎ
이벤트 전달이 스프링의 순수 기능이라고 했지만, 정말 내가 설정을 잘못하거나 해서 전달이 안 될 수도 있으니까요.

물론 실무가 아닌 학습 단계신 것 같아서, 관련 테스트를 작성해보는 것은 매우 좋다고 생각합니다 👍

 

두번째 질문으로는, 회원가입 요청이 들어왔을 때, 메인계좌까지 잘 생성되는 지 통합테스트를 하고자 하는데, 어떻게 할 수 있을지가 궁금합니다.

아래 테스트 같은 경우 메인계좌의 DB 내 존재여부가 false로 나오면서 실패했고,
이벤트 리스너와 메인계좌 생성 메서드 코드 라인을 잘 따라가는 지 디버그를 찍어봐도, 디버그로는 추적할 수가 없었습니다.

@TransactionalEventListener(phase = BEFORE_COMMIT)으로 작성했기 때문에 동기적으로 동작할 것이라 생각했고, 별도의 스레드로 동작하지 않을 것 같은데, 디버그로 왜 추적이 안되는지도 잘 모르겠습니다..

주어진 코드만 보고는 원인 파악이 쉽지는 않은데요. 몇 가지 의심해볼 수 있는 포인트를 남겨보겠습니다.

  • 테스트를 수행하는 스레드와 스프링 서버 내에서 회원가입/계좌생성 작업을 수행하는 스레드가 다르기 때문에, 테스트 코드가 먼저 종료되지는 않았는지

  • 생성한 회원의 ID가 정말 1L이 맞을지

    • 이렇게 auto-increment된 ID를 직접적으로 추측해서 테스트하는 방식은 권장하지 않습니다. 자동생성되는 ID 값은 언제든 외부 상황(다른 테스트 등)에 따라 변할 수 있으니까요.

  • MockMvc를 사용했을 때, 실제로 요청이 들어갔는지, 어디까지 수행되었는지 각 단계별로 로그를 남겨서 확인하기

  • IntegrationTestSupport 에서 어떤 컴포넌트를 mocking하고 있지는 않은지

    • 관련 프로덕션 코드에 참여하는 Bean 등이 mocking되어 있다면 말씀하신대로 코드 수행이 안 될 것 같아서요.

주어진 테스트 코드 외에도 외부 요소 때문에 실패할 수도 있을 것 같아서, 종합적으로 고려해보시면 좋을 것 같아요.
더불어, HTTP 테스트 시에는 성공하셨다고 했는데, 이를 기반으로 인수 테스트에 대해 학습해보시고 한번 적용해보시는 것 도 좋을 것 같습니다. RestAssured 등을 사용해볼 수 있어요.
한번 시도해보시고 말씀 주세요!

감사합니다 🙂

Jinyoung Choi님의 프로필 이미지
Jinyoung Choi
질문자

안녕하세요! 강사님. 저번에 상세하게 답변해주셔서 너무 감사했습니다! 말씀해주신 인수 테스트에 대해서 알아보고, RestAssured를 사용해서 테스트 적용해보았고, 테스트 무사히 성공 했습니다. 감사합니다! 다만, 적용해보면서 또 여러가지 궁금점이 생겼어요.

질문은 총 5가지 입니다!

1. RestAssured를 사용해서 API 테스트를 할 때 DB까지 테스트 하는 테스트를 보통 통합테스트라고 하는건가요? 그렇다면 E2E 테스트는 실무에서 보통 어떻게 수행하는지 궁금합니다.

2.통합 테스트라고 하면 모듈간의 통합을 검증하기 위함이라고 하던데, 개발 환경에서 실행되는 테스트일까요? 아니면 실제 운영환경과 동일한 환경에서 테스트를 수행해야 되는걸까요?

3. 인수테스트도 통합테스트에 속하는 개념인걸까요?

4. 통합테스트(인수테스트)에서 실패 케이스도 작성해야 하는 걸까요?

5. 다들 RestAssured, MockMvc를 사용해서 인수테스트를 하던데, 그럼 통합테스트는 어떤걸 사용하는지 궁금합니다. (찾아보니 @SpringBootTest가 통합 테스트는 아니라고 해서요!)

0

안녕하세요, 인프런 AI 인턴입니다. Jinyoung Choi님, 이벤트 리스너와 관련된 단위 및 통합 테스트에 대해 질문해 주셨네요. 이벤트 리스너가 잘 동작하는지 확인하는 테스트는 애플리케이션의 정확성을 보장하는 중요한 단계 중 하나입니다. 관련하여 제가 본문에서 제공한 내용과 유사한 질문에 대한 답변을 찾을 수 있었습니다.

먼저, 이벤트 리스너가 해당 이벤트를 잘 수신하는지 테스트하는 방법에 대한 질문입니다. 이와 유사한 맥락으로, Mock Test에 관련한 질문과 관련된 답변에서 이벤트나 메서드 호출을 검증할 때 Mock 객체를 활용하는 방법에 대해 자세히 설명되어 있습니다. 이러한 Mocking 기법을 이벤트 리스너 테스트에도 적용할 수 있습니다. @MockBean을 사용해 실제 컴포넌트 대신 Mock 객체를 주입함으로써, 이벤트 발행 후 해당 Mock 객체의 메서드 호출 여부를 verify()를 통해 확인할 수 있습니다.

다음으로, 통합테스트와 관련한 질문입니다. 여기서는 @TransactionalEventListener를 통한 이벤트 리스너가 회원가입 시점에 메인계좌 생성을 올바르게 수행하는지 확인하는 테스트에 대한 것입니다. 이와 관련하여, 단위테스트의 개념에 대해서 질문 드립니다!에 제공된 답변에서, 단위 테스트와 통합 테스트의 구분 및 각각의 목적에 대해 설명하는 부분이 유익할 수 있습니다. 통합 테스트는 애플리케이션의 여러 컴포넌트가 서로 어떻게 상호작용하는지 확인하는 것이 목적입니다. 이 경우, 이벤트를 발행하고 데이터베이스 내에 예상되는 변화가 실제로 발생하는지를 검증하는 것이 중요합니다.

실무에서는 이벤트 기반 테스트를 위해, 테스트 환경에서 이벤트를 발행하고 예상 결과를 정의한 뒤 실제 결과와 비교하는 방식으로 진행됩니다. 이벤트 리스너의 동작을 검증하는 단위테스트와 함께, 전체 애플리케이션의 흐름 내에서 이벤트가 예상대로 처리되는지 확인하는 통합테스트가 모두 필요합니다.

마지막으로, 테스트 과정에서 발생하는 특정 이슈(예: 디버그 추적 문제)에 대해서는, 해당 컴포넌트의 로직을 세분화하여 단계별로 검증하거나, 스프링 테스트 관련 문서 및 StackOverflow와 같은 커뮤니티의 도움을 받아보는 것도 좋은 방법입니다.

저는 질문자님의 이해를 돕고자 지식공유자님의 기존 답변을 참고하여 유사한 답변 링크를 추천해드리고 있습니다.
현재 베타 기능으로 답변이 만족스럽지 않을 수 있는 점 양해 부탁드립니다. 🙏
추가적으로 궁금한 점이 있으시면, 이어서 질문해 주세요. 곧 지식공유자께서 답변해 주실 것입니다.