조건 커버리지 (Condition Coverage)

조건 커버리지는 결정 포인트 내의 각 개별 조건식이 참(true)과 거짓(false)의 결과를 최소한 한 번씩 갖도록 테스트하는 기법이다.
이는 전체 조건식의 결과와는 독립적으로 각 개별 조건의 결과에 초점을 맞춘다.

주요 특징

  1. 개별 조건 중심: 전체 조건식이 아닌 각 개별 조건식의 결과를 검증한다.
  2. 최소 요구사항: 각 조건이 최소한 한 번씩 참과 거짓의 결과를 가져야 한다.
  3. 세분화된 테스트: 복잡한 조건문의 각 부분을 개별적으로 테스트할 수 있다.

장점

  1. 조건의 독립적 평가: 각 조건을 독립적으로 평가하여 더 세밀한 테스트가 가능하다.
  2. 제어 흐름에 대한 높은 민감도: 프로그램의 제어 흐름을 더 정확하게 테스트할 수 있다.
  3. 결정 커버리지보다 강력: 더 많은 테스트 케이스를 요구하므로 더 철저한 테스트가 가능하다.

단점

  1. 전체 조건식 결과 보장 부족: 개별 조건의 참/거짓만을 테스트하므로 전체 조건식의 모든 결과를 보장하지 않을 수 있다.
  2. 테스트 케이스 증가: 조건의 수가 많아질수록 필요한 테스트 케이스의 수가 증가한다.
    따라서 조건 커버리지는 다른 테스트 커버리지 지표들(구문 커버리지, 분기 커버리지 등)과 함께 사용되어야 하며, 이를 통해 더 완성도 높은 테스트를 수행할 수 있다.

조건 커버리지를 계산하는 방법

1
2
3
4
5
6
public boolean isEligibleForDiscount(int age, boolean isMember, int purchaseAmount) {
    if (age >= 60 && isMember || purchaseAmount > 1000) {
        return true;
    }
    return false;
}

이 코드에는 세 가지 개별 조건이 있다:

  1. age >= 60
  2. isMember
  3. purchaseAmount > 1000

완전한 조건 커버리지를 달성하기 위해서는, 각 조건이 독립적으로 참과 거짓을 모두 가져야 한다.
이를 위한 테스트 케이스를 설계해보면,

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
@Test
void testDiscountEligibility() {
    // age >= 60 조건 테스트
    assertTrue(isEligibleForDiscount(65, true, 500));  // age: true
    assertFalse(isEligibleForDiscount(55, true, 500)); // age: false
    
    // isMember 조건 테스트
    assertTrue(isEligibleForDiscount(65, true, 500));  // isMember: true
    assertFalse(isEligibleForDiscount(65, false, 500)); // isMember: false
    
    // purchaseAmount > 1000 조건 테스트
    assertTrue(isEligibleForDiscount(55, false, 1500)); // purchaseAmount: true
    assertFalse(isEligibleForDiscount(55, false, 800)); // purchaseAmount: false
}
1
2
3
4
5
6
7
8
9
조건 커버리지 = (테스트된 조건 결과의 수) / (전체 가능한 조건 결과의 수) × 100%

예시에서:
- 전체 조건 수: 3개
- 각 조건당 가능한 결과: 2개 (참/거짓)
- 전체 가능한 조건 결과의 수: 3 × 2 = 6
- 테스트된 조건 결과의 수: 6

따라서, 조건 커버리지 = (6/6) × 100% = 100%

조건 커버리지의 한계

1
2
3
4
5
6
public boolean validateUser(String username, String password, boolean isActive) {
    if (username != null && password != null && isActive) {
        return true;
    }
    return false;
}

이 코드에서 각 조건을 독립적으로 테스트하더라도, 조건들의 모든 가능한 조합을 커버하지는 못할 수 있다.
예를 들어:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
// 조건 커버리지는 만족하지만, 모든 시나리오를 커버하지는 못하는 테스트
@Test
void testUserValidation() {
    // username != null 테스트
    assertTrue(validateUser("user", "pass", true));   // true
    assertFalse(validateUser(null, "pass", true));    // false
    
    // password != null 테스트
    assertTrue(validateUser("user", "pass", true));   // true
    assertFalse(validateUser("user", null, true));    // false
    
    // isActive 테스트
    assertTrue(validateUser("user", "pass", true));   // true
    assertFalse(validateUser("user", "pass", false)); // false
}

조건 커버리지를 향상시키기 위한 실제적인 접근 방법

  1. 테스트 자동화 도구 활용:

    1
    2
    3
    4
    5
    6
    
    // JaCoCo와 같은 테스트 커버리지 도구 사용
    @Test
    @CoverageTarget(type = CoverageType.CONDITION)
    void comprehensiveTest() {
        // 테스트 케이스들
    }
    
  2. 경계값 분석과 결합:

     1
     2
     3
     4
     5
     6
     7
     8
     9
    10
    11
    12
    13
    14
    15
    16
    17
    
    public boolean validateAge(int age, boolean hasParentalConsent) {
        if (age >= 13 && age <= 19 && hasParentalConsent) {
            return true;
        }
        return false;
    }
    
    @Test
    void testAgeValidation() {
        // 경계값과 조건 커버리지를 결합한 테스트
        assertFalse(validateAge(12, true));  // 경계값 미만
        assertTrue(validateAge(13, true));   // 최소 경계값
        assertTrue(validateAge(16, true));   // 중간값
        assertTrue(validateAge(19, true));   // 최대 경계값
        assertFalse(validateAge(20, true));  // 경계값 초과
        assertFalse(validateAge(15, false)); // 동의 없음
    }
    

예시

다음과 같은 조건문이 있다고 가정해 보자:

1
2
3
if (a > 0 && b < 10) {
    // 코드 실행
}

조건 커버리지를 만족시키기 위해서는 다음과 같은 테스트 케이스가 필요하다:

  1. a > 0 (참), b < 10 (참)
  2. a > 0 (참), b < 10 (거짓)
  3. a > 0 (거짓), b < 10 (참)
  4. a > 0 (거짓), b < 10 (거짓)

이렇게 각 개별 조건이 참과 거짓의 결과를 모두 가지도록 테스트 케이스를 구성한다.

개별 조건식이 참/거짓을 모두 가지는 비율
예를 들어, (A && B)라는 조건에서 A와 B 각각에 대해 true/false 케이스를 테스트한다.

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
def is_eligible_for_discount(age, is_student, purchase_amount):
    if (age < 18 or is_student) and purchase_amount >= 100:
        return True
    return False

# 테스트 코드
def test_discount_eligibility():
    # 모든 조건 조합 테스트
    assert is_eligible_for_discount(16, False, 150)  # 미성년자
    assert is_eligible_for_discount(25, True, 150)   # 학생
    assert not is_eligible_for_discount(25, False, 150)  # 성인, 비학생
    assert not is_eligible_for_discount(16, True, 50)    # 구매액 부족

참고 및 출처