Skip to main content
입문

테스트 피라미드: 실패를 어느 테스트에 맡길 것인가

막으려는 실패를 먼저 정하고, 그 실패를 관찰하는 데 필요한 실제 경계 중 가장 작은 테스트를 선택해 빠른 피드백과 실제 환경 신뢰를 함께 설계한다.

마지막 검증 재검증 정책: 제품 버전 의존: 새 주요 버전마다 재검증
목차
표준·구현·측정·해석 표시는 무엇인가요?
  • 표준웹 표준이나 언어 명세가 정한 동작
  • 구현특정 기술이나 브라우저가 실제로 구현한 동작
  • 측정명시한 환경에서 직접 실행해 관찰한 결과
  • 해석앞선 근거에서 도출한 설계 판단
  • 미확인아직 공식 근거나 재현 결과를 확인하지 못한 내용

30초 요약

테스트 계층을 고를 때는 세 문장을 순서대로 묻는다.

  1. 어떤 실패를 막으려는가?
  2. 그 실패를 보려면 무엇이 실제로 함께 동작해야 하는가?
  3. 그 경계를 포함한 테스트 중 가장 작고 빠른 것은 무엇인가?

한 줄로 기억하면 실패 질문 → 필요한 실제 경계 → 가장 작은 충분한 테스트다.

피라미드 모양은 이 판단을 여러 기능에 반복한 결과다. 작은 로직 실패는 좁고 빠른 테스트로 많이 확인하고, 브라우저·서버·네이티브 장치가 맞물려야만 보이는 실패는 더 넓은 테스트로 적게 확인한다. 피라미드는 70 대 20 대 10 같은 비율을 복사하는 표가 아니다.

피라미드는 전체 테스트 구성의 방향이다

아래가 넓은 이유는 ‘단위 테스트가 더 고급이라서’가 아니다. 보통 더 빨리 실행되고, 실패 원인을 좁히기 쉽고, 같은 시간에 최솟값·최댓값과 그 바로 안팎 같은 경계값을 더 많이 확인할 수 있기 때문이다.

  • 아래: 작은 로직의 관찰 가능한 동작을 많이 확인한다.
  • 가운데: 둘 이상의 실제 경계가 협력하는 동작을 그보다 적게 확인한다.
  • : 실제 앱 전체의 핵심 흐름을 가장 적게 확인한다.
  • 위로 갈수록 실제 환경 경계와 실행·유지·진단 비용이 커지는 경향이 있다.

정확한 층 이름과 개수를 먼저 고정하지 않는다. Android 현재 문서도 전통적인 세 층 외에 단위, 컴포넌트, 기능, 애플리케이션, 출시 후보라는 다섯 층 예를 제시한다. 중요한 것은 이름보다 각 테스트가 무엇을 실제로 포함하고 어떤 실패를 관찰하는지 팀 문서에 적는 일이다.

범위와 크기를 나누면 이름 논쟁이 줄어든다

  • 범위(scope, 스코프): 이 테스트가 올바르다고 확인하려는 코드와 협력 경계의 폭.
  • 크기(size, 사이즈): 프로세스, 브라우저, 서버, 네트워크, 장치와 실행 시간처럼 필요한 자원의 크기.
  • 운영 환경 유사도(fidelity, 충실도): 테스트 환경이 실제 사용 환경과 얼마나 닮았는지.

예를 들어 실제 브라우저에서 사용자 화면(UI) 컴포넌트 하나의 코드 경로만 확인할 수 있다. 검증 범위는 좁지만 실제 브라우저를 실행하므로 브라우저 밖에서 JavaScript를 실행하는 Node.js 테스트보다 실행 크기는 클 수 있다. 반대로 하나의 프로세스에서 여러 실제 코드 부분을 함께 실행하면 범위는 중간이어도 크기는 작을 수 있다.

따라서 이 글에서는 다음처럼 이름을 사용한다.

  • 단위 테스트 — 작은 로직 단위의 관찰 가능한 동작을 좁은 범위에서 확인한다.
  • 통합 테스트 — 둘 이상의 실제 앱 부분이나 한 외부 경계의 협력을 확인한다.
  • 컴포넌트 테스트 — UI 컴포넌트를 렌더링하고 사용자가 볼 수 있는 결과와 상호작용을 확인한다. 실제로 함께 둔 코드와 협력 경계에 따라 단위 또는 통합 범위가 될 수 있고, 브라우저·Node.js·장치는 실행 크기와 운영 환경 유사도를 바꾼다.
  • E2E 테스트 — 외부 사용자의 동작에서 시작해 여러 실제 앱 부분을 거친 핵심 흐름의 결과를 확인한다.

