jongkwan.dev
개발 · Essay №026

테스트 자동화

단위/통합/E2E 테스트, 테스트 피라미드, 계약 테스트, 카오스 엔지니어링 실전 가이드

이종관2025년 2월 6일19 min read
Contents

테스트 자동화는 단위·통합·E2E를 어떻게 배치하고, 계약·돌연변이·카오스 테스트로 어디까지 검증할지 정하는 작업이다.

개요

테스트 자동화(Test Automation)는 단위·통합·E2E 테스트를 코드로 작성해 반복 실행하는 것이다. 수동 테스트에 의존하는 조직은 배포 속도가 느리고, 회귀 버그(Regression Bug)에 취약하며, 장기적으로 기술 부채(Technical Debt)가 누적된다.

AI 기반 테스트 생성이 확산되면서 자동화 방식이 바뀌고 있다.

테스트 수준 (Test Levels)

1. 단위 테스트 (Unit Test)

개별 함수, 메서드, 모듈을 격리된 환경에서 검증하는 가장 작은 단위의 테스트이다.

특징:

  • 가장 빠른 피드백 루프 (밀리초 단위 실행)
  • 외부 의존성은 모킹(Mocking)이나 스터빙(Stubbing)으로 대체
  • 높은 커버리지 달성이 용이
  • 리팩터링 시 안전망 역할

예시 (Jest):

javascript
describe('calculateDiscount', () => {
  it('10% 할인을 올바르게 계산한다', () => {
    const result = calculateDiscount(10000, 0.1);
    expect(result).toBe(9000);
  });
 
  it('할인율이 0이면 원래 가격을 반환한다', () => {
    const result = calculateDiscount(10000, 0);
    expect(result).toBe(10000);
  });
 
  it('음수 할인율에 대해 에러를 던진다', () => {
    expect(() => calculateDiscount(10000, -0.1)).toThrow();
  });
});

TDD (Test-Driven Development) 방법론:

  1. RED: 실패하는 테스트를 먼저 작성
  2. GREEN: 테스트를 통과시키는 최소한의 코드 작성
  3. REFACTOR: 코드를 개선하면서 테스트가 계속 통과하는지 확인

2. 통합 테스트 (Integration Test)

여러 모듈이 함께 동작할 때의 상호작용을 검증한다. 데이터베이스, 외부 API, 메시지 큐 등 실제 인프라와의 연동을 테스트한다.

검증 범위:

  • 데이터베이스 CRUD 연산의 정합성
  • 외부 API 호출 및 응답 처리
  • 메시지 큐(Kafka, RabbitMQ) 연동
  • 캐시(Redis) 동작 확인
  • 트랜잭션 경계(Transaction Boundary) 검증

Testcontainers 활용:

Testcontainers는 Docker 컨테이너를 활용하여 테스트 시 실제 데이터베이스, 메시지 브로커 등을 일시적으로 구동하는 라이브러리이다. 목(Mock) 대신 실제 인프라를 사용하므로 테스트 신뢰도가 높아진다.

java
@Testcontainers
class UserRepositoryTest {
    @Container
    static PostgreSQLContainer<?> postgres =
        new PostgreSQLContainer<>("postgres:16-alpine");
 
    @Test
    void 사용자_저장_및_조회가_올바르게_동작한다() {
        UserRepository repo = new UserRepository(postgres.getJdbcUrl());
        User user = new User("test@example.com", "홍길동");
 
        repo.save(user);
        User found = repo.findByEmail("test@example.com");
 
        assertThat(found.getName()).isEqualTo("홍길동");
    }
}

3. E2E 테스트 (End-to-End Test)

사용자의 전체 시나리오를 시뮬레이션하여 시스템의 끝단(End-to-End) 동작을 검증한다.

특징:

  • 가장 느리고 비용이 높지만, 실제 사용자 경험에 가장 가까운 테스트
  • 브라우저 자동화(Playwright, Cypress) 또는 API 수준에서 수행
  • 핵심 비즈니스 플로우에 집중하여 최소한으로 유지

Playwright 예시:

