본문 바로가기
카테고리 없음

[Test] void 메서드 테스트

by meteorfish 2025. 4. 5.
728x90

문제상황

BlueprintBusiness의 테스트 코드를 작성하는 과정에서 BlueprintService에 대한 스텁을 지정했다. 그러나 해당 BlueprintService의 메서드는 반환형이 void이기 때문에 값 기반의 검증이 불가능하다.

// BlueprintBusinessTest
@Test
void 도면을_생성한다() {
    // given
    BlueprintRequest request = BlueprintFixture.createBlueprintRequest();
    doNothing().when(blueprintService).saveBlueprint(any(Blueprint.class));

    // when
    blueprintBusiness.createBlueprint(request);

    // then
    assertThat() <- ????
}
// BlueprintService
public void saveBlueprint(Blueprint blueprint) {
    validateBlueprintIsNull(blueprint);
    blueprintRepository.save(blueprint);
}

 


고민해볼 점

Response에서 반환값 이용하기

생성된 Blueprint 객체를 Business가 Controller로 그대로 반환하여 Response Body에 표시되도록 하는 것도 좋다고 생각한다.
예를 들어, '도면 생성이 완료됐습니다. {id: 3, name: 골프장 도면}'

 

Repository에서 반환값이 없는 경우

update와 delete의 경우 반환값이 void이기 때문에 service에서 반환값을 지정해야 한다. 따라서 별도의 DTO를 통해 넘겨주도록 하여 이부분을 해결했다.

@Test
void 도면의_정보_중_도면이름과_작가이름을_수정한다() {
    // given
    final long id = 1L;

    Blueprint blueprint = BlueprintFixture.createBlueprint();
    BlueprintUpdateRequest request = BlueprintFixture.createBlueprintUpdateRequest();
    BlueprintUpdateSuccess success = BlueprintUpdateSuccess.builder().blueprintId(id).isSuccess(true).build();

    when(blueprintService.findBlueprintById(any(Long.class))).thenReturn(blueprint);
    when(blueprintService.updateBlueprint(any(Blueprint.class), any(BlueprintUpdateRequest.class)))
            .thenReturn(success);

    // when
    BlueprintUpdateSuccess blueprintUpdateSuccess = blueprintBusiness.editBlueprint(request);

    // then
    assertThat(blueprintUpdateSuccess.blueprintId()).isEqualTo(request.id());
    assertThat(blueprintUpdateSuccess.isSuccess()).isEqualTo(true);
}
// BlueprintBusiness

@Transactional
public BlueprintUpdateSuccess editBlueprint(BlueprintUpdateRequest request) {
    Blueprint blueprint = blueprintService.findBlueprintById(request.id());
    return blueprintService.updateBlueprint(blueprint, request);
}

 

해결 방안

  1. 행위 기반 검증하기
    assertThat을 통한 상태 기반 검증이 불가능하기 때문에 메서드가 실행된 적 있는지 확인하는 행위 기반 검증을 이용하는 것이다.
  2. 반환값 변경하기
    String을 통해 메시지를 전송하거나 Boolean을 통해 로직 처리 여부를 반환한다.

행위 기반의 문제점

assertThat을 이용한 상태 확인과 verify를 이용한 행위 확인 중 어떤 것이 더 좋은 테스트 일까? 행위 기반 검증은 사실상 알고리즘을 테스트하는 것과 같다. 이는 논리를 검증하는 것이므로 테스트 대상이 현재 코드 외에 다른 방법으로 개발하는 것이 불가능해진다. 따라서 시스템 코드가 전체적으로 경직될 수 있다. 또한 구현에 의존하기 때문에 프로덕션 코드 변경 시 테스트 코드의 변경도 수반될 가능성이 높다.

숨겨진 출력

blueprintSerivce 내부에서 아무리 로그를 남긴다고 하더라도 외부 모듈에선 이를 확인할 수 없다. 이를 숨겨진 출력이라고 부르고 이를 해결하기 위해 반환값을 적극적으로 이용해야한다. String을 타입을 통해 '생성 완료 {id: 3}'와 같이 반환을 할 수 도 있지만, Blueprint 객체를 넘기거나 성공 여부를 알리기 위한 DTO를 통해서도 해결할 수 있다.

 

 


해결

도면 생성의 경우, 생성된 Blueprint를 반환하도록 하여 상태 기반 검증을 하도록 하였다.

@Test
void 도면을_생성한다() {
    // given
    BlueprintRequest request = BlueprintFixture.createBlueprintRequest();
    Blueprint blueprint = Blueprint.fromRequest(request);

    when(blueprintService.saveBlueprint(any(Blueprint.class)))
            .thenReturn(blueprint);

    // when
    Blueprint savedBlueprint = blueprintBusiness.createBlueprint(request);

    // then
    assertThat(savedBlueprint.getBlueprintName()).isEqualTo("대한민국 마을");
}

 


 

 

 

728x90