개념
"이 함수, 테스트가 몇 개나 필요할까?"
레슨 7에서 구문·분기·조건 커버리지를 배웠다. 그런데 실무에서 코드를 받으면 가장 먼저 드는 궁금증은 이거다 — "이 함수를 제대로 테스트하려면 케이스가 대체 몇 개나 필요한 거지?" 감으로 "많아 보인다"고 답하는 대신, 이 질문에 숫자로 답하는 방법이 있다.
순환 복잡도(Cyclomatic Complexity)란
토마스 매케이브(Thomas McCabe)가 1976년에 제안한 지표로, 코드 안에 독립적인 경로가 몇 개 있는지를 숫자 하나로 나타낸다. 계산법은 단순하다 — 분기를 만드는 요소(if, while, for, case, &&, ||)의 개수 + 1.
function getShippingFee(amount, isMember) {
if (amount >= 30000 && isMember) { // if 1개 + && 1개 = 분기 2개
return 0;
}
return 3000;
}이 함수의 순환 복잡도는 2(분기) + 1 = 3이다. 레슨 7에서 이
함수를 100% 조건 커버리지로 만드는 데 세 번의 호출
(50000,true/50000,false/10000,true)이 필요했던 걸 떠올려보면,
순환 복잡도 숫자가 실제로 필요했던 최소 테스트 케이스 수와
거의 일치한다는 걸 알 수 있다. 이게 우연이 아니다 — 순환
복잡도는 정의상 "이 코드의 모든 독립 경로를 한 번씩 실행하는 데
필요한 최소 테스트 케이스 수"이기 때문이다.
복잡도가 커지면 무슨 일이 생기는가
복잡도 1~10 : 단순하고 테스트하기 쉬움
복잡도 11~20 : 조금 복잡함. 리팩터링을 고려할 만함
복잡도 21~50 : 복잡하고 위험함. 결함이 숨어있을 가능성 높음
복잡도 50 이상: 테스트도 유지보수도 사실상 불가능한 수준이 기준은 절대적인 법칙은 아니지만, 복잡도가 10을 넘는 함수는 버그 밀도가 통계적으로 높다는 연구가 여러 번 반복 확인됐다. SonarQube 같은 정적 분석 도구(레슨 7)가 "이 함수는 복잡도가 높습니다"라고 경고하는 게 바로 이 지표다.
경로 테스트(Path Testing) — 숫자를 실제 케이스로
순환 복잡도가 "몇 개가 필요한가"를 알려준다면, 경로 테스트는 "그 각각이 정확히 무엇인가"를 찾는 기법이다.
function classify(score, hasBonus) {
if (score >= 90) {
return hasBonus ? "S+" : "S";
} else if (score >= 70) {
return "A";
}
return "F";
}이 함수의 독립 경로 4가지를 그림 대신 표로 나열하면 이렇다.
| 경로 | 조건 | 결과 |
|---|---|---|
| 1 | score=95, hasBonus=true | S+ |
| 2 | score=95, hasBonus=false | S |
| 3 | score=75 | A |
| 4 | score=50 | F |
이 네 줄이 곧 네 개의 테스트 케이스다. 경로 테스트는 "감으로 케이스를 떠올리는 것"이 아니라, 코드 구조 자체에서 빠짐없이 케이스를 뽑아내는 체계적인 방법이다. 레슨 1(동등분할·경계값)이 "입력값 관점"에서 케이스를 뽑았다면, 경로 테스트는 "코드가 갈 수 있는 모든 길" 관점에서 뽑는다는 점이 다르다.
그레이박스 테스트와의 연결
레슨 7에서 화이트박스(코드를 보고 테스트 설계)와 명세 기반 기법(코드를 안 보고 설계)을 구분했다. 실무에서는 이 둘을 완전히 분리하기보다, 코드 구조를 대략 참고하면서 명세 기반으로 테스트를 설계하는 경우가 많다 — 이걸 **그레이박스 테스트(Grey Box Testing)**라고 부른다. 예를 들어 이 레슨의 순환 복잡도 지표로 "이 함수는 복잡하니 더 꼼꼼히 봐야겠다"고 판단한 뒤, 실제 테스트 케이스는 명세 기반으로 설계하는 것이 전형적인 그레이박스 접근이다 — 코드 내부를 완전히 몰라도, 복잡도라는 힌트만으로 어디에 힘을 쏟을지 판단할 수 있다.
실무에서 왜 필요한가
"이 PR, 테스트가 부족해 보이는데 얼마나 더 필요할까요?"라는 질문에 순환 복잡도는 근거 있는 답을 준다 — "이 함수 복잡도가 12인데 테스트 케이스는 3개뿐이니, 최소 9개는 더 있어야 독립 경로를 다 커버합니다." 이건 모듈 10(단위 테스트)에서 리뷰할 때 "테스트가 충분한가"를 감이 아니라 숫자로 논쟁할 수 있게 해준다. 또한 코드 리뷰에서 복잡도가 유난히 높은 함수를 미리 짚어내면, 버그가 나기 전에 "이 함수부터 쪼개는 게 어떨까요?"라고 제안할 수 있다 — 레슨 1에서 배운 "일찍 찾을수록 싸다"는 원칙이 설계 단계에도 적용되는 사례다.