유닛 테스트 vs 통합 테스트 vs 시스템 테스트: 차이점은?

Ashley Goolam

Ashley Goolam

23 December 2025

유닛 테스트 vs 통합 테스트 vs 시스템 테스트: 차이점은?

단위 테스트(Unit test) vs 통합 테스트(Integration test) vs 시스템 테스트(System test)의 문제는 숙련된 개발자들조차 때때로 혼란스럽게 만듭니다. 이 세 가지 테스트 레벨은 소프트웨어 품질의 기반을 형성하지만, 팀들은 종종 이를 잘못 사용하여 너무 얕거나 유지보수 비용이 엄청나게 많이 드는 테스트 스위트를 만듭니다. 각 테스트가 테스트 전략에서 어디에 적합한지 이해하는 것은 학술적인 문제를 넘어, 제품 출시 속도와 릴리스에 대한 확신에 직접적인 영향을 미칩니다.

이 가이드는 각 테스트 레벨의 범위, 목적, 시기를 명확히 하고, 테스트 피라미드에서 이들이 어떻게 함께 작동하는지 보여줄 것이며, 즉시 적용할 수 있는 실용적인 예시도 제공할 것입니다. 마이크로서비스, 모놀리식 아키텍처 또는 API를 개발하는 동안 단위 테스트 vs 통합 테스트 vs 시스템 테스트를 이해하는 것은 필수적입니다.

버튼

단위 테스트(Unit Testing)란 무엇인가요?

단위 테스트는 애플리케이션의 가장 작은 테스트 가능 부분(개별 함수, 메서드 또는 클래스)을 완벽하게 격리된 상태에서 검증합니다. 목표는 각 단위가 해당 사양에 따라 올바르게 작동함을 입증하는 것입니다.

범위 및 예시

단위 테스트는 의존성 없이 하나의 논리 조각을 검사합니다. 다음은 간단한 예시입니다.

// Function under test
function calculateDiscount(price, discountPercent) {
  if (discountPercent < 0 || discountPercent > 100) {
    throw new Error('Invalid discount percentage');
  }
  return price * (discountPercent / 100);
}

// Unit test
describe('calculateDiscount', () => {
  it('calculates 20% discount correctly', () => {
    expect(calculateDiscount(100, 20)).toBe(20);
  });
  
  it('throws error for negative discount', () => {
    expect(() => calculateDiscount(100, -5)).toThrow();
  });
});

이 테스트는 입력 값을 제공하고 출력 값을 직접 검증하며, 데이터베이스, API 또는 UI는 관여하지 않습니다.

장점과 단점

장점:

단점:

통합 테스트(Integration Testing)란 무엇인가요?

통합 테스트는 여러 구성 요소가 올바르게 함께 작동하는지 검증합니다. 이는 단위 간의 인터페이스, 즉 API 엔드포인트, 데이터베이스 연결, 메시지 큐, 서비스 상호 작용에 중점을 둡니다.

범위 및 예시

다음은 데이터베이스를 사용하는 사용자 등록 엔드포인트에 대한 통합 테스트입니다.

// Integration test for POST /api/users
describe('User Registration API', () => {
  it('creates user and stores in database', async () => {
    const userData = {
      name: 'Test User',
      email: 'test@example.com',
      password: 'ValidPass123'
    };
    
    // Act: Call the actual API
    const response = await axios.post('http://localhost:3000/api/users', userData);
    
    // Assert: Check response AND database
    expect(response.status).toBe(201);
    expect(response.data).toHaveProperty('userId');
    
    // Verify database state
    const userInDb = await db.users.findByEmail('test@example.com');
    expect(userInDb).toBeTruthy();
    expect(userInDb.name).toBe('Test User');
  });
});

이 테스트는 API, 비즈니스 로직 및 데이터베이스 통합이 함께 작동함을 증명합니다.

장점과 단점

장점:

단점:

시스템 테스트(System Testing)란 무엇인가요?

