경로 테스트로 분기와 예외 흐름 검증하기

제어 흐름 그래프와 사이클로매틱 복잡도를 활용해 기저 경로를 설계하고, 분기·루프·예외 흐름을 검증하는 화이트박스 테스트 방법

2026-08-14 · 최초 발행 2025-12-22

제어 흐름에서 테스트 대상을 고르는 방법

경로 테스트(Path Testing)는 프로그램의 제어 흐름 그래프(Control Flow Graph, CFG)에서 선형 독립 경로 집합을 선택하고, 각 경로가 실제로 실행되는지 확인하는 화이트박스 테스트 기법이다. 조건 분기, 반복문, 예외 처리를 명시적으로 다루므로 복잡한 흐름에서 빠지기 쉬운 결함을 추적하는 데 적합하다.

이 기법의 중심에는 McCabe의 사이클로매틱 복잡도 V(G)가 있다. V(G)로 기저 경로(basis paths)의 수를 산정하고, 그에 맞는 독립 테스트 케이스를 설계해 분기 구조를 검증한다.

모든 가능한 경로를 실행하는 완전 경로 커버리지는 루프가 포함되면 현실적으로 어려워진다. 반면 기저 경로 테스트는 선형 독립 경로에 범위를 한정해 실무에서 관리 가능한 대안을 제공한다.

CFG와 복잡도가 보여주는 테스트 범위

CFG에서 노드는 분기 없이 연속 실행되는 기본 블록을 뜻하고, 에지는 Branch, Jump, Call/Return 같은 제어 이전을 나타낸다. 조건문과 예외, 루프를 그래프로 옮기면 경로의 수와 결합 복잡도를 확인하기 쉬워진다.

사이클로매틱 복잡도는 다음과 같이 계산한다.

V(G) = E - N + 2P

여기서 E는 에지 수, N은 노드 수, P는 연결된 컴포넌트 수다. 일반적인 단일 함수 또는 단일 진입점에서는 P=1을 적용한다. V(G)는 필요한 최소 기저 경로 수와 같으며, 분기가 늘어날수록 선형적으로 증가하므로 테스트 비용을 예측하는 기준이 된다.

기저 경로를 만들 때는 독립 에지와 루프를 포함하도록 경로를 조합하고, 각 경로별 입력값·사전조건·기대 결과를 정의한다. 예외 및 오류 경로를 빼지 말아야 하며, 데이터 의존성도 함께 살펴야 한다. 외부 I/O나 시간 의존 로직은 Stub/Mock으로 격리한다.

루프는 무한한 경로 확장을 피하기 위해 0·1·2회 반복을 중심으로 대표 경로를 고른다. 최대 또는 상한 조건, 조기 종료도 포함한다. 분기 경계 조건을 기준으로 입력을 설계해 양쪽 분기를 모두 만족시키되, 불필요하게 많은 데이터를 만들지 않는 방식이 필요하다.

정적 분석에는 CFG와 복잡도 분석 도구인 radon, SonarQube, ESLint plugins, CodeQL을 활용할 수 있다. 동적 검증에서는 coverage.py/pytest-cov, JaCoCo, Istanbul/nyc로 커버리지와 트레이스를 수집하고, CI 파이프라인의 경로 기준 품질 게이트와 연결한다.

기저 경로를 테스트 케이스로 바꾸는 흐름

대상은 함수나 모듈 단위로 나누고 외부 시스템 호출은 Mock 처리한다. 시간, 랜덤, 네트워크처럼 비결정적인 요소는 제어할 장치를 먼저 마련한다.

그다음 CFG를 도구로 추출하거나 기본 블록과 분기를 도식화하고, V(G)를 계산해 최소 케이스 수를 정한다. 각 분기와 독립 루프 에지를 지나는 경로를 식별하면서 예외·오류 경로도 포함한다.

입력값은 경계값과 동치분할 기준으로 정하고, 사전조건·모의 객체·고정 시드를 함께 명세한다. 테스트 러너와 CI에서 실행한 뒤 경로 및 분기 커버리지 리포트를 확인한다. 실패가 발생하면 트레이스와 분기 조건을 다시 검토한다. 남은 미커버 경로에는 케이스를 추가하고, 코드가 바뀔 때는 기저 경로 영향도를 확인하며 회귀 테스트를 유지한다.

아니오요구사항·대상 코드 입력제어 흐름 그래프 작성(CFG)사이클로매틱 복잡도 계산'V(G)=E-N+2P'기저 경로 집합 도출테스트 데이터 설계·모의 구성자동화 실행·커버리지 수집(CI)조건: 경로 미커버 존재?종료: 경로 커버리지 기준 충족

예외 경로가 중요한 시스템에서의 적용

결제 승인 플로우에서는 한도 초과, 도난 카드, 3DS 인증 실패, 재시도와 폴백 경로를 테스트 대상에 넣는다. 승인 실패 원인의 오탐·미탐을 줄이고 예외 경로의 신뢰성을 높이는 데 연결된다.

