Document
- Document: https://kotest.io/
- Github Reference Dockument: https://github.com/kotest/kotest/wiki/Reference-Doc
Kotest | Kotest
Flexible, powerful and elegant kotlin test framework with multiplatform support
kotest.io
Introduce
- Kotest는 Kotlin을 위한 테스팅 도구
- Kotlin-Test에서 Kotest로 이름이 변경되었으며, 이름에서 알 수 있듯이 Kotlin언어로 작성된 코드를 테스트하기에 아주 용이
- Spock와 유사하게 간결한 코드와 다양한 유형의 테스트 방식을 지원
- Kotest는 multi-platform test framework로, 각 하위 프로젝트는 독립적으로 사용할 수 있습니다.
- Test Framework
- Assertion Library
- Property Testing
 
- 문서화가 잘되어있긴 하지만, 아직 도큐먼트나 참고할 레퍼런스들이 아직 다양하지 않은 것 같다. (2021년 8월 기준)
도입해보게된 배경
- 팀에서 주로 사용하는 Spock framework (Test with Spock framework) 와 Kotlin + Spring 조합 사용 시, Spring Component Annotation을 붙이지 않은 클래스의 mock 객체가 생성 되지 않는 문제가 발생 (실제 내부로직을 타고 들어감)
- 추측되는 원인
- 기본적으로 Kotlin 클래스의 경우, class에 별다른 키워드를 붙이지 않는 경우 final 클래스로 클래스가 생성
- Groovy의 경우, Mockito와 비슷한 형태를 띄고 있고 Mockito의 Mock 또한 final class를 mocking 할 수 없는 문제를 갖고 있어 발생한 문제로 보여짐
- class를 open class로 변경해서 mock 객체를 만드니 성공
 
 
- 추측되는 원인
- Kotlin 친화적인 테스팅 도구 서칭
How to use
Quick Start : 의존성 설정
JVM/Gradle
- 사용할 기능들에 해당하는 라이브러리만 가져다 사용하면 됩니다.
test {
    maxHeapSize = "1024m"
    useJUnitPlatform()
}
...
dependencies {
    testImplementation("io.kotest:kotest-runner-junit5-jvm:$kotestVersion") //Test Framework
    testImplementation("io.kotest:kotest-assertions-core:$kotestVersion")   //Assertions Library
    testImplementation("io.kotest:kotest-property:$kotestVersion")          //Property Testing
    testImplementation("io.kotest:kotest-extensions-spring:$kotestVersion") //Spring Extensions
}
IntelliJ Plugin
Kotest는 Jetbrains 플러그인 마켓플레이스(IntelliJ 내에서 검색)에서 사용할 수 있는 IntelliJ 플러그인을 제공합니다.
이 플러그인은 각 테스트에 대한 실행 아이콘, 테스트 탐색을 위한 도구 창, 중복 테스트 강조 표시, assertion intentions 등을 제공합니다.
Intellij 플러그인을 사용하기 위해서는 Kotest 4.2 이상이 필요합니다.
Testing Style (Spec)
- Kotest는 10가지 스타일의 테스트 레이아웃을 제공합니다. (일부는 다른 인기 있는 테스트 프레임워크에서 영감을 받아 제작)
- Kotest를 사용하려면 테스트 스타일 중 하나를 Extend하는 클래스 파일을 만들어 사용
- 그런 다음 init { } 블록 내에서 테스트 케이스를 만듬
- 스타일 간에 기능적 차이는 없음
- 모두 동일한 유형의 구성(스레드, 태그 등)을 허용
- 테스트를 구성하는 방법은 단순히 선호도의 문제

Example
String Spec (A Kotest original)
class MyTests : StringSpec({
    "strings.length should return size of string" {
        "hello".length shouldBe 5
    }
})
Fun Spec (ScalaTest)
class MyTests : FunSpec({
    test("String length should return the length of the string") {
        "sammy".length shouldBe 5
        "".length shouldBe 0
    }
})
Behavior Spec (BDD Framework)
class MyTests : BehaviorSpec({
    given("a broomstick") {
        //given condition
        `when`("I sit on it") {
            //when condition
            then("I should be able to fly") {
                // test code
            }
        }
        `when`("I throw it away") {
            then("it should come back") {
                // test code
            }
        }
    }
})
Feature Spec (Cucumber)
class MyTests : FeatureSpec({
    feature("the can of coke") {
        scenario("should be fizzy when I shake it") {
            // test here
        }
        scenario("and should be tasty") {
            // test here
        }
    }
})
Annotation Spec (JUnit)
class AnnotationSpecExample : AnnotationSpec() {
 
    @BeforeEach
    fun beforeTest() {
        println("Before each test")
    }
 
    @Test
    fun test1() {
        1 shouldBe 1
    }
 
