pile·
백엔드·마켓컬리마켓컬리 Hello World·

TestContainers로 유저 시나리오와 비슷한 통합테스트 만들기

컬리 딜리버리프로덕트팀이 서버가 분리된 환경에서 서버 간 제약조건 불일치로 생기는 버그를 잡기 위해, TestContainers로 실제 QA와 비슷한 통합테스트를 만든 경험을 다룬다. Kafka·PostgreSQL·Spring 애플리케이션 컨테이너를 하나의 시나리오로 묶어 유저 스토리를 관통 검증한다.

핵심 포인트
  • 서버별로 관심사만 나눠 mock으로 테스트하면, 조회 서버에만 추가된 제약(배송 가능 지역)이 수집 서버와 어긋나는 버그를 QA 단계에서야 발견한다.
  • TestContainers는 테스트 코드에서 도커 컨테이너를 띄워, 도커만 있으면 어디서나 동일한 통합 환경을 재현한다.
  • 시나리오에 이벤트 브로커(Kafka)·수집 서버·주문 DB(Postgres)·조회 서버 4개 컨테이너가 필요하다.
  • 컨테이너를 공통 Network와 alias로 묶어 내부 통신하고, application은 환경변수로 연결 대상을 런타임 주입한다.
  • 배포 이미지 생성 workflow 완료를 트리거로 GitHub Actions에서 통합테스트를 자동 실행한다.
상세 정리
  • 문제 상황: 고객이 주문한 주문건을 배송관리서비스에서 확인한다는 스토리에서, 조회 쪽에 배송 가능 지역 제약을 추가하면 서버별 mock 테스트는 모두 통과하지만 실제로는 일부 주문이 안 보이는 버그가 QA에서야 드러난다.
  • 해법 방향: 서버 구분 없이 하나로 합친 통합테스트라면 조회 제약 변경 시 테스트가 깨져 조기 발견이 가능해, TestContainers로 실제와 유사한 자동화 테스트를 만들기로 했다.
  • 환경 구성: testcontainers-postgresql·kafka·junit-jupiter 의존성을 추가하고 멀티프로젝트에 모듈로 붙였다. 테스트 코드는 Kotlin으로 작성했다.
  • 컨테이너 정의: KafkaContainer(cp-kafka), PostgisContainerProvider(DB명·유저·포트), Spring 앱은 GenericContainer로 Container Registry에서 이미지를 pull했고, withImagePullPolicy(alwaysPull)로 코드 수정 시 최신 이미지를 쓰게 했다.
  • 네트워크: 전역 Network를 만들고 각 컨테이너에 withNetwork와 withNetworkAliases로 alias를 부여해 컨테이너끼리 alias로 통신하게 했다. 앱은 application-local.yml에서 POSTGRES_HOST·KAFKA_HOST를 환경변수로 받아 런타임 연결한다.
  • 삽질 지점: 내부 연결 실패 시 Cannot assign requested address 로그가 남고, 연결돼도 인증 401이 떠서 local 프로필에서 magic-token으로 인증을 우회했다. 테스트에서 앱 호출은 getFirstMappedPort로 포워딩된 포트를 받아 접근한다.
  • 시나리오 검증: given으로 KafkaProducer가 주문 메시지를 발행하고, when으로 조회 API를 호출하며, then으로 orderNo 일치를 assert해 Kafka 발행부터 조회까지 관통 확인한다.
  • CI 연동: GitHub Actions에서 workflow_run(배포 이미지 생성 workflow completed)을 트리거로 통합테스트를 실행하고 결과를 리포팅받는다.
  • 의의: 기본 시나리오를 자동화하면 QA는 비정상 케이스에 집중할 수 있고, DB 등 인프라 버전 변경 시 영향도 파악도 수월해진다.
왜 읽나Spring/Kotlin 백엔드에서 Kafka·DB를 포함한 실전 통합테스트를 구축하려는 개발자에게 TestContainers 네트워크 구성과 인증·포트 함정을 보여주는 실전 가이드.
마켓컬리
마켓컬리 Hello World 블로그
원문은 여기서 이어서 읽을 수 있어요
원문 읽기
읽음 (0)

이 글과 비슷한

  1. 백엔드·github-engGitHub Engineering·

    조기 종료를 없애야 벡터화된다 — 메모리 속도 소스 코드 케이스 폴딩

    GitHub의 코드 검색 엔진 Blackbird는 480TB 이상의 소스 코드를 인덱싱하기 전 모든 바이트에 case folding을 적용한다. 이 글은 Rust로 구현한 case folding을 메모리 대역폭 한계(45+ GiB/s)까지 끌어올린 두 가지 반직관적 최적화를 상세히 다룬다. 핵심은 루프 조기 종료(break) 제거로 LLVM 벡터화를 유도하고, UTF-8을 디코딩하지 않고 바이트 공간 산술만으로 fold를 수행하는 것이다.

    #rust#unicode#simd+2
  2. 백엔드·여기어때 (GC컴퍼니)여기어때 (GC컴퍼니)·

    트랜잭션 스크립트에서 숙소 메타 + 가격 계산 모듈로 — 전시 아키텍처 개선기 (2/3)

    여기어때 전시개발팀이 숙소 상세(PDP) API를 해부한 결과, 코드상으로는 DB 호출 3번처럼 보이던 요청이 실제로는 MongoDB $lookup 체인으로 컬렉션을 19회 접근하는 구조였다. 이 트랜잭션 스크립트 방식의 핵심 문제는 "aggregation이 I/O를 가린다"는 점으로, 독립적인 쿼리 10개가 단일 파이프라인에 직렬화되어 병렬화 기회를 잃고, 가격 때문에 거의 안 바뀌는 이미지까지 매 요청마다 읽어야 하는 읽기 증폭이 발생했다. V3에서는 "조회 시점 조립"을 "쓰기 시점 사전 조립"으로 전환하고, 화면별로 복제되던 가격 계산 로직을 goodsprice 단일 모듈로 수렴했다. 4개 API(PLP/PDP/RDP/ILP)의 반복 마이그레이션은 Claude Code skill로 절차를 고정하고 쉐도잉 + 동일성 검증으로 안전망을 마련하는 방식으로 진행됐다.

    #architecture#migration#caching+2