[태그:] 결정 커버리지

  • 코드의 모든 길을 비추는 탐험: 화이트박스 테스트 커버리지 완전 정복

    코드의 모든 길을 비추는 탐험: 화이트박스 테스트 커버리지 완전 정복

    소프트웨어의 품질을 보증하는 화이트박스 테스트는 단순히 코드를 실행하는 것을 넘어, 코드의 내부 구조와 논리적 경로를 얼마나 철저하고 체계적으로 검증했는지를 측정하는 ‘커버리지(Coverage)’라는 척도를 핵심으로 삼습니다. 100%의 커버리지를 달성하는 것이 항상 100% 완벽한 소프트웨어를 의미하는 것은 아니지만, 높은 커버리지는 그만큼 코드의 잠재적인 결함이 숨어 있을 공간을 최소화했다는 강력한 증거가 됩니다. 이는 마치 복잡한 미로의 모든 경로를 지도에 표시하며 탐험하는 것과 같으며, 어떤 길에 위험이 도사리고 있는지, 혹은 전혀 사용되지 않아 막다른 길은 없는지를 확인하는 과정입니다.

    화이트박스 테스트의 커버리지는 단순한 코드 라인 실행 여부를 따지는 것부터 복잡한 조건문의 모든 논리적 조합을 검증하는 수준까지 다양한 기준으로 나뉩니다. 각 커버리지 유형은 테스트의 깊이와 강도, 그리고 그에 따른 비용과 노력을 결정하는 중요한 척도가 됩니다. 이 글에서는 가장 기본적인 ‘구문 커버리지’부터 항공우주 분야에서 필수적으로 요구되는 ‘변경 조건/결정 커버리지(MC/DC)’에 이르기까지, 다양한 화이트박스 테스트 커버리지 유형을 명확한 예시와 함께 심층적으로 분석하여, 주어진 상황과 요구사항에 맞는 최적의 테스트 전략을 수립하는 데 필요한 핵심 지식을 제공하고자 합니다.


    코드의 모든 문장을 한 번씩 읽어보기: 구문 커버리지 (Statement Coverage)

    핵심 개념

    구문 커버리지는 화이트박스 테스트에서 가장 기본적이고 직관적인 커버리지 측정 기준입니다. 소스 코드의 모든 실행 가능한 문장(Statement)이 테스트 케이스에 의해 적어도 한 번 이상 실행되었는지를 측정합니다. 코드 한 줄 한 줄이 실행되었는지 여부만 따지기 때문에 달성하기 비교적 쉽고, 테스트 진행 상황을 빠르게 파악할 수 있다는 장점이 있습니다.

    • 측정 공식: (실행된 구문 수 / 전체 구문 수) * 100

    예시 코드와 분석

    Java

    public void process(int x, int y) {
    if (x > 5) { // 조건문
    y = x + y; // 구문 1
    }
    System.out.println(y); // 구문 2
    }

    위 코드에서 구문 커버리지 100%를 달성하기 위한 테스트 케이스는 매우 간단합니다. x = 6 과 같이 x > 5 조건을 만족시키는 값을 입력하면, y = x + y; (구문 1)와 System.out.println(y); (구문 2)가 모두 실행되므로 단 하나의 테스트 케이스만으로도 100%를 만족할 수 있습니다.

    한계점

    구문 커버리지는 단순한 만큼 명확한 한계를 가집니다. 위 예시에서 x = 4 와 같이 조건문이 거짓(False)이 되는 경우는 전혀 테스트하지 않았습니다. 만약 조건문이 거짓일 때 발생하는 논리적 오류가 있다면, 구문 커버리지 100%를 달성했음에도 불구하고 이 결함을 발견할 수 없습니다. 따라서 가장 최소한의 테스트 기준으로만 활용되어야 합니다.


    모든 갈림길을 한 번씩 지나가 보기: 결정 커버리지 (Decision Coverage)

    핵심 개념

    결정 커버리지는 ‘분기 커버리지(Branch Coverage)’라고도 불리며, 코드 내의 모든 조건문(if, switch, for, while 등)의 전체 결과가 참(True)과 거짓(False)이 되는 경우를 각각 한 번 이상 수행하도록 테스트하는 기준입니다. 구문 커버리지가 놓치는 조건문의 논리적 흐름을 검증하기 때문에 더 강력한 테스트 기법입니다.

    • 측정 공식: (수행된 분기 수 / 전체 분기 수) * 100

    예시 코드와 분석

    Java

    public void process(int x, int y) {
    if (x > 5) { // 결정 지점
    y = x + y; // 분기 1 (True)
    }
    System.out.println(y); // 분기 2 (False 경로에도 포함)
    }

    결정 커버리지 100%를 달성하려면, if (x > 5)가 참이 되는 경우와 거짓이 되는 경우를 모두 테스트해야 합니다.

    • 테스트 케이스 1 (True 경로)x = 6 (조건이 참이 되어 y=x+y 실행)
    • 테스트 케이스 2 (False 경로)x = 4 (조건이 거짓이 되어 y=x+y 미실행)

    이처럼 두 개의 테스트 케이스를 통해 모든 분기 경로를 검증할 수 있습니다. 결정 커버리지 100%를 달성하면, 자연스럽게 구문 커버리지 100%도 만족하게 됩니다.

    한계점

    결정 커버리지는 조건문 전체의 결과에만 집중합니다. 만약 조건문이 여러 개의 개별 조건식으로 조합된 경우(예: if (a > 1 && b == 0)), 개별 조건식의 참/거짓 여부와 관계없이 전체 결과가 참/거짓이 되는 경우만 확인하므로, 내부 조건식의 논리적 오류를 놓칠 수 있습니다.


    조건문의 속사정까지 들여다보기: 조건 커버리지 & 그 이상의 기준들

    결정 커버리지가 복합 조건문의 내부를 충분히 검증하지 못하는 한계를 보완하기 위해 더 상세하고 강력한 커버리지 기준들이 등장했습니다.

    조건 커버리지 (Condition Coverage)

    • 핵심 개념: 결정 커버리지가 전체 조건문의 결과에 집중했다면, 조건 커버리지는 전체 조건문을 구성하는 개별 조건식의 결과가 각각 참(True)과 거짓(False)이 되는 경우를 한 번 이상 수행하는 것을 목표로 합니다.
    • 예시 코드if (x > 5 && y < 10)
    • 테스트 케이스:
      1. x=6(True), y=5(True) -> x>5는 True, y<10은 True
      2. x=4(False), y=12(False) -> x>5는 False, y<10은 False
    • 한계점: 위 두 케이스만으로 x>5와 y<10이 각각 True/False를 만족했지만, 정작 전체 결정문의 결과는 (True, True) -> True, (False, False) -> False 만 테스트되었습니다. 즉, 개별 조건식은 모두 커버했지만 전체 결정문의 모든 결과를 커버하지는 못할 수 있습니다.

    조건/결정 커버리지 (Condition/Decision Coverage)

    • 핵심 개념조건 커버리지와 결정 커버지를 모두 100% 만족하는 기준입니다. 즉, 모든 개별 조건식의 참/거짓과 전체 결정문의 참/거짓 결과가 각각 한 번 이상 나오도록 테스트 케이스를 설계합니다.
    • 예시 코드if (x > 5 && y < 10)
    • 테스트 케이스:
      1. x=6(True), y=5(True) -> 전체 결과: True
      2. x=4(False), y=12(False) -> 전체 결과: False위 두 케이스는 개별 조건식의 참/거짓과 전체 결정문의 참/거짓을 모두 만족시키므로, 조건/결정 커버리지를 만족합니다. 이는 결정 커버리지보다 강력하지만, 여전히 특정 조건식의 변화가 전체 결과에 독립적으로 영향을 미치는지 확인하지는 못합니다.

    변경 조건/결정 커버리지 (Modified Condition/Decision Coverage, MC/DC)

    • 핵심 개념: 항공, 원자력, 의료 등 미션 크리티컬(Mission-Critical) 시스템에서 강력하게 요구되는 매우 엄격한 기준입니다. 조건/결정 커버리지를 만족하면서, 각각의 개별 조건식이 다른 조건식의 값에 관계없이 전체 결정문의 결과에 독립적으로 영향을 미치는 경우를 테스트해야 합니다.
    • 예시 코드if (A && B)
    • MC/DC 만족을 위한 테스트 케이스 쌍:
      • A가 결과에 영향을 미치는 쌍: (True, True) -> True / (False, True) -> False  (B는 True로 고정, A가 T->F로 바뀌니 결과도 T->F로 바뀜)
      • B가 결과에 영향을 미치는 쌍: (True, True) -> True / (True, False) -> False (A는 True로 고정, B가 T->F로 바뀌니 결과도 T->F로 바뀜)
    • 중요성: 이 커버리지는 복합 조건문 내의 ‘죽은 코드'(Dead Code, 특정 조건식의 결과가 전체 결과에 아무런 영향을 주지 못하는 경우)를 찾아내는 데 매우 효과적이며, 코드의 논리적 견고성을 최고 수준으로 보장합니다.

    다중 조건 커버리지 (Multiple Condition Coverage)

    • 핵심 개념모든 개별 조건식의 가능한 모든 논리적 조합을 테스트하는 가장 강력하고 완벽한 커버리지 기준입니다. 조건식이 n개일 때, 2^n 개의 테스트 케이스가 필요합니다.
    • 예시 코드if (A && B && C)
    • 테스트 케이스: (T,T,T), (T,T,F), (T,F,T), (T,F,F), (F,T,T), (F,T,F), (F,F,T), (F,F,F) 총 8개의 조합을 모두 테스트해야 합니다.
    • 한계점: 이론적으로 가장 완벽하지만, 조건식의 수가 조금만 늘어나도 테스트 케이스 수가 기하급수적으로 증가하여 현실적으로 적용하기 어려운 경우가 많습니다.

    프로그램의 실행 경로를 지도로 그리기: 기본 경로 커버리지 (Basis Path Coverage)

    핵심 개념

    기본 경로 커버리지는 토머스 맥케이브(Thomas McCabe)가 제안한 ‘순환 복잡도(Cyclomatic Complexity)’ 개념에 기반합니다. 프로그램의 제어 흐름 그래프(Control Flow Graph)에서 논리적으로 실행 가능한 모든 독립적인 경로를 최소 한 번 이상 실행하는 테스트 케 “이스를 설계하는 것을 목표로 합니다.

    1. 제어 흐름 그래프 작성: 소스 코드를 노드(Node, 코드 블록)와 엣지(Edge, 제어 흐름)로 구성된 그래프로 변환합니다.
    2. 순환 복잡도 계산: 그래프의 복잡도를 측정하며, 이는 독립적인 경로의 수와 같습니다.
      • V(G) = E – N + 2 (E: 엣지의 수, N: 노드의 수)
      • V(G) = P + 1 (P: 조건문 등 결정 지점의 수)
    3. 기본 경로 집합 정의: 순환 복잡도 수만큼의 독립적인 경로를 식별합니다.
    4. 테스트 케이스 설계: 식별된 모든 기본 경로를 실행할 수 있는 테스트 케이스를 만듭니다.

    예시 코드와 분석

    Java

    // 1
    public int calculate(int a, int b) {
    // 2
    int result = 0;
    // 3
    if (a > 10) {
    // 4
    result = a;
    }
    // 5
    if (b == 5) {
    // 6
    result = b;
    }
    // 7
    return result;
    }
    • 순환 복잡도: 결정 지점(if문)이 2개이므로, V(G) = 2 + 1 = 3. 즉, 3개의 독립적인 경로가 존재합니다.
    • 기본 경로:
      • 경로 1: 1 -> 2 -> 3 -> 5 -> 7 (a <= 10, b != 5)
      • 경로 2: 1 -> 2 -> 3 -> 4 -> 5 -> 7 (a > 10, b != 5)
      • 경로 3: 1 -> 2 -> 3 -> 5 -> 6 -> 7 (a <= 10, b == 5)
    • 테스트 케이스:
      • TC1(경로1): a=5, b=1
      • TC2(경로2): a=11, b=1
      • TC3(경로3): a=5, b=5이 세 가지 테스트 케이스를 수행하면 모든 기본 경로를 커버할 수 있습니다.

    커버리지 유형 비교 및 선택 전략

    커버리지 유형강도설명
    다중 조건 커버리지가장 높음모든 개별 조건의 가능한 조합을 테스트
    변경 조건/결정 커버리지 (MC/DC)높음각 개별 조건이 독립적으로 전체 결과에 영향을 미치는 경우를 테스트
    조건/결정 커버리지중간 이상조건 커버리지 + 결정 커버리지
    결정 커버리지중간모든 결정문의 참/거짓 결과를 테스트
    조건 커버리지중간모든 개별 조건식의 참/거짓 결과를 테스트
    구문 커버리지가장 낮음모든 실행 가능한 구문을 테스트

    어떤 커버리지 수준을 목표로 할지는 프로젝트의 성격, 요구되는 신뢰도 수준, 그리고 가용한 시간과 비용을 종합적으로 고려하여 결정해야 합니다. 일반적인 상용 소프트웨어는 결정 커버리지나 조건/결정 커버리지를 목표로 하는 경우가 많으며, 안전이 최우선인 임베디드 시스템이나 항공우주 소프트웨어는 MC/DC를 의무적으로 요구합니다.

    결론적으로, 화이트박스 테스트 커버리지는 단순한 테스트 완료의 지표를 넘어, 코드의 논리적 구조를 얼마나 깊이 이해하고 체계적으로 검증했는지를 보여주는 품질의 척도입니다. 각 커버리지 유형의 장단점을 명확히 이해하고 프로젝트의 특성에 맞게 적절한 목표를 설정함으로써, 우리는 더욱 견고하고 신뢰성 높은 소프트웨어를 만들어 나갈 수 있습니다.