Testing/Unit Testing

커버리지 지표 ( coverage metrix )

Seung-o 2023. 12. 4. 00:04

테스트 스위트 품질 측정을 위한 커버리지 지표

커버리지 지표란?

커버리지 지표는 테스트 스위트가 소스 코드를 얼마나 실행하는지를 백분율로 나타낸다.

 

일반적으로 커버리지 지표의 커버리지 숫자가 높을 수록 더 좋다고 알려져있지만, 사실 이것은 그리 간단하지 않다. 커버리지 지표는 중요한 피드백을 주더라도 테스트 스위트 품질을 효과적으로 측정하는 데 사용될 수 없다. 

 

코드 커버리지가 너무 적을 때 ( 약 10% 미만 )는, 테스트가 충분치 않다는 좋은 증거이지만, 100 %  커버리지라고 해서 반드시 양질의 테스트 스위트를 보장하지는 않는다. 높은 커버리지의 테스트 스위트도 품질이 떨어질 수 있다.

 

코드 커버리지 지표에 대한 이해

가장 많이 이용되는 커버리지 지표는 "코드 커버리지( code coverage )"이며, 테스트 커버리지 ( test coverage )로도 알려져 있다. 이 지표는 다음과 같다.

 

코드 커버리지 = 테스트 시 실행된 코드 라인 수 / 전체 라인 수

 

아래 예시를 살펴보자.

 

- 서비스 코드

public static isStringLong(input: string): boolean {
  if(input.length > 5) {
    return true;
  }
  return false;
}

 

- 테스트 코드

when('isStringLong Test', () => {
  const result: boolean = isStringLong("abc");
  expect(result).equal(false);
})

 

 

서비스 코드의 전체 라인 수는 5줄인데 반해, 테스트가 실행하는 코드는 `return true` 를 제외하고, 총 4줄이다. 따라서 코드 커버리지는 4/5 = 0.8 = 80% 이다. 

 

이 때, 서비스 코드를 아래와 같이 수정하면 어떨까?

 

public static isStringLong(input: string): boolean {
  return input.length > 5;
}

 

코드 커버리지는 100 %로 늘어난다. 하지만 이 리팩터링은 테스트 스위트를 개선하지 않았다. 그저 메서드 내 코드를 바꿧을 뿐이다. 테스트가 검증하는 결과 개수는 여전히 같다.

 

분기 커버리지 지표에 대한 이해

또 다른 커버리지 지표로는 분기 커버리지 ( branch coverage )가 있다. 분기 커버리지 지표는 원시 코드 라인 수를 사용하는 대신 if문과 switch 문과 같은 제어 구조에 중점을 둔다. 

 

분기 커버리지 = 통과 분기 / 전체 분기 수

 

위의 isStringLong 예시를 다시 살펴보면, isStringLong 메서드에는 두 개의 분기가 존재하는데 반해, 테스트는 return false 분기에 대해서만 작용하므로 분기 커버리지 지표는 1/2 = 0.5 = 50%이다. 이것은 메서드의 코드의 변화와 무관하게 동일하다. 

 

커버리지 지표의 문제

분기 커버리지로 코드 커버리지보다 더 나은 결과를 얻을 수 있지만, 테스트 스위트의 품질을 결정하는 데 어떤 커버리지 지표도 의존할 수 없다. 

 

이유는 크게 두 가지로, 아래와 같다.

 

1. 가능한 모든 결과를 검증한다고 보증할 수 없음

 

IsStringLong 이 아래와 같이 변화되었을 때를 살펴보자.

 

public static wasLastStringLong: boolean;

public static isStringLong(input: string): boolean {
  const result: boolean = input.length > 5;
  wasLastStringLong = result;
  return result;
}

 

이렇게 바뀌었을 때도 코드 커버지리와 분기 커버리지는 바뀌지 않는다. 하지만 테스트는 메서드 내 두 번째 줄 ( wasLastStringLong = result ) 코드를 검증하지 않는다. 

 

극단적으로는 테스트 코드에서 expect 없이 단순히 메서드를 실행만 하더라도 동일한 분기를 얻을 수 있다. 

 

2. 외부 라이브러리의 코드 경로를 고려할 수 없음

 

모든 커버리지 지표가 테스트 대상 코드를 호출할 때, 외부 라이브러리의 코드 경로를 고려하지 않는다는 것 역시 문제이다. 아래 예시를 보자.

 

import * as _ from 'lodash';

public static parseInt(input: string) {
  return _.parseInt(input);
}

 

코드 커버리지, 분기 커버리지 모두 100 %를 나타내지만, 사실 lodash 라이브러리 내 parseInt 메서드가 수행하는 코드 경로는 고려하지 않는다. 이는 커버리지 지표만으로 테스트가 철저한지 또는 충분하지 알기 어렵다는 방증이다.

 

무엇이 성공적인 테스트 스위트를 만드는가?

 

성공적인 테스트 스위트는 다음과 같은 특성을 갖고 있다.

 

- 개발 주기에 통합돼 있다.

- 코드베이스에서 가장 중요한 부분만을 대상으로 한다.

- 최소한의 유지비로 최대의 가치를 끌어낸다.

 

이것을 이루기 위해 가치 있는 테스트를 식별할 줄 알고, 작성할 줄도 알아야한다. 두 가지는 비슷해보이지만 다르다. 가치 높은 테스트를 식별하기 위해서는 기준틀이 필요하다. 반면, 가치 높은 테스트를 작성하기 위해서는 설계 기술도 알아야 한다. 단위 테스트와 기반 코드는 서로 얽혀 있으므로 코드베이스에 노력을 많이 기울이지 않으면 가치 있는 테스트를 만들 수 없다.

 

'Testing > Unit Testing' 카테고리의 다른 글

단위 테스트란 무엇인가  (2) 2024.02.07
단위 테스트의 목표  (0) 2023.12.03
Unit Testing (Vladimir Khorikov) 책을 리뷰하기 전  (2) 2023.12.02