코틀린에서는 아래와 형태와 같은 DSL(Domain Specific Language) 스타일의 중괄호를 활용한 코드 스타일을 제공한다. 코틀린 내부에서 제공하는 Standard library 대부분도 DSL을 이용해 작성된 것을 볼 수 있다.
하지만 기존에 사용하던 Junit과 AssertJ, Mockito를 사용하면 Mocking이나 Assertion 과정에서 코틀린 DSL 을 활용할 수 없다.
기존에 JAVA에서 사용하던 Junit
, Assertion
, Mockito
등의 테스트 프레임워크 대신에, Kotest나 Mockk와 같은 도구들을 사용하면 아래처럼 코틀린 DSL과 Infix를 사용해 코틀린 스타일의 테스트 코드를 작성할 수 있다.
Kotest
Kotest 는 코틀린 진영에서 가장 많이 사용되는 테스트 프레임워크아다. 코틀린 DSL을 활용해 테스트 코드를 작성할 수 있으며 아래와 같은 기능들을 포함하고 있다.
다양한 테스트 레이아웃(String Spec, Describe Spec, Behavior Spec 등) 제공
Kotlin DSL 스타일의 Assertion 기능 제공
데이터 기반 테스트로 많은 양의 매개변수도 테스트 가능
모든 테스트에 대해 호출수, 병렬 처리, 시간제한, 테스트 그룹화, 조건부 비활성 등의 미세 조정 테스트 가능
중첩 테스트기능 제공
동적 테스트 제공 (런타임에 조건부로 테스트를 추가 가능)
테스트 수명주기에 맞는 다양한 콜백을 제공
Kotest를 사용하기 위해서는 아래와 같은 설정 / 의존성 추가가 필요하다.
testImplementation ( "io.kotest:kotest-runner-junit5:${Versions.KOTEST}" )
testImplementation ( "io.kotest:kotest-assertions-core:${Versions.KOTEST}" )
Kotest는 테스트를 위한 많은 레이아웃을 제공한다.
Annotation Spec
Behavior Spec
Describe Spec
Fun Spec
…
모든 Spec에 대한 정보는 여기서 확인해보자.
Kotest Annotation Spec
기존 Junit 방식과 가장 유사한 방식이다. 별 다른 장점이 없는 레이아웃이지만 Junit에서 Kotest로의 마이그레이션이 필요한 상황이라면 나쁘지 않은 선택이 될 수 있다.
internal class CalculatorAnnotationSpec: AnnotationSpec() {
private val sut = Calculator ()
fun `1과 2를 더하면 3이 반환된다` () {
val result = sut. calculate ( "1 + 2" )
fun `식을 입력하면, 해당하는 결과값이 반환된다` () {
calculations. forAll { (expression, answer) ->
val result = sut. calculate (expression)
fun `입력값이 null 이거나 빈 공백 문자일 경우 IllegalArgumentException 예외를 던진다` () {
shouldThrow <IllegalArgumentException> {
fun `사칙연산 기호 이외에 다른 문자가 연산자로 들어오는 경우 IllegalArgumentException 예외를 던진다 ` () {
shouldThrow <IllegalArgumentException> {
private val calculations = listOf (
"1 + 2 + 3 + 4 + 5" to 15.0
private val blanks = listOf ( "" , " " , " " )
private val invalidInputs = listOf ( "1 & 2" , "1 + 5 % 1" )
Kotest Behavior Spec
기존 스프링 기반 프로젝트에서 작성하던 Given, When, Then 패턴을 Kotest Behavior Spec을 활용해 간결하게 정의할 수 있다.
internal class CalculatorBehaviorSpec : BehaviorSpec({
val result = sut. calculate (expression)
calculations. forAll { (expression, answer) ->
val result = sut. calculate (expression)
`when` ( "입력값이 null이거나 빈 값인 경우" ) {
then ( "IllegalArgumentException 예외를 던진다" ) {
shouldThrow <IllegalArgumentException> {
`when` ( "사칙연산 기호 이외에 다른 연산자가 들어오는 경우" ) {
then ( "IllegalArgumentException 예외를 던진다" ) {
shouldThrow <IllegalArgumentException> {
private val calculations = listOf (
"1 + 2 + 3 + 4 + 5" to 15.0
private val blanks = listOf ( "" , " " , " " )
private val invalidInputs = listOf ( "1 & 2" , "1 + 5 % 1" )
Kotest Describe Spec
Kotest는 Describe Spec을 통해 DCI(Describe, Context, It) 패턴 형태의 레이아웃도 제공한다.
internal class CalculatorDescribeSpec : DescribeSpec({
it ( "해당 식에 대한 결과값이 반환된다" ) {
calculations. forAll { (expression, data ) ->
val result = sut. calculate (expression)
val result = sut. calculate ( "1 / 0" )
result shouldBe Double.POSITIVE_INFINITY
context ( "입력값이 null이거나 공백인 경우" ) {
it ( "IllegalArgumentException 예외를 던진다" ) {
shouldThrow <IllegalArgumentException> {
context ( "사칙연산 기호 이외에 다른 문자가 연산자로 들어오는 경우" ) {
it ( "IllegalArgumentException 예외를 던진다" ) {
shouldThrow <IllegalArgumentException> {
val calculations = listOf (
"1 + 2 + 3 + 4 + 5" to 15.0
val blanks = listOf ( "" , " " , " " )
val invalidInputs = listOf ( "1 & 2" , "1 + 5 % 1" )
위와 같은 여러 레이아웃 중 프로젝트 상황에 가장 잘 맞는 레이아웃을 골라 사용하면 된다.
상황에 따라 kotest 플러그인 을 깔아야 test 실행 버튼이 나타날 수도 있다.
Kotest with @SpringBootTest
@SpringBootTest
와 같은 통합 테스트에서도 Kotest의 테스트 레이아웃을 사용할 수 있다.
사용을 위해서는 아래와 같은 spring extension 의존성의 추가가 필요하다.
testImplementation ( "io.kotest:kotest-extensions-spring:${Versions.KOTEST}" )
internal class CalculatorSpringBootSpec : DescribeSpec() {
override fun extensions () = listOf (SpringExtension)
private lateinit var calculatorService: CalculatorService
this . describe ( "calculate" ) {
it ( "해당 식에 대한 결과값이 반환된다" ) {
calculations. forAll { (expression, data ) ->
val result = calculatorService. calculate (expression)
private val calculations = listOf (
"1 + 2 + 3 + 4 + 5" to 15.0
Kotest Isolation Mode
Kotest는 테스트 간 격리에 대한 설정을 제공하고 있다.
SingleInstance – Default
InstancePerTest
InstancePerLeaf
Kotest에서는 테스트 간 격리 레벨에 대해 디폴트로 SingleInstance를 설정하고 있는데, 이 경우 Mocking 등의 이유로 테스트 간 충돌이 발생할 수 있다. 따라서 테스트간 완전한 격리를 위해서는 아래와 같이 IsolationMode를 InstancePerLeaf로 지정해 사용해야 한다.
internal class CalculatorDescribeSpec : DescribeSpec({
isolationMode = IsolationMode.InstancePerLeaf