경계값 분석으로 입력 오류를 겨냥하는 테스트 설계
경계값 분석(BVA)의 경계 선택, 케이스 구성, 자동화와 API·DB·시간 처리 테스트 적용 방식을 정리한다.
2026-08-14 · 최초 발행 2025-12-17
오류가 몰리는 입력 경계부터 검증한다
경계값 분석(Boundary Value Analysis, BVA)은 입력 도메인의 경계에서 오류가 자주 발생한다는 경험 법칙을 활용하는 테스트 설계 기법이다. 오프바이원, 비교 연산, 형 변환, 반올림 문제는 정상 범위 한가운데보다 허용 범위의 시작과 끝에서 드러나기 쉽다.
동등 분할(Equivalence Partitioning, EP)이 유효·무효 입력을 여러 클래스로 나누는 작업이라면, BVA는 각 클래스의 경계 근방에서 대표값을 고른다. 두 기법을 함께 쓰면 테스트 케이스를 과도하게 늘리지 않으면서 경계 결함을 집중적으로 확인할 수 있다.
한 번에 한 변수의 경계를 확인하는 Each-Variable 방식을 우선 적용한다. 기본 경계는 최소값(min), 최소값+1, 최대값-1, 최대값(max)이며, 범위 밖 처리까지 보려면 min-1과 max+1을 더한 강건형을 사용한다. 변수 n개를 모두 조합하면 케이스가 급증하므로 기본형은 4n+1, 강건형은 6n+1 방식으로 구성하고, 의존 관계가 있는 변수만 부분 결합 테스트로 보완한다.
허용 범위의 안쪽과 바깥쪽을 함께 잡는 방법
기본형은 min, min+1, max-1, max에 초점을 둔다. ≤, <, ≥, > 같은 비교 연산자의 조건이 경계에서 의도대로 작동하는지 확인하는 데 적합하다.
강건형에서는 min-1과 max+1도 포함한다. 유효 범위를 벗어난 값이 들어왔을 때 밸리데이션과 예외 처리가 제대로 이어지는지 검증할 수 있다.
경계는 정수 범위에만 존재하지 않는다. 문자열은 길이 제한, 날짜는 말일과 윤년, 금액은 소수 자리의 반올림, 컬렉션은 원소 수가 각각 경계가 된다. 도메인에 맞는 경계 정의가 먼저 정리되어야 테스트 데이터도 일관성을 갖는다.
Each-Variable 방식에서는 다른 변수들을 명목값(typical)으로 고정한 채 한 변수의 경계만 움직인다. Worst-Case 방식은 선택한 경계값을 부분적으로 조합해 변수 상호작용에 따른 결함을 찾는다. 이때 입력 유효성 실패, 예외 메시지·코드, 롤백과 로깅까지 오라클로 정의해야 결과 판정이 흔들리지 않는다.
데이터 주도 테스트에서는 경계 집합을 테이블로 만들고 케이스를 생성한다. Property-based 테스트는 경계 근방을 우선 샘플링하도록 전략을 정하고 shrinking으로 실패 입력을 재현한다. CI에서는 변경 영향 범위에 해당하는 테스트 데이터만 다시 생성하는 파이프라인을 구성할 수 있다.
요구사항의 범위·포함·배제 제약과 테스트 케이스, 실행 결과는 양방향으로 추적할 필요가 있다. 반올림, 포맷, 시간대처럼 해석 차이가 생길 수 있는 항목은 허용 오차와 메시지 규격을 미리 정한다. 경계 실패가 발생했을 때는 입력, 파싱, 검증 단계의 로그를 분리해 원인 분석(RCA)에 활용한다.
입력 도메인에서 실행 결과까지 이어지는 흐름
경계 규칙이 자주 나타나는 운영 영역
API 입력 검증에서는 연령 파라미터가 0~120(포함)일 때 -1, 0, 1, 119, 120, 121을 검증한다. 정상 200과 오류 400/422 응답을 구분하고 메시지 규격도 확인한다. 페이지네이션은 page≥1, size∈[1,100] 조건에 따라 size=0, 1, 100, 101과 page=0을 검증하며, 디폴트 및 상한 클램핑 동작을 확인한다.
데이터베이스와 스키마에서는 VARCHAR(255)에 길이 254, 255, 256을 넣고 멀티바이트·정규화(NFC) 이슈까지 포함한다. DECIMAL(10,2)은 99999999.99와 100000000.00을 기준으로 반올림·올림·버림 정책을 검증한다.
시간·달력·배치 처리에서는 "2월 28/29일", "말일+1일", UTC 자정 같은 시간대 경계가 중요하다. 배치 윈도우는 시작 포함·종료 배제 규칙을 명확히 하고, 재시도 시 중복 처리가 없는지도 확인한다.
성능과 제한 정책에서도 경계는 명확하다. 레이트 리밋 100req/min은 99→100→101 시나리오와 윈도우 리셋 직후 동작을 검사한다. 큐 길이, 연결 수, 동시 세션 상한에서는 리소스 해제와 오류 메시지의 일관성을 함께 검증한다.
테스트 케이스를 줄이면서 지키는 범위
무작위 테스트와 비교하면 60~80% 케이스로 동등한 결함 검출이 가능하다(경험적 수치, 조직별 편차). 변수 n개일 때 기본형은 4n+1, 강건형은 6n+1로 케이스를 관리한다. 예를 들어 n=3이면 13/19 케이스로 핵심 리스크를 커버한다.
오프바이원과 비교 연산 오류의 검출율은 유의하게 향상되며, 초기 스프린트 내 발견 비율도 증가한다. 포함·배제, 반올림, 예외 규칙을 테스트 설계 과정에서 명료하게 만들 수 있고, 고정된 경계 회귀 스위트는 배포 리스크를 낮춘다. 경계 규칙과 오라클을 템플릿으로 관리하면 팀 간 커뮤니케이션도 일관성을 갖는다.
| 방법 | 성능(케이스 효율) | 확장성(변수 증가) | 일관성(규칙성) | 결함 검출(경계/오프바이원) | 운영 편의 |
|---|---|---|---|---|---|
| 경계값 분석(BVA) | 높음 | 높음(4n+1/6n+1) | 높음 | 매우 높음 | 높음 |
| 동등 분할(EP) | 매우 높음 | 매우 높음 | 높음 | 중간(경계 미포함 시 약함) | 매우 높음 |
| 결정 테이블 | 중간 | 중간(규칙수↑) | 높음 | 중간~높음 | 중간 |
| 무작위/탐색 | 낮음 | 높음 | 낮음 | 낮음~중간(운) | 높음(초기) |
EP로 범위를 정의한 뒤 BVA로 경계를 강화하고, 규칙 간 교차 조건이 필요하면 결정 테이블을 추가하는 방식이 적합하다.
pytest와 Hypothesis로 경계를 고정한다
전제조건:
- Python 3.10+, pytest >=7, hypothesis >=6
경계 파라미터화 예시(pytest):
# test_age_boundary.py
import pytest
def is_valid_age(age: int) -> bool:
return 0 <= age <= 120
@pytest.mark.parametrize("age, expected", [
(-1, False), (0, True), (1, True), (119, True), (120, True), (121, False),
])
def test_age_boundary(age, expected):
assert is_valid_age(age) is expected
Property-based로 경계 근접 샘플 강화:
# test_age_property.py
from hypothesis import given, strategies as st
# 경계 주변을 우선 포함하는 전략
edge_values = st.sampled_from([-1, 0, 1, 119, 120, 121])
nearby = st.integers(min_value=-10, max_value=130)
@given(st.one_of(edge_values, nearby))
def test_age_property(v):
def is_valid_age(age: int) -> bool:
return 0 <= age <= 120
assert is_valid_age(v) == (0 <= v <= 120)
데이터 주도 입력(문자열 길이 경계):
# test_username_len.py
import pytest
MAX = 255
@pytest.mark.parametrize("s, ok", [
("a" * (MAX-1), True),
("a" * MAX, True),
("a" * (MAX+1), False),
])
def test_username_len_boundary(s, ok):
assert (len(s) <= MAX) is ok
경계값 분석은 동등 분할과 결합할 때 최소 비용으로 결함을 포착하는 데 유용하다. 단일 변수 중심의 4n+1/6n+1에서 시작하고, 시간·금액·문자열·리소스 상한 같은 도메인 특성에 맞춰 강건성 테스트와 부분 조합을 추가한다. 데이터 주도 테스트와 Property 기반 자동화를 CI에 연결하면 추적 가능하고 반복 가능한 경계 스위트를 운영할 수 있다.