레이어 별 단위 테스트, 통합 테스트 전략 선택
스프링 부트 프로젝트에서 헥사고날 아키텍처를 적용했을때 TDD를 어떻게 적용해야하는지 우리가 고려했던 부분을 한번 정리해봤다.
아래 내용은 우리가 실제 프로젝트에 적용한 내용을 정리한 것이다.
이 방법이 절대적으로 옳은 방법이거나 정답은 아니라는 걸 명심해 줬으면 좋겠다.
또한 틀린 내용이 있을 수 있으니 언제든지 알려주면 좋겠다,,
전통적인 3-레이어 아키텍처에서의 TDD
전통적인 3-레이어 아키텍처(표현 계층, 비즈니스 로직 계층, 데이터 액세스 계층)에서 TDD를 적용할 때, 보통 다음과 같은 순서로 진행한다:
- 단위 테스트(Unit Testing): 가장 낮은 수준의 메서드(예: 데이터 액세스 메서드)에 대한 테스트를 먼저 작성하고, 이를 통과하도록 코드를 구현한다.
- 서비스 레이어 테스트(Service Layer Testing): 비즈니스 로직을 포함하는 서비스 레이어에 대한 테스트를 작성하고 구현한다. 이 과정에서 Mock 객체를 사용하여 데이터 액세스 레이어를 분리할 수 있다.
- 통합 테스트(Integration Testing): 여러 계층이 함께 작동하는지 검증하기 위해 통합 테스트를 수행한다.
- UI/표현 계층 테스트(UI/Presentation Layer Testing): 사용자 인터페이스와 관련된 테스트를 진행한다.
헥사고날 아키텍처에서의 TDD
헥사고날 아키텍처는 애플리케이션의 핵심을 중심으로 외부 요소와의 연결을 유연하게 만든다. 이 접근법에서 TDD는 다음과 같이 진행될 수 있다:
- 도메인 모델 테스트(Domain Model Testing): 헥사고날 아키텍처의 중심에는 도메인 모델이 있다. 먼저 도메인 모델과 관련된 비즈니스 로직에 대한 테스트를 작성하고 구현한다.
- 포트 및 어댑터 테스트(Ports and Adapters Testing): 도메인 모델을 외부 시스템과 연결하는 포트(Ports)와 어댑터(Adapters)에 대한 테스트를 작성한다. 여기서는 외부 API 호출, 데이터베이스 연결 등이 포함될 수 있다.
- 통합 테스트(Integration Testing): 모든 계층이 올바르게 연동되는지 확인하기 위한 통합 테스트를 수행한다.
- 애플리케이션 테스트(Application Testing): 전체 애플리케이션의 흐름과 사용자 인터페이스를 테스트한다.
헥사고날 아키텍처는 외부 요소와의 결합도를 낮춰 테스트 용이성을 높인다. 이는 TDD의 적용을 더욱 효율적으로 만든다. 각 테스트 단계에서 Mock 객체와 스텁(Stub)을 사용하여 외부 시스템과의 의존성을 제거하고, 도메인 로직의 순수성을 보장하는 것이 중요하다.
이러한 TDD 접근법은 코드의 품질을 높이고 유지 보수를 용이하게 하며, 헥사고날 아키텍처의 장점을 최대한 활용할 수 있도록 한다.
헥사고날 아키텍처에서 단위 테스트, 통합 테스트 적용
헥사고날 아키텍처에서 단위 테스트와 통합 테스트의 분리를 적용하는 전략은 매우 중요하다. 이 아키텍처의 목적 중 하나는 레이어 간의 결합도를 낮추어 각 컴포넌트를 독립적으로 테스트할 수 있게 하는 것이다. 이를 통해 테스트의 용이성을 높이고, 전체 시스템의 유연성과 유지보수성을 개선한다.
단위 테스트는 각 컴포넌트를 독립적으로 검증하는 데 중점을 둔다. 우리는 두 레이어에서 단위 테스트를 수행했다:
도메인 레이어(Domain Layer): 애플리케이션의 핵심 로직과 도메인 모델이 위치하는 곳이다. 도메인 레이어의 단위 테스트는 도메인 객체들이 올바르게 상호작용하고, 도메인 규칙을 정확히 구현하는지 확인하는 데 초점을 맞춘다.
서비스 레이어(Service Layer): 비즈니스 로직이 주로 구현되는 부분이다. 서비스 레이어의 단위 테스트는 비즈니스 규칙과 프로세스가 예상대로 작동하는지 확인한다. Mock 객체를 사용하여 외부 시스템과의 의존성을 분리함으로써, 순수한 비즈니스 로직만을 테스트할 수 있다.
통합 테스트는 애플리케이션의 서로 다른 부분들이 함께 잘 작동하는지 검증한다. 우리는 두 영역에서 통합 테스트를 수행했다:
인프라스트럭처 레이어(Infrastructure Layer): 외부 시스템과의 통신을 처리하는 부분이다. 예를 들어, 데이터베이스, 외부 API, 파일 시스템과의 연결 등이 여기에 해당한다. 이 레이어의 통합 테스트는 실제 외부 시스템과의 연동이 올바르게 이루어지는지 확인한다.
컨트롤러(Controller): 사용자 인터페이스와 애플리케이션 로직을 연결하는 부분이다. 컨트롤러의 통합 테스트는 HTTP 요청, 사용자 입력 처리 등의 기능이 정상적으로 작동하는지 검증한다.
각 레이어 별 테스트 전략 설정 이유
사실 나는 '헥사고날 아키텍처면 레이어 간 느슨한 결합이 목적이니까 모든 레이어를 단위 테스트로 진행해야 하는 거 아닌가?'라는 생각을 했었다. 그러나 헥사고날 아키텍처에서 테스트 전략을 결정할 때, '모든 레이어를 단위 테스트로만 진행해야 한다'는 생각은 일반적인 접근법이 아니라는 것을 알았다.
헥사고날 아키텍처에서의 테스트 전략은 레이어 간의 독립성을 유지하고 효과적인 테스트 가능성을 제공하는 데 중점을 둔다. 이 아키텍처의 핵심 목표는 레이어 간 낮은 결합도와 높은 응집도를 유지하는 것이다.
이를 위해 다음과 같은 점들을 고려해야 한다:
단위 테스트
비즈니스 로직과 도메인 로직의 정확성과 견고함을 검증하는 데 중요하며, 서비스 레이어와 도메인 레이어에서 특히 중요하다.
통합 테스트
어댑터, 인프라스트럭처 레이어, 그리고 컨트롤러는 외부 시스템과의 연동을 다룬다. 이러한 레이어와의 상호작용을 검증하기 위해서는 통합 테스트가 필요하다. 이 테스트는 실제 환경에서의 동작을 시뮬레이션하여 시스템의 전체적인 통합성과 안정성을 평가한다.
헥사고날 아키텍처는 테스트 전략에 대해 엄격한 규칙을 설정하지 않는다. 대신, 각 프로젝트의 특성과 요구사항에 따라 적절한 테스트 전략을 선택할 수 있는 유연성을 제공한다. 따라서, 모든 레이어를 오로지 단위 테스트로만 진행하는 것은 반드시 올바른 접근법이라고 볼 수 없다.
적절한 테스트 전략은 프로젝트의 특성과 요구사항을 고려하여 단위 테스트와 통합 테스트를 적절히 혼합하는 것이다. 이러한 접근은 테스트의 효율성을 극대화하고, 전체 시스템의 안정성과 유지보수성을 향상하는 데 기여한다.
이제 우리 프로젝트에서 각 레이어별로 테스트 전략을 정한 이유에 대해서 상세히 설명해 보겠다.
도메인 레이어는 애플리케이션의 핵심이며, 비즈니스 규칙과 로직을 담당하는 중요한 부분이다. 이 레이어의 주요 목적은 비즈니스 요구사항을 정확하게 구현하는 것이며, 이를 위한 단위 테스트 전략은 다음과 같다:
격리된 테스트 환경
단위 테스트는 도메인 로직을 외부 요인으로부터 독립적으로 검증을 가능하게 한다. 이는 비즈니스 규칙이 외부 시스템의 영향 없이 정확하게 작동하는지 확인하는 데 중요하다.
빠른 피드백 제공
단위 테스트는 개발 과정 중 신속한 피드백을 제공한다. 이는 비즈니스 로직의 오류를 조기에 발견하고 수정하는 데 도움이 된다.
서비스 레이어는 도메인 레이어와 인터페이스 레이어 사이의 중간 계층으로서, 복잡한 비즈니스 프로세스를 관리한다. 이 레이어에서의 단위 테스트 전략은 다음과 같다:
비즈니스 프로세스 검증
서비스 레이어의 단위 테스트는 복잡한 비즈니스 프로세스가 정상적으로 작동하는지 확인한다. 이는 프로젝트의 비즈니스 로직에 대한 신뢰성을 보장하는 데 핵심적이다.
의존성 관리
Mock 객체를 사용하여 데이터베이스와 같은 외부 의존성을 격리함으로써, 서비스 레이어의 로직만을 순수하게 테스트할 수 있다.
우리 프로젝트에서는 서비스 레이어에 단위 테스트를 적용한 명확한 이유가 있다. 우리는 팀 내부적으로 코드를 작성할 때 서비스 레이어에서 DTO나 엔티티 대신 도메인 객체만을 사용하여 비즈니스 로직을 구현하기로 협의했다. 이 결정은 다음과 같은 이유로 중요하다:
명확하고 중심적인 비즈니스 로직
서비스 레이어에서 DTO나 엔티티 대신 도메인 객체만을 사용함으로써, 비즈니스 로직이 더 명확하고 중심적인 역할을 하게 되었다. 도메인 객체는 비즈니스 규칙과 행위를 직접적으로 표현하는 데 최적화되어 있으며, 이를 통해 로직의 응집도가 높아졌다.
테스트의 명확성 및 단순성
도메인 객체만을 사용함으로써, 서비스 레이어의 단위 테스트가 더욱 명확하고 단순해졌다. 테스트 시에는 외부 의존성(예: 데이터베이스 접근)에 집중하기보다는 순수한 비즈니스 로직의 정확성을 검증하는 데 초점을 맞출 수 있었다.
결합도의 감소
DTO나 엔티티 대신 도메인 객체를 사용하면, 서비스 레이어와 데이터 액세스 레이어 간의 결합도가 감소한다. 이는 아키텍처의 유연성을 증가시키고, 변경에 대한 영향을 최소화했다.
Mock 객체 사용의 용이성
Mock 객체를 사용하여 단위 테스트를 수행할 때, 도메인 객체를 중심으로 테스트를 구성하면, 외부 의존성을 보다 효과적으로 격리할 수 있다. 이는 테스트 환경을 단순화하고, 테스트의 신뢰성을 높일 수 있었다.
이러한 이유로 우리 프로젝트에서는 도메인과 서비스 레이어에서 단위 테스트를 진행하기로 결정했다. 이 전략은 프로젝트의 유연성을 증가시키고, 유지보수를 용이하게 하며, 전체 시스템의 안정성과 신뢰성을 향상하는 데 기여한다고 생각한다.
데이터베이스와 연동하는 어댑터는 애플리케이션과 데이터베이스 간의 중요한 연결 고리다. 이 어댑터의 주요 역할은 데이터의 저장, 조회, 수정 및 삭제와 같은 중요한 데이터베이스 작업을 수행하는 것이다.
실제 환경에서의 작동 검증
어댑터의 통합 테스트는 실제 데이터베이스와의 연동을 포함하여 어댑터가 실제 환경에서 올바르게 작동하는지 검증한다. 이는 데이터베이스 연결, 쿼리 실행, 데이터 조작 등을 포함한다.
데이터 일관성 및 무결성 확인
데이터베이스와의 통합 테스트를 통해, 데이터의 일관성과 무결성이 유지되는지 확인한다. 이는 애플리케이션의 데이터 관리 측면에서 매우 중요하다.
컨트롤러는 사용자 인터페이스와 애플리케이션의 비즈니스 로직을 연결하는 중요한 역할을 한다. 컨트롤러의 통합 테스트는 애플리케이션의 외부 인터페이스와의 상호작용을 검증하는 데 중점을 둔다.
사용자 인터페이스와의 상호작용 검증
통합 테스트는 사용자 요청의 처리와 응답의 정확성을 검증한다. 이는 직접적인 사용자 경험과 직결되는 부분이므로 매우 중요하다.
시스템 전체 흐름 확인
컨트롤러의 통합 테스트를 통해 애플리케이션의 전체적인 흐름과 상호작용이 의도대로 작동하는지 확인할 수 있다.
헥사고날 아키텍처의 주요 목표 중 하나는 애플리케이션의 코어 로직을 외부 요소로부터 격리하고, 이를 통해 테스트 용이성과 유지보수성을 향상시키는 것이다. 이러한 관점에서 어댑터와 컨트롤러 레이어의 통합 테스트는 몇 가지 중요한 이점을 제공한다.
실제 환경과의 상호작용 검증
어댑터와 컨트롤러는 외부 시스템과 직접 상호작용하는 애플리케이션의 구성 요소다. 이들의 통합 테스트는 실제 데이터베이스, API, 사용자 인터페이스 등과의 상호작용을 검증하여, 애플리케이션이 실제 운영 환경에서 어떻게 작동하는지 확인한다.
전체 시스템의 통합성 확인
통합 테스트는 애플리케이션의 다양한 부분들이 서로 올바르게 통합되어 작동하는지 확인한다. 이는 애플리케이션의 전체적인 안정성과 신뢰성을 보장하는 데 중요하다.
사용자 경험과의 연결
특히 컨트롤러의 경우, 사용자 경험과 직접적으로 연결되는 부분이기 때문에, 사용자 요청의 처리와 응답의 정확성을 검증하는 통합 테스트는 사용자 만족도와 직결된다.
실제 사용 사례 시뮬레이션
통합 테스트는 실제 사용자의 시나리오와 가장 가깝게 애플리케이션을 테스트할 수 있다. 이는 애플리케이션의 기능적 요구사항과 비즈니스 요구사항이 잘 충족되고 있는지 확인하는 데 중요하다.
따라서, 어댑터와 컨트롤러에서 통합 테스트를 진행하는 것은 헥사고날 아키텍처를 적용한 프로젝트의 전체적인 품질과 사용자 경험을 향상시키는 데 중요한 역할을 한다. 이는 전체 시스템의 안정성과 유지보수성을 높이고, 비즈니스 요구사항에 대한 신뢰성을 확보하는 방식으로 헥사고날 아키텍처의 장점을 극대화할 수 있다.
구체적인 테스트 및 개발 순서
헥사고날 아키텍처에서 TDD를 적용할 때의 구체적인 테스트 및 개발 순서를 설명해 보겠다. TDD 접근법에서는 먼저 실패하는 테스트를 작성하고, 그 테스트를 통과하도록 실제 코드를 작성하는 순서로 진행된다. 헥사고날 아키텍처를 적용할 때, 이 프로세스는 다음과 같이 구성될 수 있다:
1. 도메인 로직 테스트 작성
먼저, 도메인 모델과 관련된 비즈니스 로직에 대한 테스트를 작성한다. 예를 들어, 주문 처리, 할인 계산 등의 도메인 규칙에 대한 테스트 케이스를 작성한다.
2. 도메인 로직 구현
작성된 테스트를 통과할 수 있도록 도메인 로직을 구현한다. 이 단계에서는 도메인 모델의 메서드나 클래스를 실제로 구현하여 테스트가 성공하도록 만든다.
3. 서비스 레이어 테스트 작성
도메인 로직을 기반으로 서비스 레이어에 대한 테스트를 작성한다. 서비스 레이어는 도메인 로직을 조합하고, 추가적인 비즈니스 프로세스를 수행한다.
4. 서비스 레이어 구현
서비스 레이어의 테스트가 성공하도록 실제 로직을 구현한다. 이 단계에서는 서비스 메서드를 구현하고, 필요한 비즈니스 로직을 추가한다.
5. 포트 및 어댑터 테스트 작성
외부 시스템과의 통신을 담당하는 포트와 어댑터에 대한 테스트를 작성한다. 예를 들어, 데이터베이스 저장, 외부 API 호출 등에 대한 테스트를 작성한다.
6. 포트 및 어댑터 구현
포트 및 어댑터의 테스트를 통과할 수 있도록 실제 로직을 구현한다. 이 단계에서는 외부 시스템과의 통신 로직, 데이터베이스 연동 로직 등을 구현한다.
7. 통합 테스트 및 최종 검증
모든 레이어가 올바르게 통합되었는지 검증하기 위해 통합 테스트를 수행한다. 이 단계에서는 전체 애플리케이션의 흐름과 사용자 인터페이스를 포함한 최종 사용자 경험까지 테스트한다.
이러한 순서로 진행하면, 헥사고날 아키텍처 내에서 각 컴포넌트의 독립성을 유지하면서도, 전체 시스템이 원활하게 통합되고 작동하는지 확인할 수 있다.
마무리
이렇게 실제로 우리 프로젝트에서 헥사고날 아키텍처에 TDD를 적용할 때 고려했던 내용을 정리해 봤다.
이 다음장에서 실제로 각 레이어별로 어떻게 테스트가 진행되고 실제 객체를 사용할지, Mock 객체를 사용할지 내가 처한 상황에 따라 정리해 보도록 하겠다.
그리고 우리 프로젝트에 나름대로 타당한 이유를 찾으며 이런 프로세스를 확립했는데 아직 배우는 단계다 보니 틀린 내용이 있을 수 있다. 언제든지 틀린 내용을 알려주면 좋겠다,,,
🔻 헥사고날 아키텍처에서 각 레이어에 주입하는 의존성이 궁금하다면? 🔻
헥사고날 아키텍처 실전 적용 (1) - 클래스 의존성 주입 및 도메인, 엔티티의 객체 변환 과정
📣 이 글은 내가 소속된 Team Chillwave에서 진행한 사이드 프로젝트에 적용한 내용을 다시 공부하고 정리한 것이다. 다른 팀원인 "개발자의 서랍" 님의 블로그도 방문하면 도움이 될 것같다 :)
'Spring > Spring 테스트코드' 카테고리의 다른 글
스프링 테스트 코드에서 실제 호출 @SpyBean으로 확인하기 (3) | 2023.12.31 |
---|---|
스프링 테스트 코드: @MockBean과 @Mock의 차이 (+ @InjectMocks) (1) | 2023.12.31 |
테스트 코드의 가독성 향상 - BDD 방법론과 @DisplayName 어노테이션 활용 (2) | 2023.12.26 |
테스트 전략에서 단위 테스트와 통합 테스트의 차이점과 의존성 관리 (1) | 2023.12.25 |
회원가입 컨트롤러 테스트(Controller Test) - RequestDto @NotBlank 필드 검증 (1) | 2023.12.22 |