레이어드 아키텍처에서의 테스트

인프런에서 진행한 워밍업 클럽 스터디 - 테스트 코드 / 클린 코드를 수강하며 들었던 내용을 정리합니다.

Persistence Layer

image.png

  • DB와 통신하는 역할
  • 비즈니스 가공 로직이 들어가면 안 됨
  • 단지 Data에 대한 CRUD, 입출입
  • @SpringDataJpa를 사용한 통합 테스트
    • H2 등 테스트를 위한 인메모리 DB를 활용
    • Fake 객체: 빠른 속도 검증

Persistence Layer는 데이터베이스와 직접 통신하는 계층으로, 데이터의 CRUD(생성, 조회, 수정, 삭제) 및 입출력 처리를 담당한다.

이 계층에서는 비즈니스 로직을 포함해서는 안 되며, 순수하게 데이터 접근 및 조작만 수행해야 한다.

테스트를 위해 @SpringDataJpa를 활용한 통합 테스트를 진행할 수 있다.

일반적으로 H2와 같은 인메모리 데이터베이스를 사용하여 빠른 속도로 검증할 수 있으며, 필요에 따라 Fake 객체를 활용하여 특정 동작을 모사할 수도 있다.

이러한 방식을 통해 실제 데이터베이스를 사용하지 않고도 독립적인 테스트 환경을 구축할 수 있다.

Business Layer

image.png

  • 비즈니스 로직을 구현하는 역할
  • Persistence Layer와의 상호작용을 통해 비즈니스 로직을 전개시킴
  • 트랜잭션에 대한 보장
  • 읽기
    • CQRS
    • @Transactional(readonly=true)로 처리
  • @DataJpaTest: @Transactional이 포함됨
  • → @SpringBootTest의 성능이 조금 더 좋다.

Business Layer는 애플리케이션의 핵심 비즈니스 로직을 구현하는 계층이다.

Persistence Layer와 상호작용하면서 트랜잭션을 보장하고, 도메인 규칙을 적용하며, 애플리케이션의 주요 흐름을 담당한다.

특히 읽기와 쓰기 작업을 분리하는 CQRS 패턴을 적용할 수 있으며, 읽기 전용 트랜잭션을 위해 @Transactional(readOnly=true)를 사용할 수 있다.

이를 통해 데이터 일관성을 유지하면서도 성능 최적화를 할 수 있다.

이 계층에서의 테스트는 @DataJpaTest 또는 @SpringBootTest를 활용하여 수행할 수 있다.

@DataJpaTest는 기본적으로 @Transactional을 포함하고 있어 롤백을 통한 테스트 환경을 유지할 수 있으며,

@SpringBootTest는 전반적인 통합 테스트에 유리하다.

비즈니스 로직의 복잡성이 증가할수록 철저한 테스트가 필요하며,

책임을 명확히 분리하는 것이 유지보수성과 확장성을 높이는 데 도움이 된다.


  • 가장 많은 테스트가 이뤄질 곳이라고 여긴 곳
  • 책임의 분리가 많을수록 테스트 단위가 늘겠지만, 오히려 결합이 느슨해지면서 테스트 자체가 보장하는 범위도 줄어들기 때문에 더욱 로직을 명확히 파악할 수 있음

Presentation Layer

image.png

  • 외부 세계의 “요청”에 대한 “검증”
  • 즉 값에 대한 밸리데이션

비즈니스 로직을 전개시키기 위한 필수 조건을 가지고 있는가?

  • Persistence Layer / Business Layer Mocking 처리
  • @WebMvcTest: 클라이언트의 요청에 대한 대역을 고려
  • @MockBean: 서비스 레이어 대역을 고려

Presentation Layer는 외부 세계의 요청을 받아 검증하는 계층이다. 이 계층은 컨트롤러를 중심으로 클라이언트의 요청을 처리하며, 입력 데이터의 유효성을 검사하는 역할을 수행한다. 즉, 사용자가 요청한 데이터가 비즈니스 로직을 수행하기 위한 필수 조건을 충족하는지 확인하는 것이 주요 목적이다.

이 계층의 테스트를 위해 @WebMvcTest를 활용할 수 있으며, Persistence Layer와 Business Layer를 Mocking 처리하여 독립적인 테스트 환경을 구축할 수 있다. 또한, @MockBean을 사용하여 서비스 레이어의 동작을 모의(Mock)할 수 있다.

이 계층에서의 테스트는 몇 가지 어려움이 존재한다.

해피 케이스 이외의 다양한 예외 상황을 고려하기 어렵다.

UI와 연동될 때 보다 정확한 테스트가 가능하지만, 단독으로 테스트할 경우 한계가 있다.

모든 의존성을 Mocking하면 실제 로직과의 차이가 발생할 수 있어, 테스트의 실효성을 고민하게 된다.

따라서, 실제 API 테스트를 먼저 수행한 후 테스트 케이스를 추가하는 방식이 효과적이다. 기본적으로 @Valid 어노테이션이 정상적으로 동작하는지 확인하는 테스트를 포함하며, 이후 실제 호출과 연동된 후 발생하는 이슈에 대해 대응하는 것이 현실적인 접근 방식이다.


  • 개인적으로 가장 어려웠던 부분
    • 이유 1: 해피 케이스 외의 것들이 잘 떠오르지 않음
    • 이유 2: UI와 연동되었을 때 정확한 테스트가 이뤄지는 곳이 많음
    • 이유 3: 다 mocking할 거면 테스트의 존재 의의가 있는가?
  • 내렸던 결론: 실제 테스트 후 테스트 케이스를 넣어 두자.
    • 임계값이나 Valid 어노테이션이 작동하는 것을 기본적으로 테스트에 넣는다.
    • 추후 실제 호출부와 연결되었을 때 이슈가 발생하면 해당 부분을 대응한다.

Who is?

1의 개발로 N배의 가치, N개의 문제를 풀고 싶은 개발자