반복된 테스트가 필요한 이유
일반적으로 멀티 스레드 환경에서 동작하는 코드를 작성했을 때, 경쟁상태 문제로 인해 간헐적으로 버그가 날 수 있다. 이렇게 간헐적으로 버그를 발생 시키는 코드에 대한 테스트 코드를 작성하면 Flacky Test 문제가 생긴다.
*결과가 일관되지 않는 테스트를 :lacky Test 라고 부른다
이런 경우 JUnit4에서는 테스트를 반복하기 위해서는 반복하는 코드를 직접 넣어주어야 했고, 이렇게 하나의 테스트에서 반복해서 테스트를 실행한다면, 넣는다면 몇 번째 테스트에서 실패했는지, 몇 번이나 테스트를 실패했는지 알 수 없는 문제가 있었다.
@Test
fun dummyTest() {
repeat(100) {
runTest {
val result = contentSearcher.searchByKeyword("Dummy")
assertEquals(emptyList(), result)
}
}
}
이런 문제를 해결하기 위해 JUnit5에서는 @RepeatedTest라는 기능을 제공한다.
@RepeatedTest 사용해 테스트 반복하기
@RepeatedTest를 사용하면, 하나의 테스트를 몇 번 반복할지 정할 수 있으며, 각 테스트가 언제 실패했는지도 알 수 있다. 이를 확인하기 위해, 다음과 같이 calculate 연산을 만들어보자. calculate연산은 50%의 확률로 옳은 정답을 내고 50% 확률로 틀린 정답을 내는 연산이다.
class SimpleTest {
private fun calculate(): Int = if (Random.nextBoolean()) 2 else 3
...
}
이 연산을 100번 실행하는 테스트를 만들기 위해서는 @Test 대신 @RepeatedTest를 사용하면 된다. @RepeatedTest 어노테이션은 정수를 인자로 받으며, 받은 정수 만큼 반복된 연산을 실행한다.
class SimpleTest {
private fun calculate(): Int = if (Random.nextBoolean()) 2 else 3
@RepeatedTest(100)
fun dummyTest() {
val result = calculate()
assertEquals(2, result)
}
}
이제 테스트를 실행해보면 다음과 같은 결과를 볼 수 있다. 100번의 테스트 중 얼만큼이 성공하고 실패했는지 파악하는 것이 가능하다. 또한 개별 테스트가 어떤 값을 내면서 실패했는지도 확인이 가능하다.
정리
멀티 스레드 환경에서 연산이 병렬로 실행될 때 공유 상태가 변경되거나 한다면, 경쟁 상태 문제가 생길 수 있으며 이로 인해 결과가 일관적이지 않을 수 있다. 이런 경우 일반적인 테스트로는 이런 간헐적인 오류를 잡아내기 매우 어렵지만 @RepeatedTest를 사용하면 오류를 잡아내기 쉬워진다. 이 외에도 결과가 랜덤으로 나올 수 있는 많은 상황(시간에 의존적인 경우 등)에 @RepeatedTest를 사용해 코드에 어떤 문제가 있는지 잡아낼 수 있다.