typescript
test('사용자가 상품을 검색하고 장바구니에 추가할 수 있다', async ({ page }) => {
  await page.goto('https://shop.example.com');
  await page.fill('[data-testid="search-input"]', '노트북');
  await page.click('[data-testid="search-button"]');
 
  await expect(page.locator('.product-card')).toHaveCount.greaterThan(0);
 
  await page.click('.product-card:first-child [data-testid="add-to-cart"]');
  await expect(page.locator('[data-testid="cart-count"]')).toHaveText('1');
});

테스트 전략 모델

테스트 피라미드 (Test Pyramid)

Martin Fowler가 제안한 전통적 모델로, 아래에서 위로 갈수록 테스트 수가 줄어든다.

text
        /  E2E  \          ← 소수 (느림, 비쌈)
       /Integration\       ← 적당량
      / Unit Tests  \      ← 다수 (빠름, 저렴)
     ‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾

테스팅 트로피 (Testing Trophy)

Kent C. Dodds가 제안한 현대적 모델로, 통합 테스트를 가장 많이 작성할 것을 권장한다.

text
        /   E2E   \
       / Integration\      ← 가장 많이 (비용 대비 효과 최적)
      /   Unit Tests  \
     /  Static Analysis \  ← TypeScript, ESLint 등
     ‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾
모델핵심 주장적합한 환경
테스트 피라미드Unit 테스트 최다순수 로직이 많은 라이브러리
테스팅 트로피Integration 테스트 최다웹 애플리케이션, API 서버

고급 테스트 기법

계약 테스트 (Contract Testing)

마이크로서비스 간의 API 계약(Contract)을 검증하는 테스트이다. 서비스 제공자(Provider)와 소비자(Consumer) 양측이 합의한 인터페이스를 위반하지 않는지 확인한다.

Pact 프레임워크:

  • Consumer가 기대하는 요청/응답 쌍을 "Pact 파일"로 정의
  • Provider가 Pact 파일을 기반으로 자동 검증
  • Pact Broker를 통해 계약을 중앙에서 관리

돌연변이 테스트 (Mutation Testing)

소스 코드에 **의도적인 결함(Mutant)**을 주입하고, 기존 테스트가 이를 탐지하는지 확인하여 테스트 스위트의 품질을 측정한다.

돌연변이 유형:

  • 조건 변이: >>=, ==!=
  • 산술 변이: +-, */
  • 반환값 변이: return truereturn false
  • 제거 변이: 특정 문장 삭제

도구: Stryker (JavaScript/TypeScript), PITest (Java), mutmut (Python)

돌연변이 점수(Mutation Score):

text
돌연변이 점수 = (죽은 돌연변이 수 / 전체 돌연변이 수) * 100%

80% 이상이면 테스트 스위트가 양호하다고 판단한다.

속성 기반 테스트 (Property-Based Testing)

특정 입력값 대신 **속성(Property)**을 정의하고, 프레임워크가 수천 개의 무작위 입력을 생성하여 속성이 항상 성립하는지 검증한다.

typescript
// fast-check 라이브러리 예시
import fc from 'fast-check';
 
test('정렬 함수는 항상 정렬된 배열을 반환한다', () => {
  fc.assert(
    fc.property(fc.array(fc.integer()), (arr) => {
      const sorted = mySort(arr);
      for (let i = 1; i < sorted.length; i++) {
        expect(sorted[i]).toBeGreaterThanOrEqual(sorted[i - 1]);
      }
    })
  );
});

장점: 개발자가 미처 생각하지 못한 엣지 케이스를 자동으로 발견

스냅샷 테스트 (Snapshot Testing)

컴포넌트의 렌더링 결과나 API 응답을 스냅샷 파일로 저장하고, 이후 변경 사항이 발생하면 차이를 보고한다.

적합한 대상:

  • React 컴포넌트의 렌더링 결과
  • API 응답 JSON 구조
  • 설정 파일 생성 결과

주의점: 스냅샷이 너무 크거나 자주 변경되면 유지보수 비용이 증가한다. 핵심 구조만 캡처하는 것이 권장된다.

카오스 엔지니어링 (Chaos Engineering)

개념

프로덕션 환경 또는 유사 환경에서 의도적으로 장애를 주입하여 시스템의 복원력(Resilience)을 검증하는 방법론이다. Netflix의 Chaos Monkey에서 시작되어, 현재는 아래 표의 도구·프레임워크로 확장됐다.

