개발이군고구마

Strangler Fig Pattern : 테스트 하지 않는 환경에서 TDD 흐름 만들기 본문

SERVER/Architecture

Strangler Fig Pattern : 테스트 하지 않는 환경에서 TDD 흐름 만들기

김구황 2026. 1. 2. 22:38
728x90

현재 우리 개발 환경은 테스트 코드를 작성하지 않는다. 

이유는 클래스들 간의 결합도를 고려하지 않는 상태에서 개발이 되어서

(예, 서비스단 메서드에 여러 비즈니스 책임, 인프라 레이어가 섞여있음) 의존성을 끊어내기가 쉽지가 않기 때문인데, 그것보다는 새로운 것을 시도하기를 주저하는 원인이 가장 큰 것 같다. 

 

그래서 테스트를 한다 함은 

서버를 구동하여 (시간 엄청 걸린다) → 화면에서 데이터를 보낸 후 (환경 세팅 클릭해서 해야함) 서버에서 에러가 뜨면 

그때서야 아, 에러가 있구나 깨닫게 되는 것이다. 

게다가 SQL의 경우에는 수정을 하고 나서 빌드를 다시 해도 반영이 되지 않아, 서버를 다시 껐다 키는 불필요한 작업을 해야했다. 

 

기존의 테스트 방식은

1) QA 기간에도 에러가 났을 때, 이게 클라이언트 화면에서 난 에러인지, 서버에서 나는 에러인지 확인을 하는데 시간이 많이 필요하며 (서버 테스트를 확실히 진행하면 클라이언트 페이지만 변경하면 된다) 

2) 화면 및 서버를 수정한 후에 영향도가 어느정도인지 확인하는 것도 실제 개발을 다 하고 화면을 클릭해야봐야만 알 수 있으며 (그마저도 안 잡힌다. 운영 배포 나가고 깨닫게 될 수도)

3) 무엇보다 테스트를 하는데 시간이 무척 오래 걸리는 것이 (특히 SQL의 경우엔 서비스 단을 다 검증한 후 까지 기다렸다가 테스트가 가능하다) 무척이나 비효율적인 구조였다. 

 

따라서 스트랭글러 패턴을 적용하여, 새로운 기능만 이번에 설계한 New Architecture로 만들기로 한다.

   이를 통해 현 사이트의 고질적인 테스트 방식을 점진적으로 변경하고,

   앞으로 개발자들이 최소한 서버단 만큼은 쉽게 테스트 할 수 있는 테스트 환경을 구축하는 것을 목표로 개발을 진행하고자 한다. 

 

 


1. 무엇이 테스트가 필요한가 확인 

핵심 원칙 : 외부 의존성(DB, UI)과 비즈니스 로직을 분리하여 단위 테스트를 확보한다.

 

1) 서비스단 데이터 조합과 분리 (단위)

DTO에서 받아온 값을 쪼개고 이어붙히는 복잡한 로직이 존재한다.  (, 로 붙혀서 온 값들을 배열로 나누고 복수개의 row 데이터를 배열로 집어넣는 등)

비즈니스 코드 검증에서 꼭 필요한 부분이다. 요청 검색 조건 분리와 대상정보 등이 대표적이다.  

 

2) 컨트럴로 단 컨버팅 점검 (단위)

convert 메서드는 단순 필드 매핑이지만, 넥사크로 특성상 넥사크로의 dataset에 매핑해주기 위한 WrapperResponse 클래스들이 존재한다. 이때 넥사크로로 가기 위한 데이터셋에 해당하는 클래스로 제대로 매핑되어 객체로 생성되었는지 확인하도록 한다. 

 

3) 쿼리 검증 (통합) 

브랜드 검색시에 1,2 뎁스별 계층형 쿼리가 존재하고, 

상세조회 등에서 조인이 많이 걸린 쿼리들은 통합테스트로 검증한다. 

 

 

 

 

 

2. 레이어 설계 방식 

테스트를 중점에 두면 레이어단 책임이 명확해진다 

 

1) 컨트롤러 단

역할 : Controller 는 "요청을 받아 적절한 서비스에 위임하고 응답을 반환"하는 역할만한다. 

 

테스트 방법:  컨트롤러 코드는 "Converter를 호출한다" 정도로 얇게 유지하고, 테스트는 Converter에 집중하도록 한다. 왜냐하면 컨트롤러 전체를 테스트(MockMvc 등 사용)하려면 스프링 컨텍스트를 띄우거나 설정을 많이 해야 해서 테스트가 무거워지기 때문이다.

 

규칙: 

컨버트는 데이터를 받아 '세팅' 하는 책임만 지도록 하자.

 

  언제 세팅할까?

현 코드 규칙상 넥사크로로 요청/응답하는 컨버팅 클래스들이 inner class로 정의되어 컨트롤러 단에 집합되어 있다. 그때 컨버팅 로직에는 데이터를 분리하거나 조합하지 않는다. "데이터를 가공하는 로직"은 컴포넌트에서 진행하도록 한다.

왜냐하면 이런 가공 작업이 흩어져 있으면 수정할 때 혼란스러워 시간이 배로 들 수 있고, 또한 convert 메서드가 Controller 내부에 강결합되어 있으면, 가공 로직만 따로 단위 테스트(Unit Test)하기 어려워진다. 

 

 

  그렇다면 어떤 컨버트를 단위테스트 한다는 것일까?

빨간 부분

 

그건 바로 넥사크로의 특성상 복수개의 데이터셋을 들고 가는 경우이다. 

wrapper dto -> 실제 데이터 담고 있는 response dto 로의 변환이 필요한데 그때 WrappingDto의 변환과정을 단위 테스트를 진행한다. 

