본문 바로가기

kotlin

Kotest로 깔끔하게 Kotlin 테스트 코드 작성하기

java로 구현하던 spring 프로젝트를 kotlin으로 전환하면서사용하고, 테스트 코드는 어떻게 할지 고민이었다. 

1. 왜 kotest 인가?

  • JUnit보다 간결
  • Kotest에서는 **다양한 테스팅 스타일**을 지원함
  • BDD 테스트 코드 작성에 용이함
  • Kotlin을 위한 테스트 도구이므로 Kotlin 스타일로 작성 가능함
  • 기존의 자바 테스트를 위한 라이브러리인 junit, assert, mockito 등을 이용해서도 Kotlin 테스트 코드를 작성할 수 있지만, 이들은 Kotlin 스타일로 코드를 작성할 수 없어서 코드와 테스트 코드 간의 괴리가 발생하게 됨
    • Mocking이나 Assertion 과정에서 코틀린 DSL 을 활용할 수 없
    • 비즈니스 로직을 코틀린 DSL을 이용해 작성하더라도 테스트에서 예전 방식의 코드를 작성해야함

kotlin 전용 테스트 라이브러리 spock과의 비교 : spock도 kotest와 마찬가지로 간결하고 BDD 테스트 코드 작성에 용이함, kotest보단 한정적인 테스팅 스타일 지원 (given, when, then, where, expect)

2. Kotest Style 선택하기

kotest는 테스트를 위한 많은 레이아웃을 제공한다.

2.1. 대표적인 스타일

  1. Kotest Annoatataion Spec
  2. 기존의 junit방식과 유사한 장점이라 마이그레이션이 필요할 땐 좋지만, kotest의 장점을 느낄 수 없음
  3. Kotest Behavior Spec 
  4. given("a broomstick") { `when`("I sit on it") { then("I should be able to fly") { } } }
  5. Given - When- Then 패턴
  6. Describe Spec
    • DCI (Describe - Context - It) 패턴 사용 
    • Describe : 테스트할 대상
    • Context : 조건 (~가 주어지면), 상황
    • It : 결과 (=then)
    describe("PageRepository 클래스의") {
        context("generateQuery 메소드에") {
            context("필수 옵션만 전달하면") {
                it("필수 옵션만 조건으로 추가한 쿼리를 반환한다.") {
                }
            }
        }
    }
    
  7. String Spec
    • 테스트 구문이 필요하지 않다. 문자열 다음에 바로 람다 표현식으로 작성하면 됨
    "PageRepository 클래스의" {
        "generateQuery 메소드에" {
            "필수 옵션만 전달하면" {
                "필수 옵션만 조건으로 추가한 쿼리를 반환한다." {
     
                }
            }
        }
    }
    
  8. Free spec
    • String Spec과 거의 동일한데 문자열 끝에 - 추가
    "PageRepository 클래스의" - {
        "generateQuery 메소드에" - {
            "필수 옵션만 전달하면" - {
                "필수 옵션만 조건으로 추가한 쿼리를 반환한다." - {
                        1 + 1 shouldBe 2
                }
            }
        }
    }
    
  9. Should Spec
    • context로 조건 중첩, should로 테스트
    context("PageRepository 클래스의") {
        context("generateQuery 메소드에") {
            context("필수 옵션만 전달하면") {
                should("필수 옵션만 조건으로 추가한 쿼리를 반환한다.") {
     
                }
            }
        }
    }
    
  10. Expect Spec
    • context로 조건 중첩, expect로 테스트
    context("PageRepository 클래스의") {
        context("generateQuery 메소드에") {
            context("필수 옵션만 전달하면") {
                expect("필수 옵션만 조건으로 추가한 쿼리를 반환한다.") {
     
                }
            }
        }
    }
    

3. 사용한 스타일 : Describe Spec

아래와 같은 이유로 Describe Spec을 사용했다. describe spec이 아닌 스타일도 junit보다 확싫히 깔끔하다, 자신의 테스트 코드 작성 스타일에 맞춰서 선택하면 좋을 것 같다. 

3.1. 이유

  • annotation spec을 제외하고 모든 스타일이 계층구조로 테스트 코드를 작성하기 쉬움
  • string이나 free와 같이 구문이 없는 스타일
    • 테스트 코드를 작성할 때 depth가 헷갈릴 수 있음
  • Fun, Should, Expect 등과 같이 Context와 테스트 이렇게 구문이 2가지인 스타일
    • Fun, Should, Expect도 처음 Context는 테스트 대상을 명시하기 위해 사용됨
    • 대상을 명시해주는 구문이 따로 있는 Describe Spec이 더 명확하지 않을까,,
  • 우리 프로젝트에서 기존의 테스트 코드 작성 방식에 가장 적합

4. 적용 예시

java - junit

@Nested
@DisplayName("generateQuery 메소드에")
class DescribeGenerateDataQuery {
 
    @Nested
    @DisplayName("필수 옵션만 전달하면")
    class ContextWithRequiredOption {
     
      @Test
      @DisplayName("필수 옵션만 조건으로 추가한 쿼리를 반환한다.")
      public void ItReturnSelectQueryWithRequiredOption() {
        String expectedQuery = ...;
     
        assertThat(PageQueryUtil.generateSelectedPageDataQuery(
            Optional.empty(),
            pageRequestWithRequired
         )).isEqualTo(expectedQuery);
      }
    }
}

kotlin - kotest

describe("generateQuery 메소드에") {
    context("필수 옵션만 전달하면") {
      it("필수 옵션만 조건으로 추가한 쿼리를 반환한다.") {
         val expectedQuery = ...
     
         PageQueryUtil.generateQuery(
            optionWithRequired
         ) shouldBe expectedQuery
      }
    }
}
  • 어노테이션을 사용하지 않고 BDD 테스트 코드를 작성할 수 있어 훨씬 간결함
  • Kotlin 문법과도 괴리감 없이 작성 가능
반응형