다른 팀의 ‘통합 테스트’가 이 글의 컴포넌트 테스트를 뜻할 수도 있다. 이름만 보고 실행 환경과 신뢰 범위를 추측하지 말고, 실제로 포함한 부분을 확인한다.

실패 질문에서 필요한 실제 경계를 찾는다

‘할인 기능을 테스트한다’는 문장만으로는 계층을 고를 수 없다. 실패를 더 구체적으로 쓴다.

  1. 쿠폰 반올림
    • 막을 실패: 10% 쿠폰의 반올림 규칙이 틀린다.
    • 실제여야 할 경계: 가격 계산 로직.
    • 시작 테스트: 단위 테스트.
  2. React 합계 화면
    • 막을 실패: 쿠폰 입력 후 합계와 오류 문구가 함께 갱신되지 않는다.
    • 실제여야 할 경계: React UI, 상태와 실제 하위 컴포넌트.
    • 시작 테스트: 컴포넌트·통합 테스트.
  3. 프론트엔드와 서버 응답
    • 막을 실패: 요청과 응답 형식이 서로 맞지 않는다.
    • 실제여야 할 경계: 데이터를 전송 형식으로 바꾸고 되돌리는 과정과 웹 요청·응답 규칙인 HTTP 경계의 실제 구현.
    • 시작 테스트: HTTP 경계 통합 테스트.
  4. Web 결제 이동
    • 막을 실패: 운영 빌드에서 로그인 뒤 결제 화면까지 이동하지 못한다.
    • 실제여야 할 경계: 실제 브라우저, 라우팅, 배포 앱과 통제한 서버.
    • 시작 테스트: 핵심 흐름 E2E 테스트.
  5. Android 권한 거부
    • 막을 실패: 권한을 거부한 뒤 앱이 잘못된 화면으로 돌아간다.
    • 실제여야 할 경계: Android 권한 화면, 네이티브 코드와 앱 바이너리.
    • 시작 테스트: 장치·에뮬레이터 테스트.

‘중요하니 E2E’가 아니라 어디에서만 생길 수 있는 실패인가를 묻는다. 반올림의 많은 입력 조합은 단위 테스트가 빠르고 원인을 잘 알려 준다. 실제 브라우저에서 같은 조합을 모두 반복해도 새로운 브라우저 경계 신뢰는 거의 늘지 않고 실행 시간과 유지 비용만 커질 수 있다.

반대로 브라우저 쿠키, 실제 라우팅, 네이티브 권한이나 패키징이 원인이라면 순수 JavaScript 테스트로 내려서는 그 실패를 볼 수 없다. 가장 낮은 계층은 ‘무조건 단위’가 아니라 필요한 경계를 잃지 않는 가장 낮은 곳이다.

통합 테스트는 협력 경계를 실제로 둔다

통합 테스트는 모든 서버와 데이터베이스를 한꺼번에 실행한다는 뜻이 아니다. React UI와 상태, 프론트엔드와 한 서버 경계처럼 이번에 확인할 협력 부분을 실제로 함께 둔다. 그보다 바깥은 통제할 수 있으며, 무엇을 실제로 두었는지 테스트 이름이나 설명에 남긴다.

테스트 대역은 경계 밖을 통제한다

테스트 대역(test double)은 실제 의존성 자리에 넣는 통제 가능한 대체물이다. 흔히 모두 ‘mock(목)’이라고 부르지만, 미리 정한 값을 돌려주는 stub(스텁), 동작 가능한 단순 구현인 fake(페이크) 등 역할이 다를 수 있다. 이 글에서는 세부 분류보다 다음 규칙을 기억한다.

검증하려는 경계는 실제로 두고, 그 바깥만 통제한다.

