Coverage의 사전적 뜻 : 적용 범위
코드 커버리지란, 소프트웨어의 Test Case가 얼마나 충족되었는지, TestCode가 Production Code를 얼마나 실행했는지를 백분율(%)로 나타내는 지표 중 하나이다. Test를 진행하였을 때, '코드 자체가 얼마나 실행되었는지'를 나타내는것이고 이는 수치를 통해 확인할 수 있다.
즉 Test Code가 Production 코드를 얼마나 검증하고 있는지를 수치(혹은 %)로 나타내고 이 커버리지를 통해 현재 작성된 Test Code의 수가 충분한 것인지 논의할 수 있다.
원래 원칙적으로 test code는 모든 시나리오에 대해서 설계되어야 한다. 실제로 배포하는 서비스라면 더욱더 모든 시나리오를 커버해야만 한다. 그러나 현실적으로 내가 지금 작성한 테스트 케이스가 모든 케이스를 커버하는지를 눈으로 확인하긴 어렵지만, 객관적인 지표로 확인할 수 있는 방법 중에 하나가 Code Coverage이다.
코드 커버리지는 소스 코드를 기반으로 수행하는 화이트박스 테스트
를 통해 측정할 수 있다.
- JVM 진영의 대표적 코드 커버리지 도구 : Jacoco
블랙 박스 테스트 (Black-box test)
- 소프트웨어의
내부 구조나 작동 원리를 모르는 상태에서(black-box)
동작을 검사하는 방식이다.- 올바른 입력과 올바르지 않은 입력을 입력하여
올바른 출력이 나오는지
테스트하는 기법이다.-
사용자 관점
의 테스트 방법이라 볼 수 있다.화이트 박스 테스트 (White-box test)
- 응용 프로그램의 내부 구조와 동작을 검사(White-box)하는 테스트 방식이다.
- 소프트웨어 내부 소스 코드를 테스트 하는 기법이다.
- 개발자 관점의 단위 테스트 방법이라 볼 수 있다.
코드 커버리지의 목표 백분율 지표 %는 정답은 없다.
모조리 다 해야 한다. 최대한 높게.
조직마다 다르다? 조직이나 팀에서 최대한 높게 잡아 목표를 이룰 수 있도록 해야 한다고 생각한다.
코드 커버리지 도구와 소나큐브와 같은 정적 코드 분석 도구를 함께 활용하여 코드 커버리지가 기존보다 떨어지는 경우 커밋이나 테스트 성공후 빌드가 불가능하도록 제한을 해서 코드의 안정성을 어느 정도 보장해 줘야 한다
. 또한 프로젝트에서 커버리지를 확인하고 관리, 적용하려고 노력해야만 한다.
가장 중요한것은 테스트 코드의 중요성이고, 테스트코드의 중요성과 장점을 활용해야 하며 그 방법중에 하나가 코드 커버리지이다.
테스트 코드를 활용하고 관리하는 방법은 코드 커버리지만 있는것이 아니다.
커버리지 100%를 달성한다고 한들, 여전히 버그의 위험은 존재한다. 높은 커버리지는 좋지만, 맹신해서는 안되며 개발자는 항상 이 모든것을 염두에 두고 개발하고 테스트해야 한다.
코드 커버리지의 측정 기준
코드 커버리지의 측정 기준은 여러가지가 있다.
크게 살펴보면 구문(Statement), 조건(Condition), 결정(Decision)의 구조로 이루어져 있다.
코드 커버리지는 이러한 코드의 구조를 얼마나 커버했느냐에 따라 측정 기준을 나눈다.
구문, 조건, 결정, 조건/결정, 변형 /조건/결정, 다중 조건, 경로, 함수 등 나뉠수도 있다.
함수 커버리지 (Function Coverage)
어떤 함수가 최소 1번 이상 호출되었는지를 기준으로 커버리지를 계산한다. 함수 내부의 모든 코드가 실행되었는지는 판단 기준에서 제외된다.
public void a() {
// ...
}
public void b() {
// ...
}
public void c() {
// ...
}
public void d() {
// ...
}
위와 같이 4개의 함수가 있고, 테스트 코드가 a()
와 b()
만을 실행한다면 4개중 2개의 함수를 실행하였으므로 테스트 커버리지는 50%가 된다.
함수 커버리지 = (실행된 함수의 수 / 전체 함수의 수) * 100
구문(Statement) 커버리지
라인(Line) 커버리지라고도 불린다. 프로덕션 코드의 전체 구문 중 몇 줄의 구문이 실행되었는지를 기준으로 판단한다.
코드 한 줄이 한 번이상 실행된다면 충족된다.
public void run(int a) {
System.out.println("A"); // A
if (a > 10) { // B
System.out.println("C"); // C
}
System.out.println("D"); // D
}
위 코드를 테스트할 때 a
를 10보다 작은 값으로만 테스트한다면, A ~ D 중 C는 실행되지 않는다.
구문 커버리지의 경우 4개의 구문 중, 3개의 구문만 실행되었으므로 커버리지는 3 / 4 * 100 = 75%
로 계산된다.
구문 커버리지 = (실행된 구문의 수 / 전체 구문의 수) * 100
조건(Condition) 커버리지
전체 조건식이 아니라 개별 조건식을 기준으로 판단한다.
모든 조건식의 내부 조건식이 개별로 true/false을 가지게 되면 조건 커버리지 기준이 충족된다.
void foo (int x, int y) {
system.out("start line"); // 1번
if (x > 0 && y < 0) { // 2번
system.out("middle line"); // 3번
}
system.out("last line"); // 4번
}
위 코드를 예시로 보면 모든 조건식으로는 2번 if 문이 있고, 그중 내부 조건식은 조건식 내부의
x > 0
,y < 0
2가지 말한다.
- 첫 번재 테스트 케이스 :
x = 1, y = 1
을 if문의 조건식에 대입해보면 결과는 false - 두 번째 테스트 케이스인
x = -1, y = -1
을 if문 조건식에 대입해보면 결과는 false
즉, 내부 조건 x > 0
, y < 0
에 대해서는 각각 true와 false 모두 나왔지만,
전체 if 조건문인 x > 0 && y < 0
의 관점에서 보면 전체결과는 false에 해당하는 결과만 발생했다.
조건 커버리지는 만족했을지 몰라도 if문 내부 코드(3번)는 실행되지 않았기 때문에 구문(라인) 커버리지를 만족하지 못한다.
- false에 해당하는 케이스만 테스트 되었기 때문에, 구문 커버리지를 만족시킬라면 전체 조건문이 true인 결과도 테스트 해야한다.
조건 커버리지를 만족하도록 테스트를 작성할할 경우, 구문 커버리지와 결정 커버리지를 만족하지 못하는 경우가 존재할 수 있다.
조건(라인) 커버리지를 만족하지 못하면 결정(브랜치) 커버리지도 만족하지 못하게 된다.
결정(Decision) 커버리지
브랜치(Branch) 커버리지라고도 불린다.
프로덕션 코드에 조건문이 있는 경우, 조건문의 전체 조건식이 True인 케이스, False인 케이스 2가지가 최소한 한번 실행되면 충족된다.
즉, 모든 조건식이 true/false 를 테스트하게 되면 충족된다.
- 조건 커버리지에서 true, false 결과 둘 중 하나만 테스트하게된다면 결정 커버러지를 만족하지 못하게 된다
void foo (int x, int y) {
system.out("start line"); // 1번
if (x > 0 && y < 0) { // 2번
system.out("middle line"); // 3번
}
system.out("last line"); // 4번
}
위의 코드를 테스트한다고 가정했을 때,
- 전체 조건식인
x > 0 && y < 0
가 true인경우, false인경우
를 반환하여 테스트 해야 결정 커버리지를 충족한다.
조건/결정(Condition/Decision) 커버리지
void foo (int x, int y) {
system.out("start line"); // 1번
if (x > 0 && y < 0) { // 2번
system.out("middle line"); // 3번
}
system.out("last line"); // 4번
}
위의 코드를 테스트한다고 가정했을 때,
x = 1, y = -1
, x = -1, y = -1
, x = -1, y = -1
의 케이스를 다 넣어야만
조건/결정 커버리지를 모두 만족할 수 있게된다.
가장 많이 사용되는 코드 커버리지
위의 코드 커버리지 중에서 구문 커버리지
가 가장 대표적으로 많이 사용되고 있다.
그 이유는 조건 커버리지나 결정(브랜치) 커버리지의 경우 코드 실행에 대한 테스트보다는 로직의 시나리오에 대한 테스트에 더 가깝다고 볼 수 있기 때문이다.
- 조건, 결정 커버리지는 조건(true/false)에 대해 모두 만족하면 코드 커버리지를 만족한다고 본다.
그렇다면 만약 조건문이 존재하지 않는 코드는 어떻게 처리될까?
조건문이 존재하지 않는 코드의 경우 위 두 커버리지의 대상에서 아예 제외된다. 즉, 해당 코드들을 아예 테스트를 하지 않는다는 말이다.
그러나 구문(라인) 커버리지를 만족한다면, (일단은) 모든 코드를 테스트 코드가 커버했다고는 말할 수 있다. 물론 앞서 설명한 것처럼 if문의 코드에 대해서는 해당 조건이 false인 시나리오에 대해서 테스트했다고 보장할 수 없지만, 그래도 조건문 내부의 코드가 실행되었을 때 문제가 없다는 것은 보장할 수 있다.
구문(라인) 커버리지를 만족하면 모든 시나리오를 테스트한다는 보장은 할 수 없지만, 어떤 코드가 실행되더라도 해당 코드는 문제가 없다는 보장은 할 수 있다.
이런 이유로 인해 구문(라인) 커버리지를 더 많이 사용된다고 한다.
코드 커버리지가 왜 중요할까?
먼저 코드 커버리지의 중요성은 테스트 코드의 중요성과 일맥상통한다고 생각한다.
- 테스트 코드의 중요성, 테스트 코드를 작성함으로써 얻을 수 있는 장점에 대해서는 메서드 시그니처를 수정하여 테스트하기 좋은 메서드로 만들기를 참고하면 도움이 될 것이다.
- 테스트 코드는 발생할 수 있는 모든 시나리오에 대해 작성되어야 한다
- 개발자도 사람인지라, 테스트를 까먹을수도, 놓칠수도 있는 부분이 있을 수 있다.
- 이런 테스트에서 놓칠 수 있는 부분을 코드 커버리지를 통해서 확인하여 부족한 테스트를 충족하고 휴먼 에러를 최대한 방지할 수 있다.
코드 커버리지 도구와 소나큐브(SonarQube)와 같은 정적 코드 분석 도구를 함께 활용하여 코드 커버리지가 기존보다 떨어지는 경우 커밋(commit) 과 테스트 성공(Success)후 빌드(build)가 불가능하도록 제한하기도 한다.
이처럼 코드 커버리지는 코드의 안정성을 어느 정도 보장해 줄 수 있는 지표이기 때문에 많은 프로젝트에서 커버리지를 확인하고 관리, 적용하려고 노력해야만 한다.
가장 중요한것은 테스트 코드의 중요성이고, 테스트코드의 중요성과 장점을 활용해야 하며 그 방법중에 하나가 코드 커버리지이다.
테스트 코드를 활용하고 관리하는 방법은 코드 커버리지만 있는것이 아니다.
코드 커버리지, 몇 퍼센트를 목표로 해야할까?
일반적으로 80% 커버리지가 좋은 목표라고 알려져있다. 반면, 책 클린 코더에서 로버트 마틴은 테스트 커버리지 100%는 권장이 아니라 강력히 요구되는 사항이라고 한다. 즉, 코드 커버리지에는 정답은 없다.
얼마만큼의 코드를 자동화한 단위 테스트로 계산해야 할까? 대답할 필요조차 없다. 모조리 다 해야 한다.
모.조.리!
100% 테스트 커버리지를 권장하냐고? 권장이 아니라 강력히 요구한다.
작성한 코드는 한 줄도 빠짐없이 전부 테스트해야 한다. 군말은 필요 없다.
― 클린 코더 (로버트 마틴 저)
높은 코드 커버리지의 장점
토스뱅크 서버 개발자 이응준님의 테스트 커버리지 100%발표에 따르면 높은 코드 커버리지는 아래와 같은 이점이 존재한다.
- 코드 커버리지가 높으면, 이전보다 더 과감하게 코드를 리팩토링할 수 있다. 리팩토링에 문제가 있다면 테스트 코드가 알려줄 것이다. 비슷한 이유로 배포도 더 자신있게 할 수 있게된다.
- 또한 불필요한 프로덕션 코드가 사라진다. 높은 코드 커버리지를 유지하기 위해서는 불필요한 코드에 대한 테스트 코드도 작성해야하는데, 이럴 바에는 불필요한 코드를 즉시 지워버리는 편이 좋기 때문이다.
- 높은 코드 커버리지는 프로덕션 코드의 이해도를 높여준다. 이해가 충분하지 못하면 테스트 코드를 작성할 수 없기 때문이다.
그렇다고, 100%의 커버리지가 소프트웨어 버그로부터 안전한것도 아니다.
커버리지가 아무리 높다고한들 어플리케이션의 중요한 부분이 테스트되지 않고
있거나,테스트 코드가 문제를 사전에 제대로 발견할만큼 견고하지 않다면
커버리지는 큰 의미 없는 수치이다.
커버리지 100%를 달성한다고 한들 여전히 버그의 위험은 존재한다. 높은 커버리지는 좋지만, 맹신해서는 안된다.
참조
'테스트' 카테고리의 다른 글
MockMvc 테스트시 andExpect(jsonpath())를 이용한 검증방법 (2) | 2023.01.25 |
---|---|
JaCoCo (0) | 2023.01.05 |
Java Mockito Void method 테스트 (0) | 2022.11.18 |
Java Static Method Mocking Test - 자바 스태틱 메소드 테스트 (0) | 2022.11.18 |
Hamcrest (In Java JUnit5) (0) | 2022.11.13 |