    @Test
    fun test2() {
        3 shouldBe 3
    }
}
Test Listeners
- 테스트 전/후, Spec 클래스의 모든 테스트 전/후, 전체 프로젝트 전/후 등과 같은 테스트 엔진 생명 주기에 어떤 세팅을 할 때 사용할 수 있도록 TestListener 인터페이스 제공
| Function | 목적 | 
| beforeTest | 테스트 케이스가 실행되기 전에 매번 호출됩니다. 무시되는 테스트(아래의 Test Ignore 참고) 에서는 실행되지 않습니다. | 
| afterTest | 테스트 케이스가 실행된 후 매번 호출됩니다. 무시되는 테스트에서는 실행되지 않습니다. 테스트가 실패하더라도 실행됩니다. | 
| beforeSpec | 모든 beforeTest 함수가 호출되기 전에, Spec이 시작될 때마다 호출됩니다. | 
| afterSpec | 모든 afterTest 함수가 호출된 후 Spec이 완료될 때마다 호출됩니다. | 
| beforeSpecClass | 엔진이 실행할 Spec을 준비할 때 호출됩니다. Spec이 인스턴스화되는 횟수에 관계없이 한 번만 실행됩니다. | 
| afterSpecClass | 사양이 인스턴스화되는 횟수에 관계없이 사양에 대한 모든 테스트가 완료되면 호출됩니다. | 
| beforeProject | 테스트 엔진이 시작되는 즉시 호출됩니다. | 
| afterProject | 테스트 엔진이 완료되는 즉시 호출됩니다. | 
| afterDiscovery | 모든 Spec 클래스를 검색하고, beforeSpec 함수가 호출되기 전과 테스트 엔진에서 Spec을 인스턴스화하기 전에 호출됩니다. | 
Example
class HelloTest : StringSpec({
    lateinit var helloDaoMock: HelloDao
    lateinit var helloService: HelloService
 
    beforeTest {
        helloDaoMock = mockk()
        helloService = HelloService(helloDaoMock)
    }
    ...
}
Mocking
- Kotest 자체에는 모의 기능이 없습니다.
- 하지만 Mock 라이브러리를 쉽게 플러그인하여 사용할 수 있습니다.
- 예를 들어 mockk 을 사용하여 다음과 같이 작성할 수 있습니다.
- mockk: https://mockk.io/
MockK
Provides DSL to mock behavior. Built from zero to fit Kotlin language. Supports named parameters, object mocks, coroutines and extension function mocking
mockk.io
testImplementation "io.mockk:mockk:{version}"
Example
class MyTest : FunSpec({
 
    val repository = mockk<MyRepository>()
    val target = MyService(repository)
 
    test("Saves to repository") {
        every { repository.save(any()) } just Runs
        target.save(MyDataClass("a"))
        verify(exactly = 1) { repository.save(MyDataClass("a")) }
    }
 
    test("Saves to repository as well") {
        every { repository.save(any()) } just Runs
        target.save(MyDataClass("a"))
        verify(exactly = 1) { repository.save(MyDataClass("a")) }
    }
 
})
Matchers
- Matchers는 Variable 또는 Function이 특정 값을 가져야 한다고 Assert 할 때 사용
- Kotest에는 100개 이상의 내장 Matcher가 있습니다.
Matcher의 Two Styles
- Extension functions
- Infix functions
Example
class MatcherTest: StringSpec({
    "Simple Test"{
    	//haveLength(): length 체크
        "Lorem ipsum dolor" shouldBe haveLength(17)
        
        //include(): 파라미터가 포함되어있는지 체크
        "Lorem ipsum dolor" should include("ipsum")
        
        //endWith(): 파라미터가 끝에 포함되는지 체크
        "Lorem ipsum dolor" should endWith("lor")           
        
        //match(): 파라미터가 매칭되는지 체크
        "Lorem ipsum dolor" should match("Hello World")     
        
        //shouldBeLowerCase(): 소문자로 작성되어있는지 체크
        "Lorem ipsum dolor".shouldBeLowerCase()             
 
        val list = emptyList<String>()                       
        val list2 = listOf("aaa", "bbb", "ccc")
        val map = mapOf<String, String>(Pair("aa", "11"))
        
        //beEmpty(): 원소가 비어있는지 체크
        list should beEmpty()         
        
        //sorted<T>(): 해당 자료형이 정렬되어있는지 체크
        list2 shouldBe sorted<String>()                
        
        //contain(): 해당 원소가 포함되어있는지 체크
        map should contain("aa", "11")       
        
        //haveKey(): 해당 key가 포함되어있는지 체크
        map should haveKey("aa")         
        
        //haveValue(): 해당 value가 포함되었는지 체크
        map should haveValue("11")                          
    }
})
Kotest Extension
Kotest는 다른 많은 라이브러리 및 프레임워크와 통합됩니다. 일부는 Kotest 팀에서 제공하고 나머지는 3rd parties에서 유지 관리 및 호스팅합니다.

JUnit vs Spock vs Kotest
Ref. https://veluxer62.github.io/explanation/comparing-testing-library-for-kotlin/
'개발 > Java, Kotlin' 카테고리의 다른 글
| [Effective Kotlin] Item 24: Consider variance for generic types (0) | 2021.09.09 | 
|---|---|
| [Effective Kotlin] Item 17: Consider naming arguments (1) | 2021.08.25 | 
| [Effective Kotlin] Item 10: Write unit tests (0) | 2021.08.12 | 
| 가비지컬렉션 (Garbage Collection) (0) | 2020.12.29 | 
| JVM (Java Virtual Machine) (0) | 2020.12.28 | 
 
										
									 
										
									 
										
									 
										
									
댓글