5주 차 강의 내용 정리

  • 단위 테스트
  • 통합 테스트

단위 테스트

 - 특정 단위(테스트 대상)가 의도한 대로 작동하는지 검증

 

단위란? 

 - 사람마다 정의가 다를 수 있음. 작은부분이라는 점에 초점을 맞추면 됨.

 - 단일 메서드에서 전체 클래스까지 될 수 있음.

 

통합과 고립(Sociable and Solitary)

 - 단위 테스트 작성 시 관계를 맺고 있는 대상(협력 객체)이 있는 경우를 고려해야 함.

 - 협력 객체를 실제 객체로 사용할지, Mock 객체로 사용할지에 따라 테스트 구현이 달라짐.

 

단위의 정의를 논하기 앞서 테스트하는 단위가 통합(Sociable)되어야 하는지 고립(Solitary)되어야 하는지 고려해야 .


Test Double

 - 테스트 목적으로 실제 객체 대신 사용되는 모든 종류의 객체를 표현하는 일반 용어.

 - 즉 실제(클래스, 모듈, 매서드)를 가짜 버전으로 대체한다는 의미.

Stubbing

 - 테스트 동안 호출이 되면 미리 지정된 답변으로 응답.

 - 미리 지정된 것 외의 것에 대해서는 응답 하지 않음.

 

Mockito, MockitoExtension, Spring 등을 활용한 Stubbing 방법이 있다.

 

MockitoExtension을 활용한 예시

@ExtendWith(MockitoExtension.class)
public class AuthServiceTest {
    public static final String EMAIL = "email@email.com";
    public static final String PASSWORD = "password";
    public static final int AGE = 10;

    private AuthService authService;

    @Mock
    private MemberRepository memberRepository;
    @Mock
    private JwtTokenProvider jwtTokenProvider;

    @BeforeEach
    void setUp() {
        authService = new AuthService(memberRepository, jwtTokenProvider);
    }

    @Test
    void login() {
        when(memberRepository.findByEmail(anyString())).thenReturn(Optional.of(new Member(EMAIL, PASSWORD, AGE)));
        when(jwtTokenProvider.createToken(anyString())).thenReturn("TOKEN");

        TokenResponse token = authService.login(new TokenRequest(EMAIL, PASSWORD));

        assertThat(token.getAccessToken()).isNotBlank();
    }
}

 

Test Double은 언제 쓰는가?

 - 테스트 대상이 협력 객체를 가질 때 사용한다.

 

실제 객체를 사용하면 협력 객체의 행위를 협력 객체 스스로가 정의

가짜 객체를 사용하면 협력 객체의 행위를 테스트가 정의


가짜 객체 특징

  • 가짜 객체를 사용 할 경우 테스트 대상을 검증할 때 외부 요인(협력 객체)으로 부터 철저히 격리
  • 하지만 테스트가 협력 객체의 상세 구현을 알아야 함

실제 객체 특징

  • 실제 객체를 사용 할 경우 협력 객체의 상세 구현에 대해서 알 필요가 없음
  • 하지만 협력 객체의 정상 동작 여부에 영향을 받음

테스트 코드를 작성 할 때

  • 가짜 객체를 활용하면 실제 객체를 사용할 때 보다 조금 더 편하게 테스트를 작성할 수 있음
  • 하지만 상세 구현에 의존하는 테스트가 될 수 있음

 


추천하는 방법

  • 우선 TDD를 연습할 때 가급적이면 실제 객체를 활용하는 것을 우선으로 진행
  • 테스트 작성이 어렵거나 흐름이 이어지지 않는다면 테스트 더블을 활용하는 방법으로 접근하시는 것을 추천

통합 테스트

 - 우리가 만드는 대부분의 애플리케이션은 데이터베이스, 파일 시스템, 외부 라이브러리 등과 같이 다른 애플리케이션과 통합되어 개발하는 경우가 많음

 - 적은 비용과 빠른 테스트를 위해 단위 테스트를 주로 사용하지만 실제로 상호 작용하는 내용에 대한 검증도 반드시 필요함

 - 예를 들면 데이터베이스와의 통합을 테스트하는 경우 테스트를 실행할 데이터베이스를 실행해야함

 

외부 라이브러리

테스트의 필요성

 - 라이브러리 기능에 대한 검증은 필요 없음.

 - 단, 그 부분을 사용하는 부분에 대한 검증이 필요.

 

변경할 수 없는 코드 검증 시 실제 객체 사용

 - 작동 원리를 깊이 이해하기 어려움

 - Mock 객체와 실제 객체의 행위를 일치시키기 어려움

@Test
void findPath() {
    // graphService에서 활용하는 Jgrpah라이브러리의 객체를 실제 객체로 사용한다.
    List<Long> stationIds = graphService.findPath(
            Lists.list(line1, line2), 
            station3.getId(), 
            station5.getId(), 
            PathType.DISTANCE
    );

    assertThat(stationIds).hasSize(5);
    assertThat(stationIds.get(0)).isEqualTo(3L);
    assertThat(stationIds.get(1)).isEqualTo(2L);
    assertThat(stationIds.get(2)).isEqualTo(1L);
    assertThat(stationIds.get(3)).isEqualTo(4L);
    assertThat(stationIds.get(4)).isEqualTo(5L);
}

데이터 베이스

 - 테스트를 쉽게 하기 위해 메모리 내 H2 데이터베이스에 연결해서 한다.

@DataJdbcTest
public class StationRepositoryTest {
    @Autowired
    private StationRepository stationRepository;

    @Test
    void saveStation() {
        String stationName = "강남역";
        stationRepository.save(new Station(stationName));

        assertThrows(DbActionExecutionException.class, () -> 
            stationRepository.save(new Station(stationName))
        );
    }
}

 

통합 테스트 vs 단위 테스트

  • 통합 테스트는 통합하고 있는 부분을 실제와 가까운 환경에서 검증하여 기능이 정상적으로 여부를 검증
  • 단위 테스트는 통합하고 있는 부분이 정상적으로 동작한다고 가정하고 단일 기능에 대해서만 검증

참고자료

+ Recent posts