쿠폰 응답을 받은 뒤 UI가 어떻게 바뀌는지 보려면 외부 쿠폰 서버는 정해진 응답으로 바꿀 수 있다. 그러나 프론트엔드가 실제 서버의 JSON을 읽을 수 있는지 검증하면서 JSON 응답을 앱 데이터로 바꾸는 응답 파서까지 가짜로 바꾸면 확인하려던 경계를 지워 버린다.

대역을 많이 쓰면 항상 더 좋은 단위 테스트가 되는 것도 아니다. 내부 함수 호출 순서를 그대로 복제한 검증은 동작이 같아도 리팩터링에서 깨질 수 있다. 리팩터링은 외부 동작을 유지한 채 내부 구조를 바꾸는 일이다. 사용자가 보거나 공개 인터페이스로 관찰할 수 있는 결과를 우선 확인한다.

React UI는 사용자가 관찰하는 결과를 검증한다

React 컴포넌트 테스트의 질문은 ‘setState가 호출됐는가?’보다 다음에 가깝다.

Given  쿠폰 입력 칸과 적용 버튼이 보인다.
When   사용자가 유효한 쿠폰을 입력하고 적용한다.
Then   할인된 합계가 보이고 오류 문구는 사라진다.

Given–When–Then은 전제–행동–결과로 테스트를 읽는 형식이다. 이 테스트는 React의 내부 상태 이름을 몰라도 UI 계약을 확인한다. 컴포넌트를 실제로 렌더링하고 관련 상태·하위 컴포넌트를 함께 두되, 네트워크 바깥은 통제하면 빠른 통합 테스트가 될 수 있다.

React와 React Native는 실행환경 경계를 따로 확인한다

React Native에서는 한 기능을 최소 두 질문으로 나누는 편이 유용하다.

  • JavaScript 질문: 권한 거부 결과를 받으면 안내 화면을 렌더링하는가?
  • 플랫폼 질문: 실제 권한 창에서 거부했을 때 네이티브 결과가 앱에 전달되는가?

첫 질문은 빠른 컴포넌트 테스트에서 네이티브 연동 코드를 테스트 대역으로 바꿔 많은 상태를 확인할 수 있다. 둘째 질문은 실제 iOS·Android 경계 없이는 증명할 수 없다. 카메라, 알림, 파일, 딥 링크와 네이티브 연동 코드처럼 운영체제 동작에 의존하는 기능은 장치 계층의 비중이 일반 Web 앱보다 커질 수 있다. 이것이 피라미드 비율을 고정하지 않는 이유다.

E2E는 전체 시스템의 실행한 경로에 높은 신뢰를 더한다. 실행하지 않은 입력 조합, 다른 브라우저·기기, 성능·접근성·보안까지 자동으로 증명하지는 않는다. 인증, 결제, 앱 시작처럼 사용자 가치와 장애 영향이 큰 대표 흐름을 고르고, 세부 분기는 더 낮은 계층에 둔다.

각 테스트는 혼자 다시 실행돼야 한다

격리는 한 테스트가 다른 테스트의 쿠키, 데이터, 시간이나 실행 순서에 기대지 않는 성질이다. 격리되지 않은 테스트는 개별 실행에서는 통과하지만 전체 묶음이나 병렬 실행에서 실패할 수 있다. 최소한 다음을 확인한다.

  • 각 테스트가 필요한 사용자와 데이터를 직접 만들고 정리하는가?
  • 실행 순서를 바꿔도 같은 결과인가?
  • 시스템 시간·난수·네트워크 응답을 필요한 범위에서 통제하는가?
  • 실패 메시지가 어떤 동작과 경계가 어긋났는지 알려 주는가?

넓은 테스트가 느린 이유를 무조건 감수하지 않는다. 실제 브라우저가 필요해도 매 테스트마다 모든 데이터를 원격 서비스에서 만들 필요는 없다. 필요한 실제 경계는 유지하면서 준비 비용과 공유 상태를 줄인다.

플레이키 테스트는 별도의 결함이다

플레이키(flaky)는 ‘가끔 실패하니 중요하지 않다’는 뜻이 아니다. 변경이 실제로 잘못됐는지, 테스트 환경이 흔들렸는지 구분할 수 없게 만드는 검증 신호의 결함이다. 재시도는 진단 자료를 더 모으는 제한된 수단일 수 있지만, 통과 결과로 원인을 지우지는 못한다.

