Post

Kotest 빠르게 입문하기

Kotest를 즉시 사용할 수 있도록 빠르게 훑는다.

시작

맛보기

1
2
3
4
5
6
7
8
9
10
class KotestExample : StringSpec({
    "Strings" {
        "length는 문자열의 길이를 반환해야 한다" {
            "hello".length shouldBe 5
        }
    }
    "중첩 하지 않아도 됨" {
    	"hello".length shouldBe 5
    }
})

위 예시는 Kotest가 제공하는 10가지의 테스트 스타일 중 하나인 StringSpec의 사용 예시다.

Kotlin DSL을 적극 활용하여 문법이 매우 심플하다. 테스트 함수를 만들기 위해 public void, fun 키워드를 사용하거나 @Test 어노테이션을 붙이지 않아도 되며,

가독성 떨어지고 매개변수 순서 헷갈리는 assertEquals(5, "hello".length) assert 메서드 호출 대신 shouldBe 키워드를 사용하면 된다.

Dependency 추가

사용하기 위해 Dependency를 빌드 파일에 추가한다.

Gradle

1
2
testImplementation 'io.kotest:kotest-runner-junit5:5.6.2'
testImplementation 'io.kotest:kotest-assertions-core:5.6.2'

Maven

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
<plugin>
   <groupId>org.apache.maven.plugins</groupId>
   <artifactId>maven-surefire-plugin</artifactId>
   <version>2.22.2</version>
</plugin>
<dependency>
   <groupId>io.kotest</groupId>
   <artifactId>kotest-runner-junit5-jvm</artifactId>
   <version>5.6.2</version>
   <scope>test</scope>
</dependency>
<dependency>
   <groupId>io.kotest</groupId>
   <artifactId>kotest-assertions-core-jvm</artifactId>
   <version>5.6.2</version>
   <scope>test</scope>
</dependency>

Testing Styles

  • Kotest는 무려 10개의 테스트 스타일을 제공한다.
  • 스타일 간 기능 차이는 전혀 없다.
    • 고민할 필요 없이 마음에 드는 걸 골라서 쓰면 된다.
    • 마음 속에 하나 정했다면 스크롤 바로 내려버리자.
스타일형태
StringSpec”” { }
FunSpectest(“”) { }
ShouldSpecshould(“”) { }
ExpectSpecexpect(“”) { }
WordSpec”” should { “” { } }
FreeSpec”” - { “” { } }
DescribeSpecdescribe(“”) { it(“”) { } }
FeatureSpecfeature(“”) { scenario(“”) { } }
BehaviorSpecgiven(“”) { `when`(“”) { then(“”) { } } }
AnnotationSpecJUnit과 동일하게 사용

StringSpec

1
2
3
4
5
class MyTests : StringSpec({
    "strings.length는 문자열의 길이를 반환해야 한다" {
        "hello".length shouldBe 5
    }
})

1
2
3
"" {

}

FunSpec

1
2
3
4
5
6
class MyTests : FunSpec({
    test("String length는 문자열의 길이를 반환해야 한다") {
        "sammy".length shouldBe 5
        "".length shouldBe 0
    }
})

1
2
3
test("") {

}

ShouldSpec

1
2
3
4
5
6
class MyTests : ShouldSpec({
    should("문자열의 길이를 반환한다") {
        "sammy".length shouldBe 5
        "".length shouldBe 0
    }
})

1
2
3
should("") {

}

ExpectSpec

1
2
3
4
5
class MyTests : ExpectSpec({
    expect("테스트") {
        // test here
    }
})

1
2
3
expect("") {

}

WordSpec

1
2
3
4
5
6
7
8
class MyTests : WordSpec({
    "String.length" should {
        "문자열의 길이를 반환한다" {
            "sammy".length shouldBe 5
            "".length shouldBe 0
        }
    }
})

1
2
3
4
5
"" should {
	"" {
    
    }
}

FreeSpec

1
2
3
4
5
6
7
8
class MyTests : FreeSpec({
    "String.length" - {
        "문자열의 길이를 반환해야 한다" {
            "sammy".length shouldBe 5
            "".length shouldBe 0
        }
    }
})

1
2
3
4
5
"" - {
	"" {
    	
    }
}

DescribeSpec

1
2
3
4
5
6
7
class MyTests : DescribeSpec({
    describe("score") {
        it("0으로 시작해야 한다") {
            // test here
        }
    }
})

1
2
3
4
5
describe("") {
	it("") {
    		
	}
}

FeatureSpec

1
2
3
4
5
6
7
class MyTests : FeatureSpec({
    feature("한 캔의 콜라") {
        scenario("흔들면 거품이 나야 한다") {
            // test here
        }
    }
})

1
2
3
4
5
feature("") {
	scenario("") {
    
    }
}

BehaviorSpec

1
2
3
4
5
6
7
8
9
10
11
12
13
14
class MyTests : BehaviorSpec({
    given("마법의 빗자루") {
        `when`("앉으면") {
            then("날 수 있어야 한다") {
                // test code
            }
        }
        `when`("던지면") {
            then("다시 되돌아와야 한다") {
                // test code
            }
        }
    }
})

1
2
3
4
5
6
7
given("") {
	`when`{
    	then {
        
        }
    }
}

AnnotationSpec

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
class AnnotationSpecExample : AnnotationSpec() {

