← Posts

Testing · Integration Test · E2E

통합과 E2E - 진짜를 붙여 본다

단위가 다 초록인데 합치면 안 돈다. 이음매는 진짜를 붙여야 보인다. 통합 테스트와 계약 테스트, E2E가 왜 느리고 불안정한지, 플래키를 어떻게 다루는지까지.

목차
  1. 단위가 다 초록인데 안 돈다
  2. 통합 테스트: 진짜를 붙인다
  3. 계약 테스트: 응답 모양을 고정한다
  4. E2E: 사용자처럼 끝까지
  5. 위로 갈수록 느리고 불안정하다
  6. 플래키: 됐다 안 됐다 하는 테스트
  7. 실무에서: 적게, 핵심 흐름만
  8. 정리

단위 테스트는 조각 하나를 본다. 그런데 조각들이 맞물리는 자리는 하나씩 봐선 안 보인다. 진짜를 붙여야 드러나는 층이 여기다.

단위가 다 초록인데 안 돈다

각 조각의 단위 테스트가 전부 통과한다. 그런데 합쳐서 돌리면 깨진다. 흔한 일이다.

plaintext
주문 서비스 단위 테스트   ✓ (재고 저장소는 가짜로)
재고 저장소 단위 테스트   ✓ (쿼리는 안 돌려봄)
────────────────────────
합쳐서 실행              ✗ 컬럼 이름이 안 맞아 쿼리 실패

단위 테스트가 가짜로 갈아끼운 바로 그 자리 - 이음매가 문제다. 가짜는 “이렇게 동작할 것”이라 믿은 모습이고, 진짜는 다를 수 있다. 그 차이는 붙여 봐야만 드러난다.

통합 테스트: 진짜를 붙인다

통합 테스트는 단위에서 가짜로 뭉갰던 걸 진짜로 바꿔 여러 조각을 함께 돌린다. 진짜 DB를 띄우고, 서비스가 그 DB에 실제로 쓰고 읽는지 본다.

  • 쿼리가 정말 도는가 - 컬럼·타입·제약이 코드와 맞나.
  • 트랜잭션이 경계대로 커밋·롤백되나(서비스 계층이 정한 그 경계).
  • 여러 컴포넌트가 기대한 순서로 맞물리나.

단위보다 느리다(진짜를 띄우니까). 대신 단위가 원리상 절대 못 잡는 이음매 버그를 잡는다. 그래서 이음매마다 몇 개씩 둔다.

계약 테스트: 응답 모양을 고정한다

서비스가 여럿이면 특별한 이음매가 생긴다 - 서로 주고받는 데이터의 모양이다. 한쪽이 응답 필드 이름을 바꾸면, 그걸 읽던 상대가 조용히 깨진다.

그래서 응답 모양을 테스트로 못 박는다. API 계약 글이 보여주듯, 응답 JSON의 필드 이름·타입을 테스트가 확인하면 누가 필드를 지우는 순간 빌드가 실패한다.

plaintext
응답에 userName 필드가 있어야 한다  ← 계약을 테스트가 붙잡는다
필드를 지우면 → 빌드 실패 → 배포 전에 걸린다

이게 계약 테스트의 기본 발상이다 - “우리가 약속한 모양이 안 깨졌나”를 자동으로 지킨다. 더 나아간 방식(Pact 같은 consumer-driven contract)은 주고받는 양쪽이 같은 계약을 각자 검증해서, 서로를 함께 띄우지 않고도 어긋남을 잡는다. 초점은 같다 - 약속을 코드가 아니라 테스트로 붙잡는 것.

E2E: 사용자처럼 끝까지

E2E(end-to-end)는 맨 위층이다. 브라우저를 실제로 띄워, 사용자가 하듯 로그인 → 담기 → 결제를 처음부터 끝까지 밟는다.

가장 현실적이다 - 프런트·백엔드·DB·네트워크가 다 진짜니, “사용자가 실제로 쓸 수 있나”를 그대로 확인한다. 단위·통합이 다 통과해도 버튼이 안 눌리거나 화면이 안 뜨는 건 E2E만 잡는다.

위로 갈수록 느리고 불안정하다

값이 큰 만큼 대가도 크다. 피라미드에서 봤듯 위층은 느리고 불안정하다.

  • 느리다 - 전체가 다 떠 있어야 하고, 화면을 진짜로 그려 클릭한다. 하나에 수 초.
  • 불안정하다 - 네트워크 지연, 화면이 아직 안 그려짐, 외부 서비스 상태에 흔들린다.
  • 원인이 흐리다 - 깨져도 프런트인지 백엔드인지 DB인지 바로 안 보인다.

그래서 E2E는 핵심 흐름 몇 개만 둔다. 로직 하나하나를 E2E로 확인하려 들면 스위트가 느려 터지고 자주 깨져, 아무도 안 믿게 된다.

플래키: 됐다 안 됐다 하는 테스트

위층에서 특히 자주 만나는 골칫거리가 플래키 테스트(flaky test) - 코드를 안 바꿨는데 어떤 땐 통과, 어떤 땐 실패하는 테스트다.

원인은 대개 비결정적인 것에 기댔기 때문이다.

  • 타이밍 - 화면이 그려지기 전에 클릭. “0.5초 기다림” 같은 고정 대기.
  • 순서·공유 상태 - 앞 테스트가 남긴 데이터에 뒤 테스트가 영향받음.
  • 외부·동시성 - 외부 API 지연, 경쟁 상태.

플래키는 신뢰를 좀먹는다. 초록을 못 믿으면 빨간 줄도 “또 플래키겠지” 하고 무시하게 되고, 그러다 진짜 버그를 놓친다. 그래서 재시도로 덮지 말고 원인을 없앤다 - “기다림”을 고정 시간이 아니라 “이 요소가 뜰 때까지”로 바꾸고, 테스트마다 상태를 격리한다.

실무에서: 적게, 핵심 흐름만

  • E2E는 golden path 몇 개 - “가입하고 첫 주문까지” 같은 핵심 여정만. 세세한 규칙은 아래층으로 내린다.
  • 통합은 이음매마다 - DB·외부 연동처럼 가짜로는 못 믿는 자리에.
  • 격리와 정리 - 각 테스트가 자기 데이터를 만들고 지워, 순서에 안 기대게. 플래키의 절반은 여기서 예방된다.
  • 느린 층은 따로 돌린다 - 빠른 단위는 매번, 느린 E2E는 배포 전에.

정리

  • 단위가 다 초록이어도 이음매는 못 본다 - 진짜를 붙이는 통합 테스트가 그 자리를 잡는다.
  • 서비스 간에는 계약 테스트로 응답 모양을 못 박아, 필드를 바꾸면 배포 전에 걸리게 한다.
  • E2E는 가장 현실적이지만 느리고 불안정 - 핵심 흐름 몇 개만.
  • 플래키는 신뢰를 좀먹으니 재시도로 덮지 말고 원인(타이밍·순서·외부)을 없앤다.

다음 글은 순서를 뒤집어 - 테스트를 먼저 쓰는 TDD로 간다.

관련 글