시스템 테스트는 완전하게 통합된 시스템이 비즈니스 요구사항에 부합하는지 검증합니다. 애플리케이션을 블랙박스로 간주하여 사용자 관점에서 종단 간 워크플로우를 테스트합니다.

범위 및 예시

전자상거래 구매 워크플로우에 대한 시스템 테스트:

// System test: Complete purchase flow
describe('E-commerce Purchase System', () => {
  it('allows user to browse, add to cart, and checkout', async () => {
    // Step 1: User registration
    const user = await api.register('shopper@example.com', 'password');
    
    // Step 2: Browse products
    const products = await api.searchProducts('laptop');
    expect(products.length).toBeGreaterThan(0);
    
    // Step 3: Add to cart
    await api.addToCart(user.token, products[0].id, 1);
    
    // Step 4: Checkout
    const order = await api.checkout(user.token, {
      shippingAddress: '123 Main St',
      paymentMethod: 'visa'
    });
    
    // Oracle: Verify complete order
    expect(order.status).toBe('confirmed');
    expect(order.total).toBeGreaterThan(0);
    
    // Verify side effects
    const inventory = await api.getInventory(products[0].id);
    expect(inventory.stock).toBe(initialStock - 1);
  });
});

이 테스트는 여러 API, 데이터베이스 및 외부 서비스(결제 게이트웨이)에 걸쳐 있습니다.

장점과 단점

장점:

단점:

소프트웨어 테스트 피라미드: 세 가지 테스트의 관계

테스트 피라미드는 **단위 테스트 vs 통합 테스트 vs 시스템 테스트**가 어떻게 배분되어야 하는지 시각적으로 보여줍니다:

        System Tests (10%)
            ▲
    Integration Tests (30%)
            ▲
    Unit Tests (60%)

하단 계층 (단위 테스트): 가장 큰 비중, 가장 빠른 실행, 지속적으로 실행
중간 계층 (통합 테스트): 중간 비중, 중요 통합 검증
상단 계층 (시스템 테스트): 가장 작은 비중, 핵심 비즈니스 워크플로우 테스트

이러한 형태는 신뢰성을 유지하면서 빠른 피드백을 보장합니다. 피라미드를 뒤집으면(많은 시스템 테스트, 적은 단위 테스트) 테스트 스위트가 느리고, 취약하며, 비용이 많이 들게 됩니다.

소프트웨어 테스트 피라미드

각 테스트 수행 시기: 라이프사이클 통합

개발 단계 주요 테스트 유형 빈도 실행 시간
코드 작성 단위 테스트 저장할 때마다 < 1초
풀 리퀘스트 단위 + 통합 커밋 전 1-5분
머지 전 통합 + 선택된 시스템 PR 승인 시 5-15분
야간 빌드 전체 스위트 (모든 유형) 매일 30-60분
릴리스 전 시스템 테스트 + 스모크 테스트 배포 전 15-30분
운영 환경 스모크 테스트 + 모니터링 지속적 실시간

단위 테스트 vs 통합 테스트 vs 시스템 테스트의 타이밍을 올바르게 맞추는 것은 품질 게이트가 의미 있도록 보장하면서 병목 현상을 방지합니다.

비교표: 올바른 테스트 선택

요인 단위 테스트 통합 테스트 시스템 테스트
속도 ⚡⚡⚡ 매우 빠름 ⚡⚡ 보통 ⚡ 느림
격리성 높음 중간 낮음
디버깅 용이성 쉬움 보통 어려움
신뢰도 낮음 중간 높음
유지보수 낮음 중간 높음
작성 시기 코딩 전/중 단위 기능 작동 후 통합 후
작성 주체 개발자 개발자 + QA QA + 개발자

실용적인 예시: API 엔드포인트 테스트

이제 `POST /api/users` 엔드포인트에 대한 **단위 테스트 vs 통합 테스트 vs 시스템 테스트**를 실제로 살펴보겠습니다.

