UnitTest 100% Code Coverage doesn't mean all code is executed
- 아래 코드는 Kotlin2, Kotest5, JDK21에서 작성했다
이번에 최근에 경험한 코드 커버리지도, 유닛테스트도, 심지어 코드리뷰 AI도 찾지 못했던 버그를 하나 경험했다… 진짜 진심으로 찾기 힘들었다 실제 코드는 너무 복잡하니 간단히 만든 코드를 보자
1
2
3
4
5
6
7
8
9
10
11
class MyService {
private fun filterFunction(data: List<Int>): List<Int> {
return data.filter { Random.nextBoolean() }
}
fun foo(data: List<Int>): List<Int> {
return data.chunked(10).flatMap {
return filterFunction(it)
}
}
}
유닛테스트는 아래와 같이 작성했다
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
class MyServiceTest : FunSpec({
val myService = MyService()
test("Function Call Test") {
//given
val maxDataCount = 1000
val testData = (1..maxDataCount).toList()
//when
val filterResult = myService.foo(testData)
//then
filterResult shouldBeSmallerThan testData
testData shouldContainAll filterResult
}
})
위 클래스에 대한 유닛 테스트 커버리지는 당연히 100% 이다
하지만 위 코드에는 치명적인 버그가 있는데 flatMap 에 넘긴 lambda 에서 return 을 하고 있기 때문에 첫번째 chunk 에 대해서만 filterFunction 이 호출되고, 나머지는 모두 버려지게 된다. 하지만 랜덤하게 필터링이 일어나니 호출 수가 잘못 되었다는 것을 눈치 채지 못하였다
정상적인 코드로 바꾸면 아래와 같이 되어야 한다
1
2
3
4
5
6
7
8
9
10
11
class MyService {
private fun filterFunction(data: List<Int>): List<Int> {
return data.filter { Random.nextBoolean() }
}
fun foo(data: List<Int>): List<Int> {
return data.chunked(10).flatMap {
filterFunction(it)
}
}
}
결국 클래스를 쪼개고, UnitTest를 추가하여, 데이터 개수에 따른 filterFunction 의 Call Count 를 검증 하는 식으로 검증을 추가하였다. 다시한번 실수 하지 않도록 주의 하면서 결심한다. 코드 커버리지가 100%라고 모든 코드가 테스트 된 것은 아니라는것을….
This post is licensed under CC BY 4.0 by the author.