- Published on
Software Testing 3 | 코드 커버리지는 거들뿐,,,
- Authors
- Name
- 이건창
Introduction
이번에는 구조적 테스트
와 코드 커버리지
에 관해 이야기를 나눠볼까 해. 옛날에 코드 커버리지를 자주 측정해왔지만, 어느 순간부터 잘 측정하지 않게 되더라. 아마 요구사항 분석과 검증을 통해서도 충분하다고 판단했던 것 같아.
책을 읽고 나서 코드 커버리지
를 어떻게 마주해야 할지 확립 할 수 있었어.
구조적 테스트
구조적 테스트란
구조적 테스트는 소스 코드의 구조를 활용해 테스트하지 못한 영역을 찾아가는 방법을 말해. 즉, 명세 기반 테스트에서 미처 찾지 못한 영역을 확인하는 과정이야.
구조적 테스트를 하는 이유는 문서에 명시되어 있지 않은 언어 구조 특성과 알고리즘 그리고 데이터 구조에 의한 부분을 검증하기 위해서야.
코드 커버리지
현대는 어떤 코드가 테스트되고 있는지 오픈 소스를 통해 쉽게 알 수 있어. 자바나 코틀린 경우 Jacoco
로 쉽게 확인 할 수 있지.

코드 커버리지를 통해 테스트가 수행되지 않은 영역이 있다면 고민해야할 부분은 두 개의 포인트야.
- 왜 그 코드가 수행되지 않았는지 이해하기
- 그 코드가 테스트할 가치가 있는지 결정하기
getter
,equals
,hashCode
처럼 간단한 동작을 하거나 인텔리제이에서 직접 생성해주는 경우 테스트 할 가치가 없다고 판단 할 수도 있어.
커버리지를 측정하는 기준은 다음과 같아. 각 기준에 맞춰 프로그램 구조가 어떻게 관리되는지 판단 할 수 있어.
- 코드 줄 커버리지 :
해당 코드 라인
을 테스트 했는지 - 분기 커버리지 :
분기에 따른 동작
를 테스트 했는지 - 조건 + 분기 커버리지 :
조건에 따라 변하는 동작
를 테스트 했는지 - 경로 커버리지 :
모든 실행 경로
를 테스트 했는지
복잡한 조건인 경우 MC/DC 커버리지를 활용하게 돼. MC/DC는 모든 조건의 조합 중 중요한 조합을 찾아 테스트 했는지를 판단해. 이 때 적어도 한 번은 결과에 영향을 가는 조합만 테스트하게 돼. 이 말이 어떻냐면 다음과 같은 상황을 이야기 해볼게.
다음과 같은 코드는 문자열을 입력받아 숫자인지를 판단하고 싶어.
class Amount(private val stringAmount: String) {
...
init {
if (stringAmount.isBlank()) {
throw IllegalArgumentException("값이 비어있습니다.")
}
if (!isInteger(stringAmount)) {
throw IllegalArgumentException("입력한 $stringAmount 값은 숫자여야 합니다.")
}
}
}
여기에서 할 수 있는 조합은 2*2
로 다음과 같아.
case/valid | isBlank | isInteger |
---|---|---|
case1 | true | true |
case2 | true | false |
case3 | false | true |
case4 | false | false |
그런데, 빈 값이면 숫자인지를 판단 할 필요가 있을까? 반대로 숫자이면 빈 값인지 판단 할 필요가 있을까? 우리는 하나가 true 이면 남은 검증 행위가 결과에 반영되지 않음을 쉽게 판단 할 수 있어. 그래서 해야 할 테스트를 2 가지로 줄일 수 있지.
case/valid | isBlank | isInteger |
---|---|---|
case1 | true | true |
case2 | true | false |
case3 | false | true |
즉, MC/DC 커버리지를 작성 할 때 효과적으로 작성하기 위해서는 적어도 한 번은 결과에 영향을 가는 조합만 테스트해야 해.
경계 테스트
명세 기반 테스트에서 가장 어려운 건 경계를 찾는 일이야. 입력 값의 자료형으로 고민하면 입력에 대한 경계만 판단하기 쉽상이지. 그래서 구조 테스트 과정에서 소스 코드의 조건 분기를 통해 경계 테스트를 쉽게 추가할 수 있어.
반대로 가독성이 떨어져서 판단하기 어려운 경우는 접점, 거점 분석을 통해 경계를 판단할 수 있으니 이런 부분들은 잘 활용해보자.
구조적 테스트만 적용하지 말자.
여기에서 중요한 건 명세 기반 테스트로 고안한 테스트 스위트를 보강한다는 거야. 명세 테스트를 수행하지 않고 구조적 테스트만 수행할 경우 요구사항을 만족하지 못하는 케이스를 판단할 수 없어져. 구조적 테스트는 명세에 대한 지식을 더 했을 때 진가가 드러날 수 있음을 명심하자.
마지막으로
다양한 개발자 분들과 이야기하다보면 코드 커버리지를 싫어하는 분들이 꽤나 있어. 명세를 이해하지 않고 테스트 커버리지 만을 추구하는 건 밑빠진 독에 물붓기나 마찬가지이기 때문임을 이해할 수 있었지. 구조적 테스트를 활용하면 명세 기반 테스트를 강화할 수 있음을 기억하자.
또한 구조적 테스트 과정에서도 여러 가지 발생할 수 있는 조합 중 결과에 영향이 가는 조합 만을 추출해 커버리지를 빠르게 높이는 것을 추구해야 해. 우리는 효율적인 테스터
가 되야함을 잊지말자.