단위 테스트 (유효성 검사 로직 테스트)

// Test only the validation function
describe('validateUser', () => {
  it('rejects invalid email', () => {
    const result = validateUser({ email: 'invalid' });
    expect(result.isValid).toBe(false);
    expect(result.errors).toContain('Invalid email format');
  });
});

통합 테스트 (API + 데이터베이스 테스트)

// Test API layer with real database
describe('POST /api/users integration', () => {
  it('creates user in database', async () => {
    const response = await request(app)
      .post('/api/users')
      .send({ name: 'Test', email: 'test@example.com' });
    
    expect(response.status).toBe(201);
    
    // Oracle: Verify database
    const user = await db.users.findByEmail('test@example.com');
    expect(user.name).toBe('Test');
  });
});

시스템 테스트 (완전한 워크플로우 테스트)

// Test registration → login → profile update
describe('User management system', () => {
  it('allows complete user lifecycle', async () => {
    // Register
    const reg = await api.post('/api/users', userData);
    expect(reg.status).toBe(201);
    
    // Login
    const login = await api.post('/api/auth/login', credentials);
    expect(login.data.token).toBeTruthy();
    
    // Update profile
    const update = await api.put('/api/users/me', updates, {
      headers: { Authorization: `Bearer ${login.data.token}` }
    });
    expect(update.status).toBe(200);
    
    // Verify final state
    const profile = await api.get('/api/users/me', {
      headers: { Authorization: `Bearer ${login.data.token}` }
    });
    expect(profile.data.name).toBe(updates.name);
  });
});

Apidog가 개발 팀의 API 테스트를 돕는 방법

단위 테스트 vs 통합 테스트 vs 시스템 테스트를 이해하는 것은 중요하지만, API에 이를 구현하는 것은 번거로울 수 있습니다. Apidog는 특히 통합 및 시스템 테스트에 대한 많은 작업을 자동화합니다.

자동 테스트 오라클 생성

통합 테스트의 경우, Apidog는 OpenAPI 사양에서 직접 테스트 오라클을 생성합니다:

# API 사양에서 Apidog가 생성하는 내용:
Test: POST /api/users
Oracle 1: 상태는 201이어야 함
Oracle 2: 응답은 사용자 스키마와 일치해야 함
Oracle 3: Location 헤더가 존재해야 함
Oracle 4: 응답 시간 < 500ms
Oracle 5: 데이터베이스 쿼리는 생성된 사용자를 반환함

이를 통해 수동 오라클 정의가 불필요해지고, 테스트가 API 계약과 동기화된 상태를 유지합니다.

시스템 테스트를 위한 시각적 테스트 빌더

Apidog에서는 복잡한 워크플로우 시스템 테스트가 시각적으로 가능해집니다:

테스트: 완전한 사용자 온보딩
1. POST /api/users (생성)
2. POST /api/auth/verify (이메일 인증)
3. POST /api/auth/login (인증)
4. GET /api/dashboard (데이터 로드)
5. POST /api/preferences (환경 설정)

각 단계에서의 Assertion + 최종 상태 검증

Apidog가 인증, 데이터 연결 및 Assertion을 자동으로 처리하므로, API 호출을 드래그 앤 드롭하여 이를 구축할 수 있습니다.

버튼
apidog에서 테스트

지속적인 테스트를 위한 CI/CD 통합

Apidog는 CI/CD에서 **단위 테스트 vs 통합 테스트 vs 시스템 테스트** 계층을 실행합니다:

# GitHub Actions 파이프라인
- name: 단위 테스트 실행
  run: npm test:unit

- name: Apidog 통합 테스트 실행
  run: apidog run --tags "@integration"

- name: Apidog 시스템 테스트 실행
  run: apidog run --tags "@system"

이를 통해 각 테스트 유형이 적절한 단계에서 실행되고, 결과는 Slack이나 이메일로 직접 게시됩니다.

apidog의 ci/cd

테스트 커버리지 가시성

