아키텍처 요소 테스트

테스트 피라미드

  • 만드는 비용이 적고 유지보수하기 쉽고 빨리 실행되고 안정적인 작은 크기의 테스트들에 대해 높은 커버리지를 유지하는 것이 중요하다.

  • 테스트가 비싸질수록 테스트의 커버리지 목표는 낮게 잡아야 시간을 덜 쓸 수 있다.

  • 단위테스트

    • 피라미드의 토대, 하나의 클래스를 인스턴스화하고 클래스의 인터페이스를 통해 기능을 테스트

    • 다른 클래스에 의존하면 인스턴스화 대신 mock으로 대체한다

  • 통합테스트

    • 연결된 여러 유닛을 인스턴스화하고 시작점이 되는 클래스의 인터페이스로 데이터 보낸 후 유닛들이 잘 동작하는지 검증

  • 시스템 테스트

    • 애플리케이션을 구성하는 모든 객체 네트워크를 가동해 특정 유스케이스가 전 계층에서 동작하는지 검증

    • UI를 포함하는 end-to-end 테스트층이 있을 수 있다.

도메인 엔티티 테스트

  • 단위테스트를 사용해 간단하게 테스트할 수 있다.

  • 비즈니스 규칙을 검증하기에 적절하다.

  • 도메인 엔티티의 행동은 다른 클래스에 거의 의존하지 않아 단위 테스트 말고 다른 종류의 테스트는 필요하지 않다.

유스케이스 테스트

  • 단위테스트이긴 하지만 의존성의 상호작용을 테스트하므로 통합테스트라는 의미에 가깝지만 실제 의존성 관리를 하지 않으므로 완전한 통합테스트는 아님

  • 행동-주도 개발(behavior driven develpment)에서 일반적으로 사용되는 방식대로 given/when/then 으로 나눈다.

  • Mockito를 사용해 목 객체를 생성하고 메서드 행동을 정의할 수 있다.

  • 테스트 중인 서비스는 상태가 없어 then 에서 특정 상태를 검증할 수 없다.

  • 서비스가 의존 대상의 특정 메서드와 상호작용했는지 여부를 테스트하는것이 목적

  • 테스트가 코드의 행동 변경뿐만 아니라 구조 변경에도 취약해지므로 모든 동작을 검증하는 대신 핵심만 골라 테스트하는 것이 좋다.

  • 모든 동작을 검증하려하면 클래스가 조금 바뀌어도 테스트를 변경해야 하므로 테스트의 가치가 떨어진다.

웹 어댑터 테스트

  • @MockMvcTest 를 사용해 웹 컨트롤러 테스트하는 방법을 사용한다.

  • 위 어노테이션을 사용하면 스프링이 특정 요청 경로, 자바와 JSON 매핑, HTTP 입력 검증에 필요한 전체 객체 네트워크를 인스턴스화하므로 통합 테스트이다.

  • JSON 문자열 등의 형태로 HTTP를 통해 입력받고 → 유효성 검증하고 → 유스케이스 포맷으로 매핑하고 → 유스케이스로 전달하는 과정을 테스트한다.

  • 웹 컨트롤러는 스프링 프레임워크에 강하게 묶여있으므로 이 프레임워크와 통합된 상태로 테스트하는 것이 합리적이다.

영속성 어댑터 테스트

  • 단순 어댑터 로직 뿐만 아니라 데이터베이스 매핑도 검증하므로 통합 테스트를 적용한다.

  • @DataJpaTest 어노테이션으로 데이터베이스 접근에 필요한 객체 네트워크를 인스턴스화 해야한다는 것을 스프링에게 알려준다.

  • @Import 어노테이션을 추가해서 특정 객체가 이 네트워크에 추가됐다는 것을 표현한다.

  • @Sql("XXX.sql") 어노테이션을 사용해 데이터베이스를 특정 상태로 만들 수 있다.

  • 데이터베이스를 모킹하지 않고 실제로 접근

  • 실제 연동했을 때 SQL 구문의 오류나 테이블과 자바 객체 간의 매핑 에러로 문제 생길 확률이 높아진다.

  • 따라서 프로덕션 환경에서는 실제 데이터베이스를 대상으로 테스트 진행해야 한다.

    Testcontainers같은 라이브러리는 필요하느데이터베이스를 도커 컨테이너에 띄울 수 있어 유용

주요 경로 테스트

  • 전체 애플리케이션을 띄우고 API를 통해 요청 보내고 모든 계층이 동작하는지 시스템 테스트를 진행

  • 유스케이스의 시스템 테스트

  • @SpringBootTest 어노테이션 사용해 스프링이 애플리케이션을 구성하는 모든 객체 네트워크를 띄우고, webEnvironment = WebEnvironment.RANDOM_PORT 한다.

  • TestRestTemplate을 사용하면 실제 HTTP 통신을 통해 요청을 보낸다.

  • 다른 시스템과 통신하는 애플리케이션의 경우 일부 출력 포트 인터페이스만 모킹하면 된다.

  • 헬퍼 메서드를 사용해 여러 상태 검증 시 사용할 수 있는 Domain Specific Language 를 형성

  • 사용자 관점에서 애플리케이션을 검증할 수 있다.

  • 단위 테스트와 통합 테스트를 만들었다면 시스템 테스트는 앞서 커버한 코드와 겹치는 부분이 많지만, 단위/통합테스트로는 못잡는 계층간 매핑 버그 등을 찾아낼 수 있다.

  • 여러 유스케이스를 결합해 시나리오를 만들어 시나리오들을 커버한다면 최신 변경 사항에도 애플리케이션이 망가지지 않았음을 보장할 수 있다.

테스트 커버리지

  • 라인 커버리지는 테스트 성공을 측정하는 데 있어서 잘못된 지표

  • 테스트의 성공 기준은 얼마나 마음편하게 소프트웨어를 배포할 수 있는지이다.

  • 자주 배포할수록 테스트를 더 신뢰할 수 있다.

  • 프로덕션의 버그를 수정하고 이로부터 배우는 것을 우선순위로 하다보면 점점 방대한 케이스를 커버할 수 있는 테스트가 될 것

Last updated