과연 테스트 커버리지는 어느정도로 작성해야하는가?

테스트를 해야한다고 막상 시작하면 막막한 경우가 생긴다.
현재 나의 경우가 그러하다. 그리고 막상 시작하게 된다면, 아래의 의문이 생기기 마련이다.

어떤 목적으로? 어느정도 범위로? 어떻게? 테스트 대상 선정은 어떤식으로?

흔히 테스트를 하기 전에 막막하게 하는 몇가지의 요소 인것 같다.

현재 내가 끄적이는 말들이 정답이 될 수는 없다.
하지만 많은 분들이 저의 글을 보고 참조 정도는 할 수 있게 도움이 될 수 있었으면 하는 바람에 올리게 됩니다.

어떤 목적으로 우리는 테스트를 해야 하는가?

사실 많은 부분이 갈리는 요소인 듯 합니다.
기본적으로 테스트를 하는데, 가볍게 기능 정도가 요정도는 돌아간다?
이건 근본적으로 테스트를 할 때 어느 정도의 범위를 산정 하는지에 대한 기준이 되지 않을 것 같습니다.

결국 내가 만드는 혹은 우리가 만드는 개발 프로그램이 어떤 목적이냐에 따라 해당 테스트의 범위와 모든 목적들이 달라질 듯 합니다.

예를 들자면, 우리에겐, 트래픽이 중요한 게 아니라, 해당 기능의 안정성이 중요하다면, 기능에 대한 테스트를 디테일 하게 잡을 수 있을 것이며,
안정성도 중요하지만, 부하에 대한 리스크가 더 크다면, 우선적으로 부하 테스트를 우선으로 할 수 있을 것입니다.

이 처럼 우리가 만드는 해당 프로그램이 각기 어떤 환경에 놓였는지, 어떤 문제점이 있는지, 어떤 목적을 가지고 개발을 해왔는지에 따라서 우리의 주 테스트가 달라지면서, 테스트의 방향을 바꿀 선택을 할 것이라고 생각이 듭니다.

테스트 커버리지

출처: 테스트 커버리지

Test Coverage?

  • 시스템 또는 소프트웨어의 테스트를 논할 대 얼마나 테스트가 충분한가? 를 나타내는 것이다.
  • 즉, 수행한 테스트가 테스트의 대상을 얼마나 커버했는지를 나타낸다.

테스트를 기능에 대한 테스트 부터 점점 작은 단위로 내려오다 보면 단위 테스트의 경우 클래스, 컴포넌트 단위의 테스트를 하기 때문에, 테스트에 대한 커버 범위를 각각의 클래스 또는 소스 코드의 각 라인을 척도로 삼을 수 있게 됩니다.
이렇게 코드가 얼마나 테스트 됐는지 나타내는 커버리지, 코드 커버리지(구조적 커버리지)라고 합니다.

소스 코드 기반으로 수행하는 화이트 박스 테스트를 통해 측정한다.

필요성

우리는 테스트를 진행할 때 얼마만큼 테스트를 해야 하고, 언제 테스트를 멈출지 정량적인 지표가 필요하다.
그렇지 않으면 불필요한 테스트들에서 비용을 사용하게 되고, 의미 없는 테스트만이 진행될 뿐이다.

또한 테스트는 발생할 수 잇는 모든 시나리오에 대해 작성되어야 하는데 매우 복잡한 로직의 경우 개발자가 놓치기 쉽다.
이러한 휴먼 에러를 최대한 방지하는 용도로써도 필요하다.

어느 곳에서는 코드 커버리지가 기존보다 떨어지는 경우 commit이 불가능하도록 제한하기도 한다.

테스트 커버리지 100%의 함정은 항상 주의해야 한다.

코드가 실행된다고 해서 모든 버그들이 제어되는 것은 아니다.
그렇기에 테스트 커버리지 100%는 완벽한 소프트웨어를 나타내지 않는다.
따라서 맹신 하면 안된다

Code Coverage 측정 종류

코드의 구조를 살펴보면 크게 구문(Statement), 조건 (Condition), 결정(Decision)의 구조로 이루어져 있다.
코드 커버리지는 이러한 코드의 구조를 얼마나 커버 했느냐에 따라 측정 기준을 나눈다.

Pasted image 20241107162524.png

Statement Coverage

Line Coverage 라고 부르기도 한다.
코드 한 줄이 한번 이상 실행된다면 충족된다.

void test(int n) {
  // 함수 A 실행 - 1번
  if (n > 0) { //- 2번
    // 함수 B 실행 - 3번
  }
  // 함수 C 실행 - 4번
}

위의 코드를 n이 음수로 들어오는 테스트 하나만 했다고 하자.
그러면 1~4 번 구문 중 3번 구문이 실행이 안돼서 구문 커버리지는 3 / 4 * 100% = 75%인 테스트가 된다.

Decision Coverage

Branch Coverage라고 부릅니다.
모든 조건 식이 true/ false를 가지게 되면 충족 됩니다.

void test(int a, int b) {
  // 함수 A 실행 - 1번
  if (a > 0 && b < 0) { //- 2번
    // 함수 B 실행 - 3번
  }
  // 함수 C 실행 - 4번
}

조건 커버리지에서의 예시를 다시 가져와보자
a = 1, b = 1a = -1, b = -1를 넣어보는 테스트를 했을 때, 조건 커버리지가 만족 되더라도 함수 B가 실행되지 않았던 결과가 있었다.

Decision Coverage를 만족한다면, 이런 문제는 없을 것이다.
그 이유는 결정 커버리지에서 결정은 내부 조건이 아닌 조건 식으로 얘기 한다.
즉, 위에 코드에서는 a > 0 && b < 0를 얘기 한다.

따라서 Decision Coverage를 만족하는 테스트를 만든다고 한다면, a = 1, b = 1a = -1, b = -1 와 같이 조건 식이 false만 나오는 식이 아닌 a = 1, b = -1 를 넣는 테스트를 만들어야 한다.
이 테스트 케이스를 넣게 되면, 조건 커버리지 결정 커버리지, 구문 커버리지 모두 만족할 수 있다.