    @BeforeEach
    fun beforeTest() {
        println("Before each test")
    }

    @Test
    fun test1() {
        1 shouldBe 1
    }

    @Test
    fun test2() {
        3 shouldBe 3
    }
}

JUnit과 동일

Assertions

Matchers

자주 사용하는 shouldBe와 shouldThrow만 소개한다. 나머지 Matcher들이 궁금하다면 참고

shouldBe

1
"hello".length shouldBe 5

shouldThrow

1
2
3
4
val exception = shouldThrow<IllegalAccessException> {
   // IllegalAccessException 예외를 던질 것으로 예상되는 코드
}
exception.message shouldBe "예외 메시지"

Inspectors

Inspector는 컬렉션의 원소들을 한번에 테스트할 수 있게 해준다.

자주 사용하는 forAll만 소개한다. 나머지 Inspector들이 궁금하다면 참고

forAll

1
2
3
4
val names = listOf("sam", "smith", "sassy", "samuel")
names.forAll {
    it.shouldStartWith("s")
}

Kotest의 테스트 계층 구조

1
2
3
4
5
6
7
8
9
10
class KotestExample : StringSpec({ // Spec
    "Strings" { // Container
        "length는 문자열의 길이를 반환해야 한다" { // TestCase
            
        }
    }
    "중첩 하지 않아도 됨" { // TestCase
    	
    }
})

Spec ⊃ Container ⊃ TestCase의 구조로 되어있다.

Spec은 테스트 클래스(class KotestExample)를 뜻한다. Container는 중첩을 위해 한번 더 괄호로 묶은 부분("Strings" { })을 뜻한다 TestCase는 가장 안쪽의 괄호를 뜻한다.

이 계층 구조를 알아야 Isolation Modes와 Lifecycle Hooks를 이해할 수 있다.

Isolation Modes

모든 Container와 TestCase에 대해 각각의 인스턴스를 만들어 격리시킬지, TestCase에 대해서만 각각의 인스턴스를 만들어 격리시킬지, 격리하지 않고 한 인스턴스 안에서 모든 Container와 TestCase를 실행시킬지 결정하는 부분이다.

종류

SingleInstance

  • 한 Spec 안에 있는 모든 Container와 TestCase가 하나의 인스턴스에서 실행되게 한다
  • 기본값

InstancePerTest

  • Container, TestCase에 대해 각각의 인스턴스를 만든다.

InstancePerLeaf

  • TestCase에 대해서만 각각의 인스턴스를 만든다.

설정 방법

특정 Spec에서만 설정

1
2
3
class MyTestClass : WordSpec({
 isolationMode = IsolationMode.SingleInstance
})

글로벌 설정

1
2
3
class ProjectConfig: AbstractProjectConfig() {
   override val isolationMode = IsolationMode.InstancePerLeaf
}

Lifecycle Hooks

JUnit으로 치면 @BeforeEach @AfterEach와 같은 것을 다루는 부분이다. 테스트 전/후의 어떤 시점에 특정 작업을 수행하도록 한다.

사용 예시

1
2
3
4
5
6
7
8
9
10
11
12
13
class TestSpec : WordSpec({
  beforeTest {
    println("Starting a test $it")
  }
  afterTest { (test, result) ->
    println("Finished spec with result $result")
  }
  "this test" should {
    "be alive" {
      println("Johnny5 is alive!")
    }
  }
})

종류

beforeContainer/afterContainer

  • Container가 실행되기 전과 후

beforeEach/afterEach

  • TestCase가 실행되기 전과 후

beforeTest/afterTest

  • Container 또는 TestCase가 실행되기 전과 후

beforeSpec/afterSpec

  • Spec이 인스턴스화 되기 전/후

prepareSpec/afterSpec

  • Spec이 최초로 인스턴스화 되기 전/후

그 외 할 수 있는 것

  • # 한 테스트가 여러 번 실행되게 설정하기 (invocations)
  • # 특정 테스트 비활성화하기
  • # Data Driven Testing (Inspectors 비슷한 것)
  • # Extensions (재사용 가능한 생명주기 Hook)
  • # Coroutines
  • # Non-deterministic Testing (비동기로 작동하는 것에 대해 테스트 해야 할 때 사용)
  • # 테스트 순서 정하기
  • # 테스트 그루핑하기 (Windows, Mac 등으로 그루핑하여 운영체제에 따라 특정 그룹 테스트만 실행되도록 하는 등으로 활용)
  • # 테스트가 끝난 뒤 AutoClosable 객체 close()하기
  • # 임시파일/임시폴더 만들기
  • # Test Factories
  • # 테스트 진행 중 하나라도 실패하면 이후 테스트 모두 스킵
  • # Soft Assertions (테스트가 실패하더라도 경고만 띄우고 실패로 처리하지 않음)
  • # Clues (Assertion에 대해 설명 제공하기)
  • # Property-based Testing (기존 테스트 코드처럼 입력 값을 개발자가 직접 주지 않고, 입력 값이 만족해야 하는 속성[조건]을 개발자가 제시하면, 음의 무한, 양의 무한 등 다양한 입력값을 자동으로 넣어서 테스트해주는 기능)

참고 자료

https://kotest.io

This post is licensed under CC BY 4.0 by the author.

Trending Tags