동등분할로 설계하는 효율적인 테스트 케이스
동등분할의 유효·무효 클래스 설계, 대표값 선택, 경계값 분석과 페어와이즈 결합 방식을 정리한다.
2026-08-14 · 최초 발행 2025-12-21
입력값 전체를 테스트하지 않고도 검증 범위를 확보하는 법
입력 도메인이 커질수록 모든 조합을 직접 확인하는 방식은 조합 폭발에 부딪힌다. 동등분할(Equivalence Partitioning)은 동일한 처리 결과를 낼 것으로 보는 입력값 집합을 동등 클래스(Equivalence Class)로 나누고, 각 집합에서 대표 테스트를 뽑는 방법이다.
클래스는 유효 클래스(Valid)와 무효 클래스(Invalid)로 구분한다. 정수 범위, 카테고리형 값, 정규식 같은 규칙 기반 패턴, 상태 기반 입력이 분할 기준이 될 수 있다. 동등분할은 클래스 중심의 대표값을 선택하고, 경계값 분석(Boundary Value Analysis)은 경계에 가까운 결함 민감 영역을 보완한다.
분할 기준은 요구사항에서 시작한다
먼저 입력 변수를 식별하고 타입, 단위, 허용 범위, 사전조건을 명세화한다. 비즈니스 규칙과 예외 규칙을 테스트 가능한 단위로 나눠야 클래스 경계가 흔들리지 않는다.
정상 시나리오와 실패 시나리오는 대칭적으로 분할한다. 이때 클래스 사이의 중복, 누락, 겹침(Overlap)을 점검하는 규칙도 필요하다. 대표값은 클래스 내부값, 경계 내부값, 전형 사례에서 선택하며, 무효 클래스는 단일 오류만 일으키는 원인 중심의 값을 고른다.
다변수 입력에서는 페어와이즈(Pairwise), 위험 기반 가중치, 동치 클래스 결합 규칙으로 조합을 축소할 수 있다. 다만 사업 규칙상 반드시 검증해야 하는 교차 조합은 화이트리스트로 따로 관리한다.
결함이 발견되면 해당 결함을 클래스에 매핑하고 분할 규칙을 보정한다. 요구사항이 바뀔 때도 클래스 사양을 먼저 갱신하면 테스트 자산의 일관성을 유지할 수 있다.
요구사항에서 실행 결과까지 이어지는 흐름
요구사항을 해석해 입력 변수와 제약을 추출한 뒤, 변수별로 유효·무효 클래스를 나눈다. 이후 내부값·경계값·무효값을 대표값으로 정하고, 필요한 경우 조합 축소를 적용해 테스트 케이스를 만든다.
요구사항이 모호하면 명확화를 요청하고 임시 가설을 명시한다. 클래스가 중복되거나 빠진 경우에는 검토 체크리스트로 정적 검증을 수행한다. 조합이 급격히 늘어나면 축소 전략과 필수 시나리오 화이트리스트를 함께 사용한다.
경계값 분석과 함께 사용할 때의 역할
동등분할은 최소한의 대표 케이스를 구성하는 데 적합하고, 경계값 분석은 임계 영역의 결함을 확인하는 데 강점이 있다. 고위험 비즈니스 규칙은 결정 테이블이나 상태 전이 테스트와 결합할 수 있다.
| 기법 | 성능(결함 검출 효율) | 확장성(변수 증가 대응) | 일관성(재현 가능성) | 안정성(리스크 커버리지) | 운영 편의 |
|---|---|---|---|---|---|
| 무작위 테스트 | 저 | 상 | 저 | 저 | 상 |
| 동등분할 | 중~상 | 상 | 상 | 중~상 | 상 |
| 경계값 분석 | 상(경계 결함) | 중 | 상 | 상(임계 리스크) | 중 |
입력 규칙에 맞춰 클래스를 나누는 사례
금융 결제와 이체
금액 입력 범위는 1~10,000,000으로 분할하고, 통화 코드 유효성과 일 한도 규칙도 별도 기준으로 나눌 수 있다. 대표값으로 내부값 10,000, 경계값 1과 10,000,000, 무효값 0·음수·알파벳을 선택한다.
전자상거래 주문
수량, 회원 등급, 쿠폰 유형, 재고 상태를 분할 대상으로 삼는다. 핵심 상호작용은 페어와이즈로 다루고, 고가 상품이나 복합 쿠폰은 화이트리스트에 넣어 반드시 포함한다.
공공 API 입력 검증
쿼리 파라미터의 타입, 필수 여부, YYYY-MM-DD 패턴을 클래스 기준으로 나눈다. 무효 클래스별 HTTP 상태 코드 400, 401, 422를 검증해 오류 처리의 일관성을 확인한다.
임베디드와 IoT 센서
측정값 범위, 이상치, 결측(NaN)을 분할한다. 경계값과 이력 기반 히스테리시스 구간은 별도 클래스로 두어 노이즈 민감도를 낮춘다.
Python으로 대표값을 명시하는 예
환경은 Python 3.10+와 pytest 옵션이며, 나이(age)는 0~120 정수만 유효하다고 전제한다.
def partitions_age():
valid = {"range": (0, 120)}
invalid = [
{"lt_min": -1},
{"gt_max": 121},
{"not_int": "abc"},
]
return valid, invalid
def representatives():
valid, invalid = partitions_age()
v_min, v_max = valid["range"]
reps = {
"valid": [v_min + 1, (v_min + v_max)//2, v_max - 1], # 내부 대표값
"boundary": [v_min, v_max], # 경계값 분석 보강
"invalid": [-1, 121, "abc"], # 무효 대표값
}
return reps
def is_valid_age(x):
return isinstance(x, int) and 0 <= x <= 120
def test_age_partitions():
reps = representatives()
for v in reps["valid"] + reps["boundary"]:
assert is_valid_age(v)
for iv in reps["invalid"]:
assert not is_valid_age(iv)
이 예시는 클래스별 대표값을 명시적으로 선택하고, 경계값을 보강하며, 무효 클래스를 분리해 검증한다.
테스트 자산을 줄이고 추적성을 남긴다
동등분할을 적용하면 조합 폭발에 비해 테스트 케이스 수를 60~90% 줄이면서 핵심 커버리지를 유지할 수 있다. 무효 클래스와 경계 영역을 함께 보강하면 입력 검증 결함을 더 이르게 찾는 데 도움이 된다.
요구사항 변경 시에는 클래스 정의를 갱신하는 방식으로 케이스를 일괄 재생성할 수 있다. 결함을 클래스 단위로 추적하면 같은 원인이 다시 발생하는 것을 막는 체계도 강화된다.