Code/Test

[단위 테스트] 1-2. 단위 테스트의 목표: 커버리지 지표

noahkim_ 2025. 1. 6. 22:26

블라디미르 코리코프 님의 "단위 테스트" 책을 정리한 포스팅입니다.


1. 테스트 스위트 품질 측정

커버리지 지표

  • 테스트에 의해 실행된 코드의 비율
  • ✅ 테스트 스위트의 품질을 평가하는데 자주 사용됨


종류

항목 설명 공식
코드 커버리지 전체 코드 중, 테스트 실행 시 실행된 라인의 비율 실행된 코드 라인 수 / 전체 코드 라인 수
분기 커버리지
모든 분기가 테스트되었는지 측정
➡️ 코드 커버리지보다 더 정밀하고 정확한 테스트 평가 가능
통과한 분기 수 / 전체 분기 수

 

예시) 분기 커버리지

더보기
public boolean isEligibleForDiscount(int age, boolean hasCoupon) {
    if (age >= 18) { // 분기 1
        if (hasCoupon) { // 분기 2
            return true;  // 조건이 모두 참일 때
        } else {
            return false; // 나이 18 이상이고, 쿠폰이 없을 때
        }
    } else {
        return false; // 나이가 18 미만일 때
    }
}

 

번호 입력 값 실행된 분기
테스트 1 age = 20
hasCoupon = true
첫 번째 분기: 참
두 번째 분기: 참
테스트 2 age = 20
hasCoupon = false
첫 번째 분기: 참
두 번째 분기: 거짓
테스트 3 age = 15
hasCoupon = true
첫 번째 분기: 거짓
테스트 4 age = 15
hasCoupon = false
첫 번째 분기: 거짓
  • 100% 분기 커버리지
  • ✅ 전체 분기 커버리지를 달성하려면 모든 조건문이 참/거짓인 경우를 다 테스트해야 함
  • ➡️ 위 4개의 테스트 스위트는 모든 분기의 테스트를 커버함

 

한계

구분 한계 설명 세부 내용
모든 가능한 결과 검증 불가
입력 조합의 무한성 모든 입력 경우의 수를 테스트 코드로 커버하기 어려움
상태 변화 다양성
시스템 상태의 모든 변화를 테스트에 포함하는 데 한계가 있음
외부 라이브러리 반영 불가
커버리지 지표 한계
외부 라이브러리 내부 로직은 커버리지 계산에 포함되지 않음
내부 조건 누락
외부 라이브러리 내부적으로 다양한 조건/분기 처리하지만, 테스트 커버리지에 반영되지 않음
에러 경로 미검증
외부 라이브러리 내부 코드 경로에서 발생할 수 있는 에러 상황을 직접 검증해야 함

 

예시) 모든 가능한 결과 검증 불가

더보기
public class LoginService {
    public String login(String username, String password) {
        if ("user1".equals(username) && "password123".equals(password)) {
            return "Login successful";
        }
        return "Login failed";
    }
}
  • 입력 조합: 다양한 사용자명과 비밀번호 조합 (너무 많음)
  • 상태 변화: 로그인 이후 세션 유지, 타임아웃 등 (단순 커버리지로 보장 안됨)

 

예시) 외부 라이브러리 반영 불가

더보기
public User parseUser(String json) {
    try {
        return objectMapper.readValue(json, User.class);
    } catch (JsonProcessingException e) {
        log.error("잘못된 JSON 입력: {}", json, e);
        return null; // 혹은 커스텀 예외 변환
    }
}
@Test
void parseUser_validJson() {
    String json = "{\"name\":\"Alice\"}";
    User user = myService.parseUser(json);
    assertEquals("Alice", user.getName());
}

@Test
void parseUser_invalidJson() {
    String json = "{invalid-json}";
    User user = myService.parseUser(json);
    assertNull(user); // 예외를 null 반환으로 처리했는지 검증
}
  • 라이브러리 내부에서 어떤 분기로 예외를 던질지 알수 없음
  • ➡️ 클라이언트 코드에서 예외 처리 상황에 대한 테스트 필요

 

2. 성공적인 테스트 스위트

항목 설명
개발 주기에 통합
- 코드 변경 시 테스트 작성: 코드가 변경될 때마다, 테스트 스위트를 작성하고 실행해야 함
- 빌드 시스템에 통합: 코드 변경 시마다 자동으로 실행되도록 해야 함
중요한 부분만을 대상으로 - 비즈니스 로직에 집중: 도메인 모델, 알고리즘을 중심으로 테스트 작성
- 인프라 및 외부 종속성: 테스트 코드에서 분리하고, 해당 부분은 모의 객체를 사용
최소 유지비용, 최대 가치
- 유지비용이 적고, 가치 있는 테스트에 집중하기
- 좋은 코드 설계 필요: 좋은 테스트 코드를 작성하기 위해서는 코드베이스의 설계가 잘 되어 있어야 함