- 페이징 (리스트를 wrapper dto에 넘겨서 컨버팅된 class 의 리스트와 페이지 정보로 변환한다) 

- 상세정보 (단일 객체를 wrapper dto에 넘겨 컨버팅된 class 1개 그리고 그룹사 리스틀 컨버팅한 class 리스트1개 2개가 반환한다) 

 

* 만약 어노테이션을 만드는 경우엔 어떻게 테스트를 하게 될까? ▶ 추후에 다른 블로그에서 깊이 분석하도록 하자. 

 

 

2) 서비스

역할 : 서비스 레이어는 **"오케스트레이션(Orchestration)"**을 담당하는 부분이다. ((권한/트랜잭션/도메인 규칙) + repository 호출)

테스트 방법: 통합테스트와 같은 큰 테스트가 필요한 부분이지만, 이번 프로젝트에선 따로 서비스 통합테스트는 진행하지 않기로 한다. 

규칙: 프리젠테이션과 인프라 어뎁터 사이를 이어주는 역할만 하기 때문에 데이터 가공은 하지 않는다.

 

참고 : 도메인 규칙의 예시 

  • 그룹핑
  • 필터링
  • 비즈니스 규칙 반영 (GROUP_CODE_ALL, GROUP_CODE_IND)
  • 여러 필드 간 관계 조합

 

3) 컴포넌트 ✨

역할 : 데이터를 가공하는 책임을 진다. 따라서, 단위 테스트가 필요한 단이다. 

 

테스트 방법 : 요청 응답값에서 필요한 데이터 조합/분리  검색시에 필요한 데이터 분리 메서드를 테스트 한다. 

 

규칙:

꼭 이곳에서만 데이터를 변환하자. (컨버트 메서드에서 하지 않는다) 

DB 접근 로직은 절대 만들지 않는다. 만약 데이터 어댑터에 접근하는 로직이 생기면 서비스단 단위테스트가 복잡해진다. (repository mock bean으로 주입하여야함. 이 마저도 현 사이트는 repository가 extends 받고 있는 서비스들이 상당해서 해당 빈을 사용하는 순간 의존성이 복잡하게 꼬여버린다) 

CQRS 원칙에 따라 컴포넌트가 2개로 분리되어있다. 

select 컴포넌트와 command 컴포넌트가 있으며, command에서부터 Transaction이 시작된다. 

 

 

4) 리파지토리  

인프라단과 연결되는 부분 외에 다른 일은 하지 않는다. 

해당 클래스를 테스트를 한다면 그건 DB와 커넥션하여 SQL의 짜여짐을 테스트 하는 정도 뿐이다. 

 

 

5) DTO

데이터를 가져와 매핑하는 역할을 담당한다.

 

규칙: 의도의 모호함을 피한다.

화면에서 넘어온 원본 데이터를 담는 DTO에 가공된 데이터 필드가 섞이면, 나중에 코드를 보는 사람은 "이 필드는 화면에서 준 건가? 서버에서 만든 건가?" 라고 헷갈리게 되므로 가공한 데이터는 새로운 DTO 에 담아서 직접 데이터에 접근한다. 

 

따라서 DTO는 (화면/DB) 원본 데이터를 그대로 매핑시킨 DTO와 가공 후 데이터를 담는 DTO를 두 종류로 구분하기로 한다. 

현재 데이터를 가공하는 부분은 2곳 (전시대상 분리/ 검색 조건 분리) 이다. 

 

참고) 아래와 같은 기능은 값을 가공하여 생성자를 통해 필드 값으로 넣어도 된다. 

  • 단순 필드 복사
  • 검증 없는 값 세팅 (◁ 해당 경우에 해당하였기에 따로 서비스 로직 만들지 않고 생성자로) 
  • 1:1 매핑
  • 조건 분기 없음

 

3. 단위테스트 작성과 구현 

의사코드로 어떤 코드를 작성할 것인가 프레임을 잡고 코드 작성과 테스트를 반복한다.

 

1) 컴포넌트 데이터 가공 메서드  

  • 데이터를 가공하는 메서드 로직을 의사코드로 작성한다. 
  • 필요한 공메서드를 우선 생성한 후 단위테스트 프레임을 작성한다. 
    • 대상 분리 
    • 대상 합침
    • 검색 조건 분리 
  • 이후 비즈니스 로직을 작성한 후 '성공' 테스트를 우선 작성하여 돌려보고 이후 경계값/문제의 소지가 있을 값들을 가지고 실패 케이스도 작성한다. 

 

2) 통합테스트 인프라 세팅 

  • 검색 SQL만 테스트하고자 했던 목표와는 다르게 간단한 SQL도 '성공여부' 만 확인하도록 방향을 선회하였다. 
  • 저장의 경우엔 return 값이 없기 때문에, [저장/응답]  SQL을 한 쌍으로 묶어서 한 메서드에서 테스트를 진행했다.  

 

 

3) 컨트롤러 단 응답 컨버팅 한 생성자 확인 

ㄴ 데이터로 변형했던 값들이 제대로 생성되었는지? 

- 검색 응답 (DB 에서 받아온 값이 페이징과 함께 제대로 컨버팅 되었는지) 

- 상세 응답 (그룹사 리스트와 상세정보가 함께 제대로 컨버팅 되었는지)

 

 


4. 결론 

1) 서비스 데이터 가공 

DTO 또는 비즈니스 로직에서 변수가 변경이 될때 테스트만 한번 돌려보면 영향도를 한번에 파악할 수 있었다. 

 

2) 통합테스트 인프라 세팅 

SQL이 잘 짜졌는지 먼저 검증할 수 있어서 기존에 화면에서 모든 값을 세팅하고 실제 SQL을 돌려보던 방식에 비해 시간이 획기적으로 단축되었다.