들어가면서
저번주에 우테코 스터디에서 팀원 분이 좋은 인텔리제이 기능을 알려주셨다. 바로 테스트 커버리지 기능.. ✨
나는 테스트 커버리지라는 단어에 대해서 정처기 실기를 막 치뤘던 참이라 외워서 알고만 있었다.
이런 커버리지를 직접 인텔리제이에서 확인해볼 수 있다는 게 신기해서, 이번 3주차는 테스트 커버리지 기능을 잘 써보기로 생각했다.
이 글은 공부하면서의 미숙한 내용을 기록한다.
테스트에 대해 무지한 점, TDD를 처음 실천한 점, 모든게 다 처음인 점을 강조한다!
테스트 커버리지란?
이건 나의 저번주차 차 이름 validator 테스트 코드를 테스트 커버리지를 이용해서 돌린 이미지이다. 38퍼,,ㅎㅎ 50퍼,,
그렇다면 도대체 저 퍼센트는 뭐고, 커버리지가 뭘까?
퍼센트 = 코드 커버리지
코드 커버리지란, 내 테스트 코드가 얼마나 현재 프로젝트 코드를 사용했는지 백분율로 나타낸 것이다.
코드 커버리지를 통해 현재 작성된 테스트 코드의 수가 충분한 것인지 논의할 수 있다.
이것을 활용하게 되면, 테스트 코드를 작성하지 못한 부분이 존재하는지도 확인할 수 있어 테스트 코드를 작성하기가 수월해진다.
즉, 내 코드 테스트의 완전성을 측정할 수 있는 지표가 되는 것이다.
위 사진에서 보이는 %들이 내가 얼마나 코드 테스트를 완벽히 짰는지 알 수 있는 퍼센트이다.
그렇다면 이러한 측정은 기준이 어떻게 될까?
코드 커버리지의 측정 기준
- Statement Coverage: 실행된 문장 수 / 전체 문장 수
- Branch Coverage: 실행된 조건문 블록 수 / 전체 조건문 블록 수
- Function Coverage: 실행된 함수 수 / 전체 함수 수
- Decision Coverage: 수행된 분기 수 / 전체 분기 수
- Condition Coverage: 수행된 조건 수 / 전체 조건 수
1. 함수 커버리지
함수가 최소 한 번 이상 호출된다면 충족된다.
함수 내부의 모든 코드가 실행되었는지는 판단 기준에서 제외된다.
public void a() {
// ...
}
public void b() {
// ...
}
public void c() {
// ...
}
public void d() {
// ...
}
이 코드가 있으면, 메서드 a,b,c,d 모두 한 번씩 호출되면 100퍼를 만족하며 안의 코드는 실행 여부를 신경쓰지 않는다.
예를 들어,
여기 차 이름과 시도 횟수를 전달해 객체로 바꾸는 InputDTO의 to 함수가 있다. (예시라고도 하기 뭐하지만,,) 이 함수는 100퍼 만족했는데, record에 to 함수만 존재하고, 함수가 한 번 이상 호출되었기에 그렇다..ㅎㅎ 정확한 예시로는 그냥 클래스 안의 함수가 최소 한 번 이상 호출되면 된다! 저번주차에는 100퍼를 넘긴 코드가 없어서 예시가 미숙한 점 죄송하다..
2. 구문 커버리지
코드 한 줄이 한 번 이상 실행되면 충족된다.
따라서, 아래 코드에서는 총 6번이 실행되어야 100퍼를 충족한다.
public List<String> validate(String carNames) {
validateUserInput(carNames); //1번
List<String> names = tokenizerNames(carNames); //2번
for (String name : names) { //3번
validateEachName(name); //4번
validCarNames.add(name); //5번
}
return validCarNames; //6번
}
예시로, carName로 빈 값을 테스트 했다고 생각하자.
먼저 1번 코드가 실행될 것이다. 그러면 빈칸인지 첫번째 구문에서 확인하고 예외를 던지기 때문에, for 루프를 돌지 않을 것이다. 이렇게 되면 1번만 실행되고 나머지 코드들은 돌지 않게 된다.
이렇게 되면 validate 의 구문 커버리지는 6개 코드 중 1개만 통과했으므로 6분의 1 x 100 = 16.6%가 된다.
3. 결정 커버리지
결정 커버리지는 브랜치 커버리지라고도 불리며, 모든 조건식이 참과 거짓 2가지 케이스가 최소한 한 번 실행되면 충족된다. 개별 조건식 개수와는 상관없이 최소 2개의 테스트 케이스로 충족이 가능하다.
if (x >= -2 && y < 4) {
// ...
}
예를 들어 이런 코드가 있다고 가정하면,
x = 3, y = 3 (참) x = -3 y = 5 (거짓) |
두가지 케이스를 한번씩 통과했으므로 결정 커버리지 기준을 만족하게 된다.
4. 조건 커버리지
조건 커버리지는 결정 커버리지와 다르게 전체 조건식이 아니라 개별 조건식을 기준으로 판단한다. 조건식이 모두 참, 거짓을 한 번씩 갖도록 하면 조건 커버리지 기준을 만족한다.
if (x >= -2 && y < 4) {
// ...
}
위의 코드를 다시 가져왔다.
위에서는
x = 3, y = 3 (참) x = -3 y = 5 (거짓) |
이여야만 100퍼라고 했었는데, 개별 조건식 기준이기 때문에
x>= -2 따로, y< 4 따로 판단한다.
따라서
x = 3(참), y = 5 (거짓) x = -5(거짓), y = 2 (참) |
이렇게 해야 충족한다. 모두 참과 거짓이 따로 한 번씩 선택되어야 하는 것이다!
코드 커버리지, 몇 퍼센트를 목표로 해야 좋은 코드일까?
일반적으로 80%의 커버리지가 좋은 목표라고 알려져 있다.
하지만 클린 코드라는 책에서는 테스트 케이스 100퍼 권장이 아니라 강력히 요구되는 사항이라고 적어두었다. 즉 코드 커버리지에는 정답이 없다. (역시 가스라이팅의 도서,, 📚)
높은 코드 커버리지의 장점
토스 뱅크 서버 개발자 분도 테스트 커버리지 100퍼에 도전하셨고, 테스트 커버리지가 100퍼가 되지 않으면 배포가 불가능하게 해두셨다고 한다. 이에 장점을 알려주셨는데,
1. 높은 커버리지는 이전보다 과감하게 코드 리팩토링이 가능하게 된다.
2. 불필요한 프로덕션 코드가 사라진다.
3. 프로덕션 코드의 이해도를 높여준다. 이해가 충분하지 못하면 테스트 코드를 작성하지 못하기 때문이다.
꼭 100 을 채워야 할까?
하지만 커버리지 100이 아닌 테스트 코드도 나쁜 것은 아니다!
만약 테스트 코드가 중요한 부분을 테스트 하고 있지 않고, (위의 내 예시처럼,,) 테스트 코드가 문제를 사전에 발견하지 못할 만큼 견고하지 않다면 커버리지는 의미가 없어지게 된다.
예를 들어,
public class Calculator {
public int sum(int a, int b) {
return a + b;
}
}
이렇게 덧셈을 수행하는 계산기 클래스가 있다.
위의 클래스를 테스트 코드로 테스트 한다면 테스트 커버리지는 100퍼를 달성한다.
@DisplayName("0과 0을 전달하면 0을 반환한다")
@Test
void sumTest1() {
// given
Calculator calculator = new Calculator();
int a = 0;
int b = 0;
// when
int actual = calculator.sum(a, b);
// then
assertThat(actual).isEqualTo(0);
}
@DisplayName("1과 0을 전달하면 1을 반환한다")
@Test
void sumTest2() {
// given
Calculator calculator = new Calculator();
int a = 1;
int b = 0;
// when
int actual = calculator.sum(a, b);
// then
assertThat(actual).isEqualTo(1);
}
하지만 1+0 도 1이고, 1-0도 1이기에 sum 안의 연산자를 - 로 변경한다면? -> 여전히 통과된다.
이렇게 개발자가 테스트 케이스를 정교하게 나누지 못하고 쓴 테스트 코드는 100퍼를 넘겨도 의미가 없어진다.
즉, 높은 테스트 커버리지가 테스트의 견고함을 보장해주지 않는다는 점이다. 커버리지가 중요하긴 하겠지만 보여지는 통계일 뿐이고, 제일 중요한 것은 내가 어떻게 짜느냐에 달려있는 것 같다.
그래서 인텔리제이에서 테스트 커버리지는 어떻게 실행하나?
인텔리제이의 테스트 파일에서, 한 테스트를 클릭한다.
커버리지를 적용하고 싶은 테스트 코드에서 실행을 누른다.
그렇다면 Run with Coverage 버튼이 나올 것이다!
수행해보자.
수행하면 옆 부분에 통계가 뜨면서 커버리지를 확인할 수 있게 된다.
실행해서 안으로 들어가게 되면, 커버되지 않은 부분은 초록색으로, 커버되지 않은 부분은 붉은색으로 나타난다.
이렇게해서 내가 어떤 점을 커버하지 못했는지 볼 수 있다.
마무리하면서 느낀점, 알게된 점
커버리지의 개념
- 코드 커버리지는 테스트가 코드의 몇 %를 실행했는지를 측정하는 지표이다. 이를 사용하면서 내가 테스트 코드를 얼마나 정교하게 짰는지 알 수 있다.
- 인텔리제이에서 테스트 코드 커버리지 모드로 돌리면 %를 볼 수 있다. 이번 우테코 미션 하면서 자주 애용할 것 같다.
커버리지 목표
내가 처음에 이 기능을 알게 되었을 때 스터디 원 분들께 질문을 했었다. 꼭 100퍼를 맞춰야 하나요?
답은 상한선이 80퍼라고 하셨다. 다들 비즈니스 로직이 복잡해지거나 사소한 것들은 굳이 테스트 코드를 짜지 않는 분도 계시다고 하셨다. 그걸 듣고 나는 이번에 테스트 커버리지를 확인하면서 테스트 코드를 짰을 때 어떤 생각일지 궁금헀는데, 이 글을 쓰면서 많은 분들의 글을 읽어보며 깨달았다. 커버리지가 100퍼가 나오면 좋지만, 통계보다는 내가 얼마나 정교하게 짜는지에 달려있을 것 같다. 나는 아직 테스트 코드를 제대로 시작한지 얼마 되지 않았기 때문에 의식하면서 짜면 실력이 늘 것 같다고 생각했다. 따라서 100퍼를 맞춰볼 생각이긴 하다!
완전히 100퍼에 도달한다기 보다는, 의식하면서 정교한 테스트 코드를 짜보도록 노력할 것이다.
테스트 코드의 중요성
테스트 코드는 단순히 코드가 작동하는지를 확인하는 것 이상의 역할을 한다.
사실 커버리지 기능을 알기 전까지는 테스트 코드의 정교함을 아직은 잘 알지 못했던 것 같다. 커버리지 판단 조건에 분기문 참, 거짓 통과 여부와 구문을 모두 실행시켜야 하는 것, 함수를 모두 실행해봐야하는 것... 등이 들어가는 것을 보고 테스트 코드를 잘 짜게 된다면 정말 꼼꼼한 코드를 짤 수 있겠구나 느꼈다.
마무리 하면서
이번 2주차 스터디 때, 스터디원 분들이 좋은 기능을 알려주셔서 정처기 실기에서만 봤던 단어인데 직접 쓰이는구나 이번에 꼭 실행시켜 봐야지! 라고 생각하고 열심히 커버리지 모드로 돌려보긴 했다. 하지만 커버리지에 대한 지식도 아직 부족하고, 잘 충족시키는 법도 어려운 것 같다.
3주차 때 목표로 잡아서 이렇게 커버리지에 대한 공부도 실제 내 코드에 적용시키면서 해보고, 다른 분들의 테스트 커버리지에 대한 의견도 읽었다. 테스트 커버리지가 100퍼가 나오지 않으면 안 좋은 코드라고만 생각하고 있었는데, 직접 많은 글들을 읽고 정리하면서 결국은 코드를 짜는 프로그래머에게 많은 것이 달려있구나 깨달은 것 같다. 이로서 테스트 코드의 중요성을 더 알게 되었다.
우테코가 이제 마지막 주를 달려간다. 1-3주차 동안 많은 걸 깨닫고 배웠다. 새로운 걸 점점 알아간다는 것은 나를 채워가는 것이라고 생각한다. 4주차 마무리 할 때 쯤에는 후회없이 모두 채워가고 싶다.
참고한 고마운 블로그
참고한 블로그들도 읽어보는 걸 추천한다!
https://hudi.blog/code-coverage/