같은 코드와 같은 선언 설정에서는 다음처럼 신호를 구분한다.

  • 항상 통과: 안정된 통과 신호다.
  • 항상 같은 이유로 실패: 재현 가능한 결함 신호다.
  • 통과와 실패가 오락가락: 플레이키 신호이므로 원인을 조사한다.

Google의 원인 정리처럼 공유 데이터, 통제하지 않은 시간, 외부 네트워크, 비동기 완료를 기다리지 않는 검증과 테스트 순서 의존성을 먼저 본다. 격리와 기다림 계약을 고친 뒤 같은 조건에서 결과가 안정적인지 확인한다.

정적 분석과 커버리지는 피라미드를 대신하지 않는다

정적 분석은 사용하지 않은 값, 금지한 문법, 타입 불일치처럼 실행 전 알 수 있는 실패에 가장 빠른 계층이다. 그러나 버튼을 눌렀을 때 화면이 바뀌는지는 코드를 실행해야 안다. 반대로 E2E 테스트가 통과해도 실행하지 않은 타입 경로가 모두 안전하다는 뜻은 아니다.

코드 커버리지는 테스트가 방문한 코드의 비율이나 위치를 나타내는 지표다. Android 공식 문서도 검사하지 않은 부분을 찾는 데 쓸 수 있지만 좋은 전략을 주장하는 유일한 지표로 쓰지 말라고 설명한다. 줄을 실행했다는 사실과 올바른 결과를 검증했다는 사실은 다르다.

이 글의 피라미드 바깥에도 탐색적 수동 테스트, 화면 모양 변화 비교, 접근성, 성능, 보안과 호환성 검증이 있다. 피라미드는 자동 기능 테스트의 비용과 경계를 정리하는 한 도구이지 품질 전체의 그림은 아니다.

새 기능의 테스트를 고르는 순서

새 기능마다 다음 순서를 짧게 기록한다.

  1. 사용자가 기대하는 동작과 막으려는 실패를 한 문장으로 쓴다.
  2. 그 실패가 생길 수 있는 실제 경계를 표시한다.
  3. 해당 경계를 잃지 않는 가장 작은 테스트를 고른다.
  4. 바깥 의존성만 테스트 대역으로 통제한다.
  5. 더 높은 테스트가 새 경계 신뢰를 더하는지 확인한다.
  6. 독립 실행, 실패 진단 시간과 플레이키 여부를 관찰한다.

예를 들어 상위 E2E가 반올림 오류를 처음 잡았다면 같은 오류를 더 빠르게 재현하는 단위 회귀 테스트를 추가한다. E2E에는 ‘실제 결제 흐름이 연결된다’는 고유한 경계만 남긴다. 회귀 테스트는 한 번 고친 오류가 다시 생기지 않는지 확인하는 테스트다.

정해진 비율보다 다음 질문이 더 오래 남는다.

이 테스트가 실패하면 무엇이 깨졌는지 바로 알 수 있고, 이보다 작은 테스트는 같은 실패를 볼 수 없는가?

다음 글과의 경계

이번 글은 실패 유형을 어떤 테스트 경계에 맡길지에 집중했다. 다음 내용은 분리한다.

  • 이 테스트들을 변경 검증과 배포 단계에 언제 실행할까: 다음 10편 CI와 CD
  • 테스트한 산출물과 실제 배포 산출물이 같은가: 11편 동일성과 승격
  • 로그·오류·메트릭은 테스트가 놓친 운영 문제를 어떻게 보여 주는가: 12편 관측성 신호
  • 테스트 실행 시간과 장치 비용을 어떻게 비교할까: 20편 프론트엔드 비용 모델

마지막으로 한 줄만 기억한다. 막으려는 실패를 볼 수 있는 실제 경계를 남기고, 그중 가장 작고 빠른 테스트를 선택한다.

Active recall

기억에서 꺼내 보기