Apidog는 어떤 API가 단위, 통합, 시스템 테스트 커버리지를 가지고 있는지 보여줍니다:

엔드포인트 단위 통합 시스템 커버리지
POST /users 100%
GET /users/:id 67%
DELETE /users 67%

이러한 가시성은 팀이 전략적으로 테스트 공백을 메우는 데 도움이 됩니다.

자주 묻는 질문

Q1: API 엔드포인트에 단위 테스트를 작성해야 하나요?

Ans: API 엔드포인트는 로직을 조율하므로 통합 테스트를 가져야 합니다. 엔드포인트 내의 *비즈니스 로직*은 별도로 단위 테스트되어야 합니다.

Q2: 통합 테스트는 얼마나 많이 작성해야 충분한가요?

Ans: 모든 중요 경로와 오류 시나리오를 다루세요. 좋은 규칙은 다음과 같습니다. 통합 과정의 버그가 프로덕션에 도달할 수 있다면, 이를 위한 테스트를 작성하세요.

Q3: 시스템 테스트는 유지보수 비용을 감수할 가치가 있나요?

Ans: 네, 하지만 핵심 비즈니스 워크플로우에만 해당됩니다. 시스템 테스트는 비즈니스 가치의 80%를 창출하는 기능의 10-20%로 제한하세요.

Q4: Apidog가 단위 테스트를 생성할 수 있나요?

Ans: 아니요. 단위 테스트는 내부 코드 구조에 대한 지식을 필요로 합니다. Apidog는 외부에서 API 동작을 관찰할 수 있는 통합 및 시스템 테스트에 탁월합니다.

Q5: 새 프로젝트의 경우 어떤 테스트 유형을 우선시해야 하나요?

Ans: 단위 테스트(기초)로 시작하고, 구성 요소가 연결됨에 따라 통합 테스트를 추가한 다음, 중요한 사용자 여정을 위한 시스템 테스트를 추가하세요. 이 피라미드 접근 방식은 기술 부채를 방지합니다.

결론

단위 테스트 vs 통합 테스트 vs 시스템 테스트 결정은 하나를 다른 것보다 우선시하는 것이 아니라, 각 테스트를 적절한 시기와 비율로 적용하는 것에 관한 것입니다. 단위 테스트는 개발에 속도와 정밀성을 제공합니다. 통합 테스트는 단위 테스트가 놓칠 수 있는 연결 문제를 포착합니다. 시스템 테스트는 전체 제품이 사용자에게 제대로 작동한다는 확신을 줍니다.

이 계층 구조를 숙달하면 테스트 스위트가 유지보수 부담이 아닌 전략적 자산이 됩니다. 현재 테스트 분포를 감사하는 것부터 시작하세요. 너무 많고 느리며 취약한 시스템 테스트로 피라미드가 역전되어 있나요? 초점을 아래로 옮기세요. 중요한 통합 커버리지가 부족한가요? 그 격차를 채우세요.

Apidog와 같은 최신 도구는 테스트 생성 및 실행을 자동화하여 통합 및 시스템 계층을 훨씬 더 쉽게 관리할 수 있도록 합니다. 이를 통해 개발 속도를 늦추지 않고 테스트 피라미드 형태를 유지할 수 있습니다. 품질은 릴리스를 지연시키는 별도의 단계가 아니라 프로세스의 자연스러운 결과가 됩니다.

기억하세요: 목표는 모든 것을 테스트하는 것이 아니라, 적절한 수준에서 올바른 것을 테스트하는 것입니다. **단위 테스트 vs 통합 테스트 vs 시스템 테스트**가 전략에서 명확해지면, 출시가 예측 가능해지고, 신뢰가 커지며, 팀은 문제 해결에 시간을 덜 쓰고 가치를 창출하는 데 더 많은 시간을 할애할 수 있습니다.

버튼

Apidog에서 API 설계-첫 번째 연습

API를 더 쉽게 구축하고 사용하는 방법을 발견하세요