카오스 엔지니어링의 원칙

  1. 정상 상태 가설 수립: 시스템의 정상 동작을 정의
  2. 실험 변수 도입: 서버 장애, 네트워크 지연, 디스크 고갈 등
  3. 통제 그룹과 실험 그룹 비교: A/B 형태로 영향도 측정
  4. 폭발 반경(Blast Radius) 최소화: 점진적 확대

주요 도구

도구특징환경
Chaos MonkeyNetflix OSS, 랜덤 인스턴스 종료AWS
LitmusKubernetes 네이티브 카오스Kubernetes
Chaos Toolkit선언적 카오스 실험 정의범용
Gremlin엔터프라이즈급 SaaS범용
Steadybit팀 협업 기반 신뢰성 테스트Kubernetes/클라우드

AI 기반 카오스 테스트

Uber: DragonCrawl + uHavoc

Uber는 LLM 기반 모바일 테스트 플랫폼(DragonCrawl)과 서비스 레벨 장애 주입 시스템(uHavoc)을 통합하여, 2024년 Q1 이후 Rider·Driver·Eats 앱의 47개 핵심 플로우에서 180,000건 이상의 자동화된 카오스 테스트를 실행했다. 이는 약 39,000시간의 수동 테스트에 해당하는 분량이다(출처: arXiv:2602.06223).

uHavoc은 원격 프로시저 호출(Remote Procedure Call, RPC) 계층에서 세 가지 장애를 주입한다.

  • Abort: 4xx/5xx 에러 강제 반환
  • Timeout: 응답 지연 후 실패
  • Latency: 응답 시간 저하

장애 주입 대상은 비핵심 서비스 티어(서비스 중요도가 낮은 T2~T5 등급)로 제한하고, 테스트 테넌시 격리로 프로덕션 트래픽은 건드리지 않는다. DragonCrawl 테스트 실행 시 장애 설정을 헤더에 실어 보내면 uHavoc이 이를 처리하므로, 플로우 정의 하나로 여러 실패 시나리오를 돌릴 수 있다. 도시·플로우·장애 유형의 조합마다 테스트 케이스를 따로 작성하지 않아도 된다.

DragonCrawl이 장애 상황을 견디는 이유는 화면을 셀렉터가 아니라 의미로 해석하기 때문이다. "이 화면에서 트립을 예약할 수 있는가?"라는 질문은 요금이 구체적 금액 대신 "계산 중..."으로 표시돼도 답할 수 있다. 장애 주입으로 UI 요소가 비거나 지연돼도 테스트가 깨지지 않고, T2 티어 장애 주입 상태에서도 99% 이상의 통과율을 유지했다.

발견한 복원력 리스크는 23건이며, 그중 12건은 트립 요청이나 음식 주문 자체를 막는 심각한 결함, 2건은 모바일 테스트로만 잡히는 앱 크래시였다. 발견된 문제의 약 70%는 비핵심 서비스 장애가 핵심 플로우로 전파된 아키텍처 의존성 위반이었다. 근본 원인 자동 귀속은 precision@5 기준 88%를 기록해, 시스템을 깊이 모르는 엔지니어의 디버깅 시간을 약 3시간에서 5분 미만으로 줄였다.

Red Hat · IBM Research: Krkn chaos-recommender

Red Hat과 IBM Research 팀은 Krkn(카오스 엔지니어링 프레임워크)에 chaos-recommender를 더해, Prometheus 메트릭으로 각 Pod의 리소스 프로필을 분석하고 가장 높은 중단 확률을 가진 시나리오를 자동 추천한다.

동작은 머신러닝이 아니라 통계 휴리스틱이다.

  1. 부하 상태에서 각 Pod의 CPU·메모리·네트워크 사용량 텔레메트리를 Prometheus에서 수집한다.
  2. Z-score(평균에서 표준편차의 몇 배만큼 떨어졌는지)로 이상치를 탐지하고, 설정한 임계값을 넘는 Pod를 network-intensive·CPU-intensive·memory-intensive로 분류한다.
  3. 분류된 민감도에 맞는 카오스 시나리오를 매핑한다. CPU에 민감한 서비스에는 CPU 부하 시나리오를, 메모리에 민감한 서비스에는 메모리 시나리오를 권한다.

자원 스트레스를 받는 축이 곧 가장 잘 무너지는 축이라는 가정에 기댄 추천이다. 무작위로 장애를 뿌리는 대신 텔레메트리로 후보를 좁혀 실험 효율을 높인다.