답을 쓰지 않아도 됩니다. 먼저 머릿속으로 답한 뒤 펼쳐서 정답·이유·흔한 오해를 비교하세요.

  1. 01테스트 종류 이름보다 먼저 써야 할 것은 무엇일까?

    정답

    막으려는 실패와 그 실패를 관찰하려면 어떤 부분이 실제로 함께 동작해야 하는지를 먼저 쓴다.

    왜 그런가

    이름부터 고르면 필요한 경계를 가짜로 바꾸거나, 반대로 불필요하게 전체 시스템을 띄우기 쉽다.

    관련 설명 다시 읽기
    지금 어느 정도 기억났나요?

    선택한 상태와 다음 복습일은 이 브라우저에만 저장됩니다.

  2. 02테스트 크기와 테스트 범위는 같은 말일까?

    정답

    아니다. 범위는 검증하려는 코드 경로의 폭이고, 크기는 실행에 필요한 프로세스·장치·네트워크·시간 같은 자원이다.

    왜 그런가

    실제 브라우저에서 UI 컴포넌트 하나만 확인하면 코드 범위는 좁아도 Node.js 테스트보다 실행 크기는 클 수 있다.

    관련 설명 다시 읽기
    지금 어느 정도 기억났나요?

    선택한 상태와 다음 복습일은 이 브라우저에만 저장됩니다.

  3. 03더 낮은 계층으로 내린다는 것은 모든 의존성을 모의 구현으로 바꾼다는 뜻일까?

    정답

    아니다. 검증하려는 협력 경계는 실제로 두고, 그 바깥에서 통제할 필요가 있는 부분만 테스트 대역으로 바꾼다.

    왜 그런가

    확인하려는 대상 자체를 가짜로 바꾸면 빠르더라도 그 경계가 실제로 맞물리는지는 증명하지 못한다.

    관련 설명 다시 읽기
    지금 어느 정도 기억났나요?

    선택한 상태와 다음 복습일은 이 브라우저에만 저장됩니다.

  4. 04React 또는 React Native 컴포넌트 테스트가 통과하면 실제 브라우저나 장치 동작도 증명될까?

    정답

    컴포넌트가 관찰 가능한 UI 동작을 만드는지는 확인할 수 있지만, 실제 브라우저 기능이나 네이티브 코드·권한·패키징 경계는 별도 실제 환경 테스트가 필요하다.

    왜 그런가

    각 계층은 자신이 실제로 포함한 경계에서만 신뢰를 더한다.

    관련 설명 다시 읽기
    지금 어느 정도 기억났나요?

    선택한 상태와 다음 복습일은 이 브라우저에만 저장됩니다.

  5. 05같은 변경에서 테스트가 통과했다 실패하면 재시도로 통과시키면 될까?

    정답

    재시도 결과만으로 변경의 정상 여부를 확정하지 말고, 공유 상태·시간·네트워크·순서 의존성을 찾아 같은 조건이면 항상 같은 결과가 나오게 해야 한다.

    왜 그런가

    플레이키 테스트는 실패 신호를 믿지 못하게 하며 이전에 동작하던 기능이 다시 깨진 회귀와 테스트 결함을 구분하는 비용을 만든다.

    관련 설명 다시 읽기
    지금 어느 정도 기억났나요?

    선택한 상태와 다음 복습일은 이 브라우저에만 저장됩니다.

출처와 검증 범위

아래 날짜는 링크를 마지막으로 열어 본 날입니다. 문서 상단의 검증일은 글의 설명과 적용 범위를 다시 확인한 날입니다.

  1. Software Engineering at Google — Testing OverviewGoogle and Abseil · 공식 문서 · 확인 2026-08-04
  2. Software Engineering at Google — Unit TestingGoogle and Abseil · 공식 문서 · 확인 2026-08-04
  3. Testing strategiesAndroid Developers · 공식 문서 · 확인 2026-08-04
  4. What to test in AndroidAndroid Developers · 공식 문서 · 확인 2026-08-04
  5. Use test doubles in AndroidAndroid Developers · 공식 문서 · 확인 2026-08-04
  6. TestingReact Native · 공식 문서 · 확인 2026-08-04
  7. Best PracticesPlaywright · 공식 문서 · 확인 2026-08-04
  8. IntroductionTesting Library · 공식 문서 · 확인 2026-08-04
  9. jsdom READMEjsdom project · 소스 코드 · 확인 2026-08-04
  10. De-Flake Your Tests: Automatically Locating Root Causes of Flaky Tests in Code At GoogleGoogle Research and IEEE ICSME · 논문 · 확인 2026-08-04
  11. Test Flakiness — One of the main challenges of automated testingGoogle Testing Blog · 공식 문서 · 확인 2026-08-04
이 문서의 마지막까지 읽었습니다.