구조 기반 테스트와 커버리지 기준 운영

구조 기반 테스트의 커버리지 기준과 MC/DC, 경로·데이터흐름·루프 테스트를 실무 품질 게이팅 관점에서 정리한다.

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

코드 구조를 실행 범위로 검증하는 테스트

구조 기반 테스트는 소스코드나 설계 산출물의 제어흐름·데이터흐름을 분석하고, 정해진 커버리지 기준을 충족하도록 테스트를 설계·수행·측정하는 활동이다. 테스트가 소스코드의 어느 수준까지 실행됐는지를 지표로 표현하며, 모든 테스트 레벨에 적용할 수 있고 시스템 아키텍처를 기반으로도 수행할 수 있다.

여기서 커버리지 충분성은 특정 구조 커버리지의 달성 정도로 테스팅의 보장성 또는 충분성을 정량적으로 판단하는 기준이다. 구문, 결정, 조건, 조건/결정, MC/DC, 다중조건, 경로, 기본경로, 제어흐름, 데이터흐름, 루프처럼 여러 기준을 목적과 리스크에 맞춰 선택하거나 조합한다.

운영은 보통 계측, 실행, 수집, 리포트, 게이팅 순서로 이어진다. 자동화 도구를 CI/CD와 연결하면 임계치 정책을 적용할 수 있다. 유닛·컴포넌트·통합·시스템·인수 테스트에서도 구조화된 산출물을 기준으로 측정 가능하며, 코드가 없는 환경은 설계 수준의 제어흐름·데이터흐름 모델로 대체할 수 있다.

제품과 도메인에 따라 구문 80%+, 분기 70%+, 안전 필수 모듈 MC/DC 100% 같은 목표를 둘 수 있다. 다만 목표치는 결함 비용, 안전성, 규제 요구와 커버리지 달성 비용, 결함 포착 이득을 함께 고려해 정한다.

커버리지 기준이 보는 범위

구문 커버리지

전체 구문 가운데 테스트로 실행된 구문의 비율을 측정한다.

구문 커버리지(%) = (수행된 구문 수 / 전체 구문 수) × 100

달성하기는 쉽지만 결함 포착력에는 한계가 있다. 데드 코드 탐지에는 유용하다.

결정 커버리지

모든 결정문의 분기, 즉 참과 거짓이 각각 적어도 한 번 실행됐는지를 본다.

결정 커버리지(%) = (수행된 분기 수 / 전체 분기 수) × 100

단축 평가도 테스트 설계에 포함해야 한다. 예를 들어 if (x > 3 && y < 6) {} else {}에서 x > 3이 false이면 y < 6은 평가되지 않을 수 있다.

조건 커버리지와 조건/결정 커버리지

조건 커버리지는 각 개별 조건식이 true와 false를 모두 경험했는지 측정한다.

조건 커버리지(%) = (수행된 조건 수 / 전체 조건 수) × 100

각 조건이 true와 false를 모두 거쳤더라도 전체 분기 결과가 모두 커버됐다는 보장은 없다. 조건/결정 커버리지는 이 한계를 보완하기 위해 개별 조건식의 true/false와 분기 결과를 함께 측정한다.

조건/결정(%) = [(수행된 분기 수 + 수행된 조건 수) / (전체 분기 수 + 전체 조건 수)] × 100

MC/DC와 다중조건 커버리지

MC/DC는 다른 조건식의 값이 고정된 상태에서, 각 조건식의 변경이 분기 결과를 독립적으로 바꾼다는 것을 입증하는 기준이다. n개의 개별 조건식에는 일반적으로 n+1개의 테스트 케이스가 필요하다. 항공·의료 등 안전 필수 분야에서 표준적 요구로 쓰이며, 높은 보장성만큼 설계 비용도 커진다.

다중조건 커버리지는 모든 개별 조건식의 true/false 조합을 실행한다. 완전 조합은 테스트 수가 급증하므로 실제 적용에서는 페어와이즈 같은 부분 조합과 함께 사용한다.

경로, 기본경로, 제어흐름

경로 커버리지는 가능한 모든 경로를 적어도 1회 실행한다. 하지만 실제 시스템은 경로 수가 폭발하고 루프에는 무한 경로 문제가 있어, 고위험 구간의 대표 경로를 고르는 전략이 필요하다.

기본경로 커버리지는 맥케이브 순환복잡도 V(G) = E − N + 2P로 독립 경로 집합을 도출하고 각 경로를 최소 1회 실행한다. 모든 분기 조건을 최소한으로 보장하면서도 경로 수를 유한하고 실용적인 수준으로 제한한다.

제어흐름 커버리지는 흐름 그래프의 노드(구문)와 에지(전이)를 기준으로 도달성, 분기, 교차점의 커버리지를 측정한다. 데드 코드, 미도달 분기, 예외 경로 누락을 찾는 데 적용할 수 있다.

데이터흐름과 루프 경계

데이터흐름 커버리지는 변수의 정의(def)와 사용(use) 쌍인 DU pair를 기준으로 all-defs, all-uses, all-du-paths를 측정한다. 초기화 누락, 사용 전 변경, 정의 후 미사용, 변이 도중 손실 같은 결함을 탐지하는 데 쓴다.

루프 커버리지는 반복 횟수 경계를 중심으로 테스트를 설계한다. 0회, 1회, 다회, 최대-1/최대/초과 시나리오를 다루며, 중첩 루프는 외부 루프 경계와 내부 루프 경계의 조합을 최소화해야 한다.

측정부터 품질 게이트까지