마이크로서비스의 회로차단기와 재시도 정책에서는 타임아웃, 백오프, 폴백 리소스 경로가 주요 대상이다. 장애 상황의 복구 경로를 자동 검증하고 SLO 준수율을 높이는 데 활용할 수 있다.

데이터 파이프라인에서는 스키마 검증 실패, 지연 데이터, 데드레터 큐 이동처럼 흐름이 갈리는 지점을 다룬다. 손실과 중복 처리를 막고 운영 경보의 품질을 개선하는 목적이다.

DB 트랜잭션 경계에서는 커밋·롤백, 고유키 충돌, 락 타임아웃을 경로로 설계한다. ACID 유지 여부와 보정 작업 경로의 안정성을 검증할 수 있다.

커버리지 기법을 비교할 때의 관점

기법 성능(실행/분석 비용) 확장성(대규모 코드) 일관성(재현/지표 안정성) 안정성(변경 영향 민감도) 운영 편의(도구/적용 난이도)
Statement Coverage 높음(낮은 비용) 높음 중간 중간 높음
Branch Coverage 중간 중간 중간~높음 중간 중간~높음
Path Testing(기저 경로) 중간~낮음(케이스 수 증가) 중간(경로 폭증 위험) 높음(분기 기준 정합) 높음(코드 변경에 민감) 중간(도구+절차 필요)

주: “성능 높음”은 비용 낮음 의미로 사용

파이썬 함수의 기저 경로 테스트

전제조건:

  • Python 3.11+
  • pytest 7.5+, coverage.py 선택
  • 선택: radon(복잡도 계산)

대상 함수 calc_discount는 예외, 회원 분기, 쿠폰 분기를 포함하므로 V(G) ≈ 4로 가정한다.

# file: discount.py
def calc_discount(amount: float, is_member: bool, coupon: str | None = None) -> float:
    if amount <= 0:
        raise ValueError("amount must be positive")
    base = amount * 0.9 if is_member else amount
    if coupon == "BLACK":
        return base * 0.8
    return base

각 독립 경로를 검증하는 테스트는 다음과 같다.

# file: test_discount.py
import pytest
from discount import calc_discount

def test_invalid_amount_raises():
    with pytest.raises(ValueError):
        calc_discount(0, True)

def test_member_with_black_coupon():
    assert calc_discount(100, True, "BLACK") == 72.0  # 100*0.9*0.8

def test_member_without_coupon():
    assert calc_discount(100, True, None) == 90.0

def test_nonmember_with_black_coupon():
    assert calc_discount(100, False, "BLACK") == 80.0

# 선택: 필요 시 의존 경로 축소를 위해 아래 케이스 중 하나만 유지(선형 독립성 기준)
def test_nonmember_without_coupon():
    assert calc_discount(100, False, None) == 100.0

실행 명령은 다음과 같다.

  • 단위 테스트: pytest -q
  • 커버리지: pytest --cov=discount -q
  • 복잡도: pip install radon && radon cc -s -a discount.py

루프는 반복 0·1·2회와 조기 종료·상한 도달 케이스를 포함해 확장한다. 예외는 입력 검증 실패, Mock으로 재현하는 외부 의존 오류, 타임아웃을 분리해 다룬다.

경로 폭증과 유지보수 비용을 통제하기

경로 테스트를 우선 적용할 단위는 함수이며, 외부 I/O와 시간 의존성은 Mock 처리한다. 경계값과 예외 경로를 반드시 포함하고, CI 품질 게이트에는 분기 커버리지와 결함 밀도를 함께 설정한다. V(G) > 10인 함수는 분기 분해와 가드 절 추가를 통한 리팩터링 대상으로 볼 수 있다.

완전 경로를 목표로 삼으면 경로 폭증 문제가 발생한다. 기저 경로를 기본 전략으로 두고 위험 기반으로 추가 경로를 선택하는 편이 현실적이다. 코드 변경 후에는 경로 맵을 갱신해야 하며, 테스트 과적합을 막기 위한 리팩터링도 병행해야 한다. 시드 고정, 가상 시간, 리트라이 정책 분리는 비결정적 실행의 재현성을 높인다.

분기 커버리지는 2040%p, 결함 검출률은 1530% 향상될 수 있으며, 크리티컬 장애(예외/에러 경로) 유출률은 30% 이상 감소할 수 있다. 회귀 범위를 명확히 하는 기준에서는 신규 기능 리드타임을 10~20% 단축할 수 있다. 복잡도 가시화는 리팩터링 우선순위를 분명히 하고, 테스트 설계의 일관성·재현성과 품질 게이트 기준의 표준화를 뒷받침한다.

경로 테스트화이트박스 테스트제어 흐름 그래프테스트 설계소프트웨어 품질