상위 프레임워크인 Krkn-AI는 한 단계 더 나아가, 서비스 레벨 목표(SLO)를 기준으로 실험을 자동으로 진화시킨다. 유전 알고리즘(genetic algorithm)으로 단일·복합 장애 조합을 생성하고, SLO 위반 정도로 점수를 매겨 영향이 큰 실험을 반복적으로 다듬는다. 사람이 시나리오를 일일이 추측하지 않아도 클러스터에 맞는 고파괴 실험을 찾아낸다.

Shift-Left 테스팅

테스트를 개발 주기의 왼쪽(초기 단계) 으로 이동시키는 전략이다.

Shift-Left 실천 방법

  1. 정적 분석 (Static Analysis): TypeScript, ESLint, SonarQube로 코드 작성 시점에 문제 탐지
  2. Pre-commit Hooks: husky + lint-staged로 커밋 전 자동 검사
  3. PR 단계 자동 테스트: GitHub Actions에서 모든 PR에 대해 테스트 실행
  4. 보안 스캔 (Static Application Security Testing, SAST): Snyk, Semgrep으로 취약점 조기 발견

AI 기반 테스트 생성

현황

AI가 테스트 생성, 플레이크 탐지(Flake Detection), 셀렉터 자동 수정(Self-Healing Selectors)을 자동화하고 있다.

주요 도구 (2026)

도구기능특징
TestimAI 기반 E2E 테스트 자동 생성Self-healing 셀렉터
Mabl지능형 테스트 자동화자동 결함 탐지
Diffblue CoverJava 단위 테스트 자동 생성AI 기반 코드 분석
Codium AILLM 기반 테스트 생성코드 컨텍스트 이해
testRigor자연어 기반 테스트 작성비개발자도 테스트 작성 가능

AI 기반 테스트 자동화

AI 모델이 테스트 케이스를 생성하고 결과를 분석하는 접근 방식이 확산되고 있다:

  • 합성 데이터 생성 (Synthetic Data Generation): 테스트용 데이터 자동 생성
  • 편향 탐지 (Bias Detection): ML 모델의 편향을 자동으로 식별
  • 설명 가능성 테스트 (Explainability Testing): 모델 결정의 해석 가능성 검증

테스트 자동화 모범 사례

CI/CD 파이프라인 통합

yaml
# GitHub Actions 예시
test:
  runs-on: ubuntu-latest
  steps:
    - name: Unit Tests
      run: npm run test:unit -- --coverage
    - name: Integration Tests
      run: npm run test:integration
    - name: E2E Tests (핵심 플로우만)
      run: npx playwright test --project=critical
    - name: Mutation Testing (주간)
      if: github.event.schedule
      run: npx stryker run

테스트 커버리지 가이드라인

지표목표비고
라인 커버리지80% 이상최소 기준
브랜치 커버리지70% 이상조건문 분기 검증
돌연변이 점수80% 이상테스트 품질 검증
E2E 커버리지핵심 플로우 100%비즈니스 크리티컬 경로

테스트 작성 원칙

  1. FIRST 원칙: Fast, Isolated, Repeatable, Self-validating, Timely
  2. AAA 패턴: Arrange(준비) → Act(실행) → Assert(검증)
  3. 한 테스트에 한 가지 검증: 실패 시 원인을 즉시 파악 가능
  4. 테스트 이름으로 의도 표현: 사용자가_존재하지_않으면_404를_반환한다

정리

테스트 자동화는 단위·통합·E2E 테스트를 어떤 비율로 배치할지 정하는 데서 시작한다. 순수 로직이 많으면 테스트 피라미드, 웹 애플리케이션·API 서버면 테스팅 트로피가 비용 대비 효과가 좋다. 계약·돌연변이·속성 기반·스냅샷 테스트는 기본 수준이 검증하지 못하는 영역을 메우고, 카오스 엔지니어링은 장애 상황의 복원력을 확인한다. Shift-Left와 CI/CD 통합으로 검증 시점을 앞당기고, AI 기반 생성·자가 치유 셀렉터가 작성·유지보수 비용을 줄이는 방향으로 자동화 범위가 넓어지고 있다. 다음 글에서는 수평 확장의 원칙과 스케일 아웃 전략을 다룬다.

참고 자료