Kotest 빠르게 입문하기

디퐁·2023년 5월 12일
0

시작

맛보기

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

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

Maven

<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

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

"" {

}

FunSpec

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

test("") {

}

ShouldSpec

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

should("") {

}

ExpectSpec

class MyTests : ExpectSpec({
    expect("테스트") {
        // test here
    }
})

expect("") {

}

WordSpec

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

"" should {
	"" {
    
    }
}

FreeSpec

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

"" - {
	"" {
    	
    }
}

DescribeSpec

class MyTests : DescribeSpec({
    describe("score") {
        it("0으로 시작해야 한다") {
            // test here
        }
    }
})

describe("") {
	it("") {
    		
	}
}

FeatureSpec

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

feature("") {
	scenario("") {
    
    }
}

BehaviorSpec

class MyTests : BehaviorSpec({
    given("마법의 빗자루") {
        `when`("앉으면") {
            then("날 수 있어야 한다") {
                // test code
            }
        }
        `when`("던지면") {
            then("다시 되돌아와야 한다") {
                // test code
            }
        }
    }
})

given("") {
	`when`{
    	then {
        
        }
    }
}

AnnotationSpec

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

"hello".length shouldBe 5

shouldThrow

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

Inspectors

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

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

forAll

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

Kotest의 테스트 계층 구조

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에서만 설정

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

글로벌 설정

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

Lifecycle Hooks

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

사용 예시

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

profile
Backend 취준생

0개의 댓글