요구사항부터 코드까지 이어지는 단계별 테스트 대응 체계
요구사항·설계·코드 산출물을 테스트와 연결하는 단계별 테스트 대응 체계, 추적성 매트릭스와 품질 게이트 운영 방법을 정리한다.
2026-08-14 · 최초 발행 2025-12-21
테스트는 마지막 검증이 아니라 각 산출물의 대응물이다
대규모 소프트웨어에서 품질을 내재화하려면 테스트를 개발 말미의 검증 활동으로 둘 수 없다. 요구사항, 설계, 코드 각각에 맞는 테스트 산출물과 게이트를 정렬해야 한다. 이 체계의 목적은 추적성을 확보하고 결함을 일찍 발견하며 재작업을 줄이는 데 있다.
단계별 테스트 대응(Stage-wise Testing Alignment)은 요구사항·설계·코드 산출물에 대응하는 테스트 목적, 범위, 기준을 사전에 정의하고 이를 품질 게이트로 묶는 운영 방식이다. 요구사항 검증(Validation)은 설계와 코드 검증(Verification)과 구분하되, 추적성 매트릭스를 통해 서로 연결한다.
RTM(Requirements Traceability Matrix)은 REQ-ID ↔ TEST-ID ↔ DEFECT-ID를 1:다 관계로 연결하는 기준 문서이자 자동화 규칙이다. 변경 영향 분석과 커버리지 계량화는 이 연결을 바탕으로 이뤄진다.
테스트 피라미드도 단계별로 역할을 나눈다. 상위 단계에는 시나리오와 계약 중심의 검증을, 하위 단계에는 단위 테스트와 정적 분석을 배치한다. E2E 비중은 최소화하고 계약·통합·단위 테스트 자동화 비중을 높인다.
추적성과 게이트가 단계 사이를 연결하는 방식
요구사항 베이스라인의 버전과 상태를 관리하고 TEST-ID 네이밍 규칙을 정한다. 커밋 메시지와 pytest markers, JUnit tags 같은 테스트 메타데이터에 REQ-ID를 연결하면, CI는 변경된 REQ-ID에 영향을 받는 테스트를 선택해 실행할 수 있다.
품질 게이트에는 Definition of Ready/Done, 테스트 통과 기준, 결함 임계치를 둔다. 정적 분석, 커버리지, 취약점 스캔, 성능 기준도 게이트 조건으로 선언한다. 실패한 경우에는 단계별 되돌림 경로와 결함 분류 체계를 적용한다.
요구사항 단계에서는 수용 기준(AC)과 시나리오 테스트를 설계하고 목업 또는 시뮬레이터를 준비한다. 설계 단계에서는 디자인 리뷰 체크리스트, 계약 테스트(Pact/OpenAPI), 데이터 모델 검증을 다룬다. 코드 단계에서는 단위·통합·회귀 테스트와 정적·동적 분석을 수행하고, 일부에는 프로퍼티 기반 테스트를 도입한다.
결함은 요구사항·설계·코드 중 어느 단계에서 비롯됐는지 분류한다. 재현 스텝, 가설, 수정 가이드도 같은 기준으로 표준화한다. MTTR을 줄이고 누적된 플래키 테스트를 제거하는 주기를 운영하는 일도 여기에 포함된다.
요구사항 커버리지, 변경 리드타임, 게이트 통과율, 결함 누수율은 지표로 관리한다. 대시보드에서는 이를 실시간으로 보이고 릴리스 승인 기준과 연결하며, 팀과 서비스별 벤치마크 및 목표 관리(OKR)에도 활용한다.
게이트 실패는 결함 기원으로 되돌린다
입력은 요구사항 또는 변경 요청(REQ-ID), AC 초안, 아키텍처 방향성이다. 테스트 설계부터 계약·단위·통합 자동화, CI 게이트 평가까지를 처리하고, 게이트 통과 결과와 릴리스 후보, 결함 및 원인 분류를 출력한다. 게이트가 실패하면 결함 기원별 되돌림 경로를 선택하며, 플래키 테스트는 격리 큐로 이동한다.
테스트 수준마다 검증할 지표가 다르다
| 테스트 레벨 | 성능 | 확장성 | 일관성 | 안정성 | 운영 편의 |
|---|---|---|---|---|---|
| 수용 기준(AC)/시나리오 | 중간 | 낮음 | 높음 | 중간 | 중간 |
| 설계 리뷰/계약 테스트 | 낮음 | 중간 | 높음 | 높음 | 중간 |
| 단위 테스트 | 낮음 | 낮음 | 중간 | 높음 | 높음 |
| 통합 테스트 | 중간 | 중간 | 중간 | 중간 | 중간 |
| E2E/시스템 | 높음 | 중간 | 높음 | 중간 | 낮음 |
| 정적 분석/보안 스캔 | 낮음 | 낮음 | 높음 | 높음 | 높음 |
일관성과 안정성은 계약 테스트, 정적 분석, 단위 테스트에서 비용 대비 효과가 우수하다. 반대로 성능과 확장성은 시스템 또는 통합 수준에서 검증해야 한다. 운영 편의는 단위 테스트와 정적 분석 자동화가 제공하는 결함 예방 효과와 연결된다.
제품 개발과 현대화 작업에서의 적용
대규모 제품 개발에서는 Jira(REQ-ID), Confluence, Git, GitHub Actions/Jenkins를 하나의 툴체인으로 정렬할 수 있다. 브랜치와 커밋 메시지에 REQ-ID를 포함하고 테스트 태깅으로 자동 추적 규칙을 적용한다. 최소 라인·분기 커버리지, 주요 모듈 임계 커버리지, 정적 분석 임계치를 정하며 계약 테스트가 성공하지 않으면 통합 및 배포 단계로 진입하지 못하게 한다.
금융권 레거시 현대화에서는 OpenAPI 스키마로 계약을 고정하고 Consumer-Driven Contract로 변경 영향을 제어한다. 스키마 호환성 체크는 릴리스 게이트로 쓴다. 데이터 정합성은 마이그레이션 전후의 제약조건과 계수 합 일치 여부로 검증하고, 샘플링+전수 혼합과 실패 시 롤백 트랜잭션 시나리오 리허설을 적용한다.
데이터 파이프라인과 ML에서는 Null 비율, 분포 드리프트 같은 데이터 품질 규칙을 AC로 정의한다. 스키마 검증과 샘플 기반 시나리오 테스트를 자동화하고, 설계 검토에는 파티션·락 전략 체크리스트를 둔다. 코드 단계에서는 재현 가능한 시드와 고정 피처 엔지니어링 테스트를 다룬다.
배송비 요구사항을 테스트 산출물로 연결하기
전제조건
- 환경: Python 3.11+, pytest 7+
- 설치: pip install pytest
요구사항 REQ-001: “지역별 배송비 계산. 기본료 + 무게당 추가료. 지역: KR, US. 무게 단위 kg, 0 이상 실수. 잘못된 지역/음수 무게 입력 시 오류.”
수용 기준은 요구사항이 말하는 동작을 시나리오로 드러낸다.
Feature: Shipping fee calculation (REQ-001)
Scenario: KR 1.0kg
Given region "KR" and weight 1.0
Then fee is 3000
Scenario: KR 2.5kg
Given region "KR" and weight 2.5
Then fee is 4500
Scenario: US 1.0kg
Given region "US" and weight 1.0
Then fee is 10000
Scenario: Invalid region
Given region "EU" and weight 1.0
Then error occurs
수용 기준을 pytest로 옮기면 REQ-001에 대응하는 테스트 식별자가 남는다.
# tests/test_shipping_acceptance.py
# REQ-001 AC: 수용 기준 시나리오
import pytest
from shipping import calc_shipping_fee
@pytest.mark.parametrize("case_id, region, weight, expected", [
("REQ-001-AC1", "KR", 1.0, 3000),
("REQ-001-AC2", "KR", 2.5, 4500), # 3000 + 1.5kg*1000
("REQ-001-AC3", "US", 1.0, 10000),
])
def test_acceptance_scenarios(case_id, region, weight, expected):
assert calc_shipping_fee(region, weight) == expected, case_id
def test_acceptance_invalid_region():
with pytest.raises(ValueError):
calc_shipping_fee("EU", 1.0)
def test_acceptance_negative_weight():
with pytest.raises(ValueError):
calc_shipping_fee("KR", -0.1)
계약 테스트는 인터페이스와 유효성 규칙을 검증한다.
# tests/test_shipping_contract.py
# REQ-001 계약 테스트: 인터페이스/유효성 계약
import pytest
from shipping import calc_shipping_fee
@pytest.mark.parametrize("region", ["kr", "Kr", "KR"])
def test_region_is_case_insensitive(region):
assert calc_shipping_fee(region, 1.0) == 3000 # 계약: 지역 코드 대소문자 무시
@pytest.mark.parametrize("bad", ["", None, 123])
def test_contract_rejects_non_string_region(bad):
with pytest.raises((TypeError, ValueError)):
calc_shipping_fee(bad, 1.0)
@pytest.mark.parametrize("w", [0.0, 0.001])
def test_weight_boundary_non_negative(w):
assert isinstance(calc_shipping_fee("KR", w), int)
구현은 설계에서 정한 지역별 요금, 입력 검증, 반올림 규칙을 따른다.
# shipping.py
# 설계 요약:
# - 규칙: KR(기본 3000, kg당 1000), US(기본 10000, kg당 2000)
# - 무게는 올림 없이 연속값 적용, 수수료는 원단위 반올림
# - 지역 코드 대소문자 무시, 미지원 지역/음수 무게는 ValueError
from typing import Dict
RATES: Dict[str, dict] = {
"KR": {"base": 3000, "per_kg": 1000},
"US": {"base": 10000, "per_kg": 2000},
}
def calc_shipping_fee(region: str, weight_kg: float) -> int:
# 계약 검증
if not isinstance(region, str):
raise TypeError("region must be str")
if weight_kg is None or weight_kg < 0:
raise ValueError("weight must be non-negative")
region_up = region.upper()
if region_up not in RATES:
raise ValueError(f"unsupported region: {region}")
rate = RATES[region_up]
fee = rate["base"] + (rate["per_kg"] * float(weight_kg))
return int(round(fee))
요구사항 단계에서는 AC를 시나리오로 명시하고 테스트 ID에 REQ-001 접두사를 부여한다. 설계 단계에서는 인터페이스와 유효성 계약, 즉 지역 코드 대소문자 무시와 타입·경계를 정의하고 테스트한다. 코드 단계에서는 구현과 단위·계약·수용 테스트를 CI에서 병렬 실행해 게이트의 통과 또는 차단 기준으로 사용한다.
테스트 메타데이터는 pytest -m "req_001" 같은 태그로 영향도 기반 선택 실행에 활용할 수 있다. 커버리지는 REQ별 라인·분기·조건 커버리지를 집계하고, 미달하면 게이트를 차단한다. 실패 테스트에는 REQ-ID를 자동 주입하고 원인을 요구사항·설계·코드로 분류한다.
운영에서 확인할 변화
단계별 테스트 대응은 결함 누수율을 3060% 감소시키고, 변경 리드타임을 1535% 단축하며, 재작업 감소에 따른 비용 절감으로 이어진다. E2E 비중을 축소하면 테스트 실행 시간도 40% 이상 단축할 수 있다.
요구사항·테스트·코드 간 추적성이 강화되면 변경에 대한 부담이 줄어든다. 품질 게이트를 투명하게 운영하면 릴리스 신뢰도가 높아지고 협업 비용도 감소한다. 신규 프로젝트와 기존 프로젝트 모두 RTM 도입, 계약 테스트 정착, 게이트 기준치 정의부터 단계적으로 적용할 수 있다.