어떤 언어로 개발하든, 테스트 코드는 작성할 수 있다.
테스트 코드가 필요한 이유는 작성한 코드가 제대로 작동하는지 검증하는 이유도 있지만, 코드로 표현되는 문서화와 요구 사항의 증명이라고 생각한다.
그만큼 테스트 코드는 중요하고 가독성 있게 잘 작성할 수 있어야 한다.
테스트 코드 작성을 위해 언어마다 제공되는 대표적인 테스트 프레임워크가 있다.
해당 테스트 프레임워크를 사용하면 쉽고 빠르게 테스트 코드 작성이 가능하다.
Java를 사용하면 주로 JUnit
을 사용한다.
코틀린 또한 JVM 위에서 동작하고, Java로 작성된 코드 또한 호환이 가능하기 때문에 JUnit을 사용할 수 있다.
하지만 굳이 코틀린을 사용하면서 JUnit만 사용할 필요는 없다.
코틀린과 Java를 비교했을 때 코틀린이 가지는 장점은 간결한 문법과 확장 함수와 중위 함수를 사용할 수 있다는 점이다.
따라서 Java로 작성된 Junit을 사용하면 코틀린이 가지는 장점을 누릴 수 없다.
이러한 점으로 코틀린에서는 Kotest
라는 테스트 프레임워크를 제공한다.
Kotest는 코틀린이 가지고 있는 확장 함수와 중위 함수를 토대로 DSL을 제공하기에 가독성이 높고 효과적인 테스트 코드를 작성할 수 있다.
그러면 Kotest에 대해 자세히 알아보자
Kotest는 코틀린으로 작성된 테스트 프레임워크이다.
Kotest의 특징 중 하나는 여러 개의 독립형 하위 프로젝트로 나뉘어 있다.
Kotest는 JVM 위에서 동작할 때 JUnit 플랫폼을 사용한다.
따라서 JUnit 프레임워크가 설치되어 있어야 한다.
스프링 부트를 사용한다면 JUnit이 기본으로 설치되어 있기 때문에 Kotest 의존성만 추가하면 된다.
val kotestVersion = "5.7.2"
dependencies {
// Test framework
testImplementation("io.kotest:kotest-runner-junit5:$kotestVersion")
// Assertion library
testImplementation("io.kotest:kotest-assertions-core:$kotestVersion")
}
Assertion library가 나뉘어져 있기에, Kotest를 사용하면서 JUnit5 또는 AssertJ의 Assertion을 사용할 수 있다.
역으로, JUnit에서 Kotest의 Assertion 또한 사용할 수 있다.
class JUnit5Test {
@Test
fun `1 + 2는 3이다`() {
1 + 2 shouldBe 3
}
}
이러한 유연함은 테스트 코드 작성 시 선택의 폭을 넓혀준다.
JUnit을 사용했을 때는 단순히 다음과 같이 테스트 코드를 작성할 수 있었다.
class JUnit5Test {
@Test
void myTest() {
...
}
@Nested
class outer {
@Test
void inner() {
...
}
}
}
JUnit의 방식도 나쁘지 않지만, BDD
기반의 테스트 코드를 작성하기에는 부족한 면이 있다.
따라서 Given-When-Then
주석을 달아 테스트의 행위를 설명하거나, @Nested
와 내부 클래스를 활용하여 행위를 설명한다.
class JUnit5Test {
@Test
@DisplayName("1과 2를 더하면 3이다.")
void one_plus_two_is_three() {
// given
int first = 1
int second = 2
// when
int actual = first + second
// then
assertThat(actual).isEqualTo(3)
}
@Nested
@DisplayName("1과 2를 더하면")
class outer {
val actual = 1 + 2
@Test
@DisplayName("3이다.")
fun inner() {
assertThat(actual).isEqualTo(3)
}
}
}
이러한 JUnit의 한정적인 기능 때문에 @DisplayName
을 사용해야 하고, 클래스 명, 메소드 명을 중복적으로 작성해야 하는 문제점이 생긴다.
게다가 클래스, 메서드 명을 지을 때는 제약이 있으므로 불편함 또한 발생한다.
이러한 점 때문에 JUnit을 사용하면 BDD 기반의 테스트 코드를 작성하는 것이 힘들다.
하지만 Kotest를 사용하면 이러한 JUnit의 문제점을 해결할 수 있다.
// 여러 스타일 중 BehaviorSpec을 사용했다.
class KotestTest : BehaviorSpec({
// given("1과 2가 있고")
Given("1과 2가 있고") {
val one = 1
val two = 2
// `when`("1과 2를 더하면")
When("1과 2를 더하면") {
val actual = one + two
// then("3이다")
Then("3이다.") {
actual shouldBe 3
}
}
}
})
메서드 또는 클래스 명을 생각하느라 시간을 쓸 필요가 없고, @Nested
, @Test
어노테이션 또한 필요 없다.
그저 Kotest Spec 클래스를 상속하고, 상속한 클래스의 매개변수에 람다를 정의하고, 람다에 테스트 코드를 작성하면 된다.
이처럼 Kotest를 사용하면 가독성이 좋고 간결하게 테스트 코드를 작성할 수 있다.
Kotest는 여러 가지의 테스트 스타일을 제공한다.
테스트 스타일 | 영감 받은 테스트 |
---|---|
Fun Spec | ScalaTest |
Describe Spec | Javascript frameworks and RSpec |
Should Spec | A Kotest original |
String Spec | A Kotest original |
Behavior Spec | BDD frameworks |
Free Spec | ScalaTest |
Word Spec | ScalaTest |
Feature Spec | Cucumber |
Expect Spec | A Kotest original |
Annotation Spec | JUnit |
위에서 사용한 스타일은 Behavior Spec이다.
몇 가지 스타일을 간단히 알아보자.
FunSpec
은 ScalaTest에서 영감을 받은 스타일이다.
다음과 같이 사용할 수 있다.
class KotestTest : FunSpec({
test("1과 2를 더하면 3이다.") {
val actual = 1 + 2
actual shouldBe 3
}
})
StringSpec
을 사용하면 구문을 최소화 할 수 있다.
class KotestTest : FunSpec({
"1과 2를 더하면 3이다." {
val actual = 1 + 2
actual shouldBe 3
}
})
BehaviorSpec
을 사용하면 BDD 스타일의 테스트 코드를 쉽게 작성할 수 있다.
// 여러 스타일 중 BehaviorSpec을 사용했다.
class KotestTest : BehaviorSpec({
// given("1과 2가 있고")
Given("1과 2가 있고") {
val one = 1
val two = 2
// `when`("1과 2를 더하면")
When("1과 2를 더하면") {
val actual = one + two
// then("3이다.")
Then("3이다.") {
actual shouldBe 3
}
}
}
})
when
은 코틀린의 예약어이기 때문에 백틱을 사용하여 `when()` 과 같이 사용하거나,Given(), When(), Then()
을 메서드를 사용하면 된다.
DescribeSpec
을 사용하면 describe()
, it()
메서드를 통해 테스트 코드를 작성할 수 있다.
또한, DCI 패턴을 사용하여 BDD 스타일과 차별점을 둔 테스트를 작성할 수 있다.
class KotestTest : DescribeSpec({
describe("plus") {
context("1과 2를 더하면") {
val actual = 1 + 2
it("3이다.") {
actual shouldBe 3
}
}
}
})
AnnotationSpec
을 사용하면 JUnit 테스트 코드 스타일로 테스트를 작성할 수 있다.
class KotestTest : AnnotationSpec({
@Test
fun `1과 2를 더하면 3이다`() {
val actual = 1 + 2
actual shouldBe 3
}
})
AnnotationSpec은 JUnit을 사용하는 것보다 많은 이점을 제공하지 않지만, 기존 JUnit으로 작성된 테스트를 Kotest로 마이그레이션 할 때 변경 없이 가져올 수 있다.
테스트 코드는 단순 검증을 넘어, 사람이 읽었을 때 가독성이 좋고 쉽게 요구 사항의 증명을 나타낼 수 있어야 한다.
이는 코틀린과 Kotest의 조합으로 쉽게 표현할 수 있다.
Kotest가 가지는 여러 스타일의 테스트 코드로 효과적인 테스트 코드를 작성하여 개발의 편의성과 가독성이 높은 테스트 코드를 작성할 수 있다.
또한 공식 문서에서 나와 있듯 한 가지 스타일만 고집하여 사용하지 않고, 상황에 맞게 효과적인 스타일을 사용해야 한다.
참고할 점은 Kotest를 Spring에 종속적인 테스트를 수행할 때 Spring에 대한 지원이 기본으로 제공되지 않기 때문에 별도 라이브러리를 추가해야 하며, 추가로 설정해야 하는 부분이 있으므로 주의해야 한다.
코테스트 정리 감사합니다