본문 바로가기
🛠 백엔드

[Test] 테스트 코드 작성에 대한 의문점

by meteorfish 2025. 4. 10.
728x90

# 테스트 코드를 왜 작성하는가?

 

나는 크게 3가지의 이유로 테스트 코드를 작성한다고 생각한다.

 

  1. 예외 처리를 위해
  2. 프로덕션 코드 변경에 따른 영향을 쉽게 파악하기 위해
  3. 코드 작성의 의도를 문서화

 

이 중 나는 1번과 3번의 의미가 테스트 코드에서 크다고 생각한다. <<자바/스프링 개발자를 위한 실용주의 프로그래밍>>에서도 3번에 대한 내용을 강조하고 있다. 해당 책에선 대부분의 테스트 코드(80%가량)가 단위 테스트로 이루어져야 한다고 말한다. 그래서 나도 이번 프로젝트에서 각 레이어의 메서드별 테스트 코드를 작성하였다. 그 결과 코드 커버리지는 높일 수 있었지만 테스트 코드 작성의 필요성에 대한 궁금증이 더 커졌다.

 

# 작성할 수록 모르겠는 테스트 코드

현재 자바 진영에서 주로 Mockito 라이브러리를 통해 테스트 코드를 작성한다. 나 또한 Mockito를 통해 테스트 코드를 작성했고, 다른 프로젝트처럼 Stub과 Mock을 통해 작성했다. 내가 의문점이 발생하는 부분은 Service와 Business를 분리한 이후에 발생했다. 현재 Business는 단순히 Service를 호출하고, 데이터 타입을 변경시키는 메서드를 호출하는 역할만 수행하고 있다. 쉽게 말해 프록시 객체의 역할을 수행하고 있기 때문에 테스트 코드 작성에 있어 의미가 상실된다.

 

@Transactional(readOnly = true)
public String findEmail(String name, String phoneNum) {
    Member member = memberService.findOne(name, phoneNum);
    return member.getEmail();
}

이 코드는 이름과 전화번호를 통해 유저의 이메일을 찾는 기능이다. 이를 테스트하기 위해 어떻게 테스트 코드를 작성할 수 있을까?

 

@Test
void 회원의_이메일을_조회한다() {
    // given
    MemberFindEmailRequest request = new MemberFindEmailRequest("홍길동", "01000000000");
    when(memberService.findOne(request.name(), request.phone_num())).thenReturn(member);

    // when
    String email = memberBusiness.findEmail(request.name(), request.phone_num());

    // then
    assertThat(email).isEqualTo(member.getEmail());
    verify(memberService, times(1)).findOne(request.name(), request.phone_num());
}

 

아마 memberService에 대한 Stub을 수행하고 Stub이 잘되었나 확인하는 수준의 코드가 작성될 것이다. 이 테스트 코드를 얻을 수 있는 것은 무엇이 있을까 생각해봤다.

 

1. findEmail의 파라미터 값 예시

2. ...

 

아무리 생각해도 잘 생각되지 않는다. 첫 문단에서 정리한 테스트 코드 작성 이유에 맞춰 생각을 해보자.

 

- 예외 처리를 테스트 할 수 있는가? -> Business에는 예외 처리가 존재하지 않는다.

- 프로덕션 코드의 변경에 따른 영향 파악이 쉬운가? -> 이는 컴파일러에서도 파악이 가능하다.

- 코드 사용을 문서화할 수 있는가? -> 문서화가 되나, 코드만으로 충분히 이해가 가능한 수준이다.

 

따라서 이 테스트 코드는 단순히 코드 커버리지를 위한 테스트 임을 알 수 있다.

그렇다면 어떻게 테스트 코드를 작성해야 할까? 그리고 business는 테스트 코드를 작성할 의미가 아예 없는 것일까?

 

## Stub을 무조건 사용해야 할까?

Stub은 외부 의존성과 의존성을 분리하기 위해 사용된다. Spring MVC는 Controller - Service - Repositorty 등의 계층으로 나뉘어 의존하여 설계하도록 하고 있다. 예를 들어 Service 계층에서 테스트를 하기 위해 DB와 연결하는 Repository의 외부 의존성을 없애기 위해 사용한다.

 

Business에선 Serivce를 호출하고, Service에선 Repository를 호출하기 때문에 Business에서 Service를 Stub하지 않고 Repository를 Stub하게 되면 내부 로직만 테스트가 가능하다. 즉, Business + Service를 묶어 테스트를 하는 것도 단위 테스트라고 봐도 좋다고 생각한다.

 

# 어떻게 해야할까?

현재 예외가 발생하는 입력이 주어졌을 때의 테스트 코드들이 추가되어 있지 않다. 따라서, Business에 대한 테스트 코드의 의미가 더 떨어진다는 생각이 들었다. '만약 Serivce에서 반환한 값이 문제가 있을 경우, Business에선 어떻게 대처할 것인가?'에 대해 생각해보고 이를 테스트 코드로 먼적 작성해보고, 예외 처리 로직을 추가하는 방식으로 해결하는 것이 좋다고 생각한다.

 

https://jojoldu.tistory.com/637

 

Stub 을 이용한 Service 계층 단위 테스트 하기

간혹 Service 계층을 항상 통합 테스트로만 작성하는 경우를 보게됩니다. 모든 Service를 통합 테스트 혹은 E2E 테스트로만 검증하기 보다는 상황에 따라 적절한 Stub을 사용하여 단위 테스트로 작성

jojoldu.tistory.com

 

(24.04.25 추가)

단위 테스트를 읽고 테스트 코드의 본질에 대해 느낀점을 바탕으로 해결책을 작성해본다.

 

## 단위 테스트에서 단위는?

 

단위 테스트라고 하면 보통 메서드 단위의 테스트를 의미한다고 생각한다. 그런데 너무 작은 범위의 테스트는 실효성이 없다. 내가 이전에 가진 단위 테스트의 실효성에 대한 의문점과 같은 이야기이다.

 

단위 테스트에서 단위는 기능을 의미한다. 절대로 메서드 단위를 의미하는 것이다. 따라서, Business와 Service를 묶어 하나의 단위 테스트를 수행하는 것이 옮바른 방법 중 하나라고 생각한다.

 

여기서 우리가 주의깊게 생각해봐야할 점은 Mock과 Stub의 취약성이다. 목과 스텁과 같은 테스트 더블 기법은 구현 세부 사항을 검증해선 안된다. 왜냐하면 테스트 코드의 리펙터링 내성을 지키기 위해선 최종 결과를 바탕으로 검증이 이루어져야 한다. 그렇기 때문에 Business 테스트에서 Service에 대한 모킹을 하는 것은 기능의 완전한 검증을 막는 장애물 역할을 하게된다. 

 

테스트 더블 기법은 외부 API 호출과 같이 테스트 과정에서 부담스러운 기능을 대체하기 위해 사용되어야 한다. 되도록 이면 가짜 객체대신 실제 객체를 적용하여 테스트에 대한 신뢰성을 높이도록 해야한다.

 

728x90

'🛠 백엔드' 카테고리의 다른 글

RequestBody, ResponseBody와 ResponseEntity  (0) 2023.01.18
톰켓이란?  (0) 2023.01.04
HTTP란 무엇인가?  (0) 2023.01.03
Failed to load ApplicationContext 오류  (0) 2022.12.08