도구 설정: 계측/옵션 구성테스트 실행: 단위/통합/시스템리포트 생성:XML/HTML/LCOV임계치 검증: ''stmt =80%,branch =70%''통과실패재실행 루프입력: 테스트 케이스,소스코드/모델빌드/계측 단계실행 추적 수집: 라인/브랜치히트커버리지 리포트 집계게이트 통과 여부머지/릴리스피드백: 테스트 보완/결함 분석

안전 필수 임베디드 도메인에서는 제어 로직에 MC/DC 100% 목표를 두고 일반 모듈은 결정 90% 목표로 운영할 수 있다. 대규모 마이크로서비스에서는 PR마다 구문 80%, 분기 70% 게이트를 적용하고 핵심 경로는 기본경로 커버리지로 보강한다.

레거시 리팩터링에서는 제어흐름·데이터흐름 기반 회귀 테스트를 보강하고 루프 경계 조건을 중심으로 결함 재발을 막는다. 성능에 민감한 서비스라면 다중조건 전수 대신 페어와이즈와 MC/DC를 조합해 테스트 수와 보장성의 균형을 맞춘다.

커버리지 기준 성능(수행/측정 오버헤드) 확장성(대상 규모 대응) 일관성(측정 재현성) 안정성(리스크 저감) 운영 편의(도구/자동화)
구문 낮음 높음 높음 낮음 매우 높음
결정 낮음~보통 높음 높음 보통 높음
조건/결정 보통 높음 높음 보통~높음 높음
MC/DC 보통~높음 보통 높음 매우 높음 보통
다중조건 높음 낮음 높음 매우 높음 낮음
기본경로 보통 보통 높음 높음 보통
경로(전수) 매우 높음 매우 낮음 높음 매우 높음 낮음
제어/데이터흐름 보통 보통 높음 높음 보통
루프 경계 낮음 높음 높음 보통 높음

MC/DC를 측정하는 예제

환경과 전제조건은 Python 3.11+, pytest 8+, coverage.py 7+를 가정한다. branch 커버리지를 활성화하는 편이 좋다.

변경조건/결정(MC/DC) 대상 조건식 구현은 다음과 같다.

# src/guard.py
def select_path(x: int, y: int, z: int) -> str:
    # 결정식: ((x > 3 or y < 6) and z == 20)
    if ((x > 3 or y < 6) and z == 20):
        return "A"
    else:
        return "B"

n=3 조건에 대해 n+1=4케이스로 구성한 테스트다.

# tests/test_guard.py
from guard import select_path

def test_mcdc_case1():
    # x>3=F, y<6=F, z==20=T → decision=F
    assert select_path(2, 7, 20) == "B"

def test_mcdc_case2():
    # x>3=F, y<6=T, z==20=T → decision=T (y의 독립 영향)
    assert select_path(2, 5, 20) == "A"

def test_mcdc_case3():
    # x>3=F, y<6=T, z==20=F → decision=F (z의 독립 영향)
    assert select_path(2, 5, 0) == "B"

def test_mcdc_case4():
    # x>3=T, y<6=F, z==20=T → decision=T (x의 독립 영향)
    assert select_path(4, 7, 20) == "A"

실행과 측정은 다음 명령으로 수행한다.

pip install pytest coverage
coverage run -m pytest -q
coverage report -m  # 라인 커버리지 확인
# branch 커버리지를 위해 .coveragerc 설정:
# [run]
# branch = True
coverage html  # HTML 리포트 생성

위 4케이스에서는 모든 라인이 실행돼 구문 커버리지 100%를 달성한다. if의 참과 거짓도 모두 실행되므로 결정 커버리지 역시 100%다. x>3, y<6, z==20은 각각 T/F를 모두 경험해 조건 커버리지 100%가 되며, 각 조건의 변경이 분기 결과에 독립적으로 영향을 준다는 것을 케이스 쌍으로 입증해 MC/DC 100%를 달성한다.

루프는 단일 for 또는 while에 대해 0회, 1회, N회(대표값), 최대치, 최대치+1(예외/가드)을 설계한다. 중첩 루프는 외부 루프 경계와 내부 루프 경계의 최소 조합으로 축약한다.

커버리지 목표를 운영 정책으로 바꾸는 법

모듈 위험도에 따라 목표치를 다르게 적용할 수 있다. 예를 들어 Core에는 분기 90%+MC/DC를, Peripheral에는 구문 80%를 적용한다. 논리 연산자 &&, ||의 단축 평가와 early return, guard를 포함한 예외 흐름은 별도로 보강해야 한다.

다중조건 대신 MC/DC와 페어와이즈를 함께 적용하면 보장성과 비용을 조절할 수 있다. 데이터흐름은 all-uses 기준으로 강화하고 정적 분석 도구와 병행한다. 경로 폭발은 기본경로 중심으로 대표 경로를 선정하고 루프 경계 테스트로 무한 경로 문제를 피한다.

분기와 MC/DC를 도입하면 논리 결함 검출률이 10~30%p 상승할 것으로 기대할 수 있다. 변경 영향 경로를 보장해 배포 장애율을 낮추고, 커버리지 메트릭으로 객관적인 품질 게이팅을 구현할 수 있다. 안전 필수 도메인에서는 MC/DC 같은 인증 기준 충족을 지원하며, 리스크 기반 기준 적용으로 테스트 수 대비 효과를 높인다.

다만 100% 커버리지는 무결함을 의미하지 않는다. 요구기반 테스트와 변이 테스트를 병행하면서 도메인 요구와 리스크에 맞는 기준 조합을 유지해야 한다.

구조 기반 테스트커버리지MC/DC소프트웨어 테스트품질 관리