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 | ”” { } |
FunSpec | test(“”) { } |
ShouldSpec | should(“”) { } |
ExpectSpec | expect(“”) { } |
WordSpec | ”” should { “” { } } |
FreeSpec | ”” - { “” { } } |
DescribeSpec | describe(“”) { it(“”) { } } |
FeatureSpec | feature(“”) { scenario(“”) { } } |
BehaviorSpec | given(“”) { `when`(“”) { then(“”) { } } } |
AnnotationSpec | JUnit과 동일하게 사용 |
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