개념
레벨별 테스트 대상을 세는 법
시스템이 상위 컴포넌트 하나(CSCI) 아래 중간 컴포넌트 셋(CSC0103), 그 아래 각각
둘씩 단위 컴포넌트 여섯(CSU11CSU32)으로 이루어져 있다고 하자. 화살표는 호출 관계다.
CSCI
┌─────────┼─────────┐
CSC01 CSC02 CSC03
┌─┴─┐ ┌─┴─┐ ┌─┴─┐
CSU11 CSU12 CSU21 CSU22 CSU31 CSU32| 레벨 | 대상 | 대상 수 |
|---|---|---|
| 컴포넌트 테스트 | 각 컴포넌트 | 10개 (1 + 3 + 6) |
| 통합 테스트 | 호출 관계가 있는 컴포넌트 간 연결(화살표) | 9개 (3 + 6) |
| 시스템 테스트 | 시스템 자체 | 1개 |
이런 구조도를 아키텍처 설계도 라고 한다. 레벨별 테스트 대상을 정하려면 간단하더라도 시스템 구조부터 알아야 한다는 것이 요점이다. 시험에서는 구조도를 주고 "통합 테스트 대상은 몇 개인가"를 물을 수 있다 — 화살표의 개수 를 세면 된다.
컴포넌트 테스트 — 드라이버와 스텁
컴포넌트(단위) 테스트는 개별 모듈을 독립적으로 테스트한다. 그런데 모듈은 혼자 실행되지 않는다. 그래서 두 가지 대역이 필요하다.
| 대역 | 위치 | 역할 |
|---|---|---|
| 테스트 드라이버 (Driver) | 대상 위 | 대상을 호출하고 입력을 넘겨 주는 가짜 상위 모듈 |
| 테스트 스텁 (Stub) | 대상 아래 | 대상이 호출하는 하위 모듈을 대신하는 가짜 하위 모듈 |
컴포넌트 0 → 1 → 2 순서로 호출하는 시스템에서 컴포넌트 1을 테스트한다면, 위에는 드라이버 1을 두고, 아래의 컴포넌트 2 대신 컴포넌트 2의 스텁 을 둔다. 이렇게 해야 컴포넌트 1의 동작만 독립적으로 확인할 수 있다. 드라이버와 스텁을 포함한 이 실행 환경을 테스트 베드(Test bed) 또는 테스트 하네스 라고 부른다.
모의 객체 — 스텁의 객체 지향 버전
객체 지향 프로그램에서는 테스트하려는 메소드가 다른 클래스의 객체에 의존한다. 이때 쓰는 것이 모의(Mock) 객체 이고, 가이드는 이를 "스텁의 객체 지향 버전"이라고 설명한다. Mockito 같은 생성 프레임워크로 만들 수 있다.
모의 객체를 쓴 단위 테스트는 세 부분으로 이루어진다.
- 생성(Setup, fixture) — 대상 객체와 모의 객체를 만들고, 모의 객체가 어떤 값을 돌려줄지 정한다. 예: 계좌 잔액 조회가 850을 돌려주게 한다.
- 동작 — 테스트하려는 기능을 호출한다.
- 확인 — 실행 결과를 기대와 비교한다. 예: 잔액 조회가 1번, 500 출금이 1번 호출되었는지.
확인 부분이 빠지면 테스트 케이스의 역할을 전혀 못 한다는 점이 강조된다.
넓은 뜻의 모의 객체는 다음 넷을 모두 포함한다.
| 종류 | 하는 일 |
|---|---|
| 더미(Dummy) | 객체 자리만 채운다. 호출되면 정상 동작 없이 예외를 던진다 |
| 스텁(Stub) | 특정 상태를 가정해 정해진 값을 돌려준다 |
| 스파이(Spy) | 협력 객체로 가는 호출을 기록해 두었다가 끝난 뒤 정상 호출 여부를 검사한다 |
| 가짜(Fake) | 실제 협력 객체의 기능을 훨씬 단순하게 구현한다 (실제가 없거나 느릴 때) |
FIRST 원칙
좋은 컴포넌트 테스트의 다섯 조건이다. 각 글자의 이유 까지 같이 외운다.
| 글자 | 원칙 | 이유 |
|---|---|---|
| F | Fast — 빨라야 한다 | 500개 × 0.5초면 4분이 넘는다. 느리면 코드를 바꿀 때마다 안 돌리게 된다. DB·네트워크 의존은 모의 객체로 끊는다 |
| I | Isolated — 서로 독립이어야 한다 | 다른 테스트 결과에 의존하면 실행 순서에 따라 결과가 바뀐다 |
| R | Repeatable — 몇 번 돌려도 같아야 한다 | 랜덤·시간·날짜·공유 DB에 의존하면 결과가 흔들려 신뢰를 잃는다. 개인 샌드박스를 쓴다 |
| S | Self-Validating — 스스로 판정해야 한다 | 사람이 결과를 눈으로 판단하면 느리고 위험하다. 데이터 준비·설정도 자동화한다 |
| T | Timely — 제때 작성해야 한다 | 대상 코드를 작성하는 시점에. TDD라면 코드 작성 바로 전에 |
통합 테스트 — 무엇을 보는가
통합 테스트는 개별적으로 테스트된 컴포넌트를 합쳤을 때 상호 연동이 제대로 되는지 를 본다. 관점이 두 가지다.
- 상호작용에 초점 — 컴포넌트 1의 출력과 컴포넌트 2의 입력 사이 데이터가 누락·변경· 순서 변경 없이 전달되는지 본다. 보낸 쪽과 받은 쪽에 로그를 남겨 비교한다. 로그 공간이 모자라는 임베디드 환경이면 데이터 전체 대신 핑거프린트 (데이터 요약값)만 남겨 변경 여부를 판단한다.
- 기능에 초점 — 실제 컴포넌트 2를 붙인 상태에서 컴포넌트 1과 2의 종합 동작 결과 를 본다. 컴포넌트 테스트에서는 2를 스텁으로 대신했지만 여기서는 실물을 붙인다.
연결 자체에 문제가 생길 가능성이 크면 상호작용 방식을 먼저, 그렇지 않으면 기능 방식을 바로 하는 것이 효과적이다.
통합 전략 — 빅뱅과 점진적 통합
| 전략 | 방법 | 필요한 것 | 장점 | 단점 |
|---|---|---|---|---|
| 빅뱅 | 전체를 한 번에 합친다 | 드라이버 하나 | 준비가 단순 | 오동작 시 어느 컴포넌트가 원인인지 찾기 어렵다 |
| 상향식 | 하위부터 위로 | 드라이버 | 하위(공통 기능) 컴포넌트를 충분히 자주 테스트한다. 스텁 비용이 없다 | 상위(시스템 기능) 결함을 늦게 본다 |
| 하향식 | 상위부터 아래로 | 스텁 | 상위 컴포넌트 결함(설계 문제)을 빨리 발견한다 | 스텁이 많이 필요해, 스텁 구현 비용이 크면 비효율적 |
| 샌드위치 | 상향식 + 하향식 | 드라이버와 스텁 | 두 방식의 장점을 결합 | 계획이 복잡하다 |
점진적 방식은 원인 컴포넌트를 찾기 쉬운 대신 드라이버·스텁을 여러 번 개발해야 한다.
상향식에서는 특정 기능을 맡은 하위 모듈들을 묶어 클러스터(또는 빌드) 로 만들고 드라이버로 테스트한 뒤, 드라이버를 걷어 내고 실제 상위 모듈과 합친다. 하향식에서는 최상위 모듈을 스텁과 함께 테스트하고, 깊이 우선 또는 너비 우선 으로 스텁을 하나씩 실제 모듈로 바꾼다. 바꿀 때마다 시스템이 변했으므로 리그레션 테스트 를 한다.
시험에 그대로 나오는 짝은 "상향식 = 드라이버", "하향식 = 스텁" 이다. 위에서 아래로 내려가면 아직 없는 아래를 스텁으로 채우고, 아래에서 위로 올라가면 아직 없는 위를 드라이버로 채운다고 그림으로 외우면 된다.
시스템 테스트와 인수 테스트
| 시스템 테스트 | 인수 테스트 | |
|---|---|---|
| 시점 | 통합 테스트 완료 후 | 시스템 테스트 완료 후 |
| 기준 | 시스템 명세(요구사항 명세서) | 실제 사용자의 요구와 기대 |
| 주목적 | 명세대로 개발되었는지 검증. 기능뿐 아니라 성능·호환성·사용성·신뢰성·보안성·유지보수성·이식성 같은 비기능 요구도 본다 | 결함 검출이 아니라 인수해도 되는지 고객 입장에서 평가 |
| 수행자(일반적) | 독립 테스트팀(테스터 주도) | 고객·사용자 |
| 케이스 | 테스터가 명세 기반으로 설계 | 사용자·구매자가 제시하거나 시스템 테스트 케이스를 재사용 |
인수 테스트는 두 종류다. 알파 테스트 는 선택된 사용자가 개발자 환경에서 통제된 상태로 한다. 베타 테스트 는 일정 수의 사용자가 실제로 써 보고 피드백을 주며, 보통 개발자가 참여하지 않는다.
생명 주기 모델과 테스트
폭포수 모델 은 요구사항 분석 → 구조 설계 → 상세 설계 → 코딩 → 테스팅 → 운영을 순서대로 진행한다. 테스트 입장에서 좋은 점은 앞 단계 문서가 많이 남아 있어 테스트에 필요한 정보를 얻기 쉽다는 것이다. 나쁜 점은 테스트가 코딩이 끝난 뒤의 한 단계 로만 취급되어, 늦게 발견된 결함(특히 구조 설계 문제)을 고칠 시간과 비용이 부족하다는 것이다. 요구사항이 익숙하고 개발 중 변경이 드문 경우에 맞는다.
V 모델 은 테스트를 개발과 동등하게 놓는다. 왼쪽 개발 단계(SDLC)와 오른쪽 테스트 단계(STLC)가 짝을 이룬다.
요구사항 ─────────────────────── 인수 테스트
요구사항 분석 ─────────────── 시스템 테스트
구조 설계 ─────────────── 통합 테스트
상세 설계 ──────────── 단위(컴포넌트) 테스트
코딩V 모델의 핵심은 테스트 활동이 개발 시작과 동시에 시작된다 는 점이다. 요구사항을 모으는 동안 인수 테스트 계획을 세우고, 요구분석 단계에서 시스템 테스트의 계획·설계와 케이스· 절차를 도출 하며, 구조 설계 단계에서 통합 테스트 계획과 케이스를 도출한다. 실행은 코딩 후에 하지만 계획과 설계는 이미 앞에서 끝나 있다.
예전 판에는 "요구분석 단계에서는 시스템 테스트를 수행 한다"는 문장이 있었는데, 정오표에서 위처럼 "계획·설계와 케이스·절차를 도출한다"로 고쳐졌다. 요구분석 단계에는 실행할 코드가 없으니 수행이 아니라 준비를 하는 것이다. 이 구분이 그대로 시험 보기로 나올 수 있다.
V 모델에서는 동적 테스트 외에 요구사항 명세서·설계서·코드에 대한 정적 테스트(리뷰·정적 분석) 도 함께 한다. 그리고 V는 V&V 를 뜻한다. 단위·통합·시스템 테스트는 각각 상세 설계·구조 설계·시스템 명세를 검증 하고, 인수 테스트는 사용자 의도를 확인 한다.
| 폭포수 | V 모델 |
|---|---|
| 개발 중심. 테스트는 개발 단계 중 하나 | 개발과 테스트를 동등하게 보고 명확히 구분 |
| 모든 테스트 활동이 코딩 후에 | 계획·설계는 개발과 거의 동시에 시작, 실행은 코딩 후 |
진화적 모델 은 요구사항이 처음부터 분명하지 않을 때 쓴다. 핵심 부분을 먼저 만든 뒤 여러 이터레이션(반복 주기)으로 개선한다. 각 이터레이션의 결과물이 고객에게 전달되어 평가받고, 이터레이션마다 테스트 계획을 세워 테스트한다. 요구사항·설계 단계에서는 문서를 대상으로 위험을 분석해 검토 방법을 정하거나 정적 분석을 한다.
나선형 모델 은 진화적 모델의 한 예다. 기술적으로 어렵거나 비즈니스 가치가 큰 요구사항부터 프로토타입을 만들고 테스트와 고객 평가를 거쳐 다음 주기로 간다. 주기마다 위험 분석 을 하는 것이 특징이고, 주기마다 다른 생명 주기 모델을 섞어 쓸 수 있어서 메타 생명 주기 모델 이라고도 불린다.
애자일 모델 은 애자일 선언의 네 가치를 따른다.
- 프로세스나 도구보다 사람과 상호 의사소통
- 포괄적인 문서보다 동작하는 소프트웨어
- 계약 협상보다 고객과의 협력
- 계획을 따르는 것보다 변화에 대응하는 것
반복적이고 점진적인 개발(IID)을 따르고, 이터레이션은 보통 1~4주 다. 각 이터레이션 시작 전에 고객이 비즈니스 가치가 가장 큰 요구사항을 고르고, 이터레이션이 시작되면 요구사항 변경은 받지 않는다. XP(익스트림 프로그래밍)의 실천으로 짝 프로그래밍과 TDD가 있다.
TDD(테스트 주도 개발) 는 실패하는 테스트를 먼저 쓰고 → 그 테스트를 통과하는 코드를 쓰고 → 필요하면 리팩토링(기능을 바꾸지 않고 내부 구조 개선)한 뒤, 더 필요한 테스트가 없으면 종료한다. 테스트되지 않는 코드가 없어지고, 테스트 용이성이 높은 설계가 나오며, 테스트 케이스 자체가 요구사항을 드러낸다. 옛 판 TDD 그림의 마지막 단계에 "종류"라는 오타가 있었고 "종료"로 정정되었다.
지속적 통합(CI) 은 개발자가 코드를 저장소에 반영할 때마다 통합하고, 그때마다 테스트 같은 품질 관리 활동을 바로 수행하는 것이다. 이점은 세 가지로 정리된다 — 통합 지연에 따른 비용 증가를 막고, 결함을 빨리 발견해 비용을 줄이며, 항상 빌드 가능한 버전이 있어 품질에 확신을 준다.
위험 기반 테스트와 모델 기반 테스트
위험 기반 테스트 는 비용과 일정이라는 제약 안에서 테스트 범위를 정하는 방법이다. 범위를 줄이면 비용은 줄지만 테스트하지 않은 곳에서 결함이 날 가능성이 커진다. 그래서 테스트를 안 했을 때의 위험 을 기준으로, 위험이 낮은 것은 빼고 높은 것은 반드시 넣는다. 피처의 위험 분석을 바탕으로 계획·설계·실행을 수행하는 것이 위험 기반 테스트다.
모델 기반 테스트 는 테스트 절차를 자동으로 뽑아낼 수 있을 만큼 정형화되고 상세한 모델 (결정표, UML 상태 다이어그램, UML 액티비티 다이어그램 등)을 바탕으로 계획·케이스· 절차·입력·예상 결과를 정한다. 장점은 계획에서 종료까지 대부분을 자동화 할 수 있고, 모델 자체의 문제를 일찍 찾아 개발 산출물의 결함을 잡을 수 있다는 것이다. 그래서 자동차·의료 같은 안전 필수(Safety critical) 소프트웨어 에 쓰인다. 단점은 정형적이고 상세한 모델을 만드는 추가 비용 이다.
실무에서 왜 필요한가
드라이버와 스텁은 이름만 낯설 뿐 실무에서 매일 쓴다. 프런트엔드에서 백엔드 API가 아직 없을 때 쓰는 가짜 응답 서버가 스텁이고, 백엔드 함수를 직접 호출하는 테스트 코드가 드라이버다. 이 대역이 "실물과 얼마나 다른가"를 늘 의식해야 한다. 스텁이 항상 성공만 돌려주면 실패 처리 코드는 한 번도 실행되지 않은 채 통과한다.
통합 전략은 일정 회의에서 쓸모가 있다. 결제 모듈이 늦게 나오는 프로젝트라면 하향식으로 화면과 상위 흐름부터 붙이고 결제는 스텁으로 두는 편이 낫다. 반대로 공통 라이브러리가 불안하면 상향식으로 하위부터 다지는 편이 낫다. "다 되면 한꺼번에 붙이자"는 빅뱅 제안이 나오면, 원인 컴포넌트를 찾기 어렵다는 근거로 단계적 통합을 제안할 수 있다.
V 모델의 "계획·설계는 앞에서" 원칙은 애자일 팀에서도 유효하다. 스토리 리파인먼트(다음 스프린트 작업을 구체화하는 회의) 때 인수 기준과 테스트 아이디어를 먼저 쓰는 것은 V 모델의 왼쪽 팔에서 테스트를 준비하는 것과 같은 발상이다.
채용시장에서의 위치
공공 SI·금융 차세대 사업은 여전히 V 모델 기반의 단계별 테스트(단위·통합·시스템·인수)와 그 산출물을 요구한다. 반면 스타트업과 플랫폼 기업은 애자일·CI 환경을 전제로 한다. 신입 지원자가 두 세계를 모두 설명할 수 있으면 지원 범위가 넓어진다. 면접에서 "애자일에서는 테스트 계획서를 안 쓰나요?"라는 질문을 받으면, "문서의 형태가 가벼워질 뿐 이터레이션마다 무엇을 어디까지 테스트할지 정하는 계획 활동은 있다"고 답하는 것이 정확하다.