Kotlin과 JUnit으로 Java 코드 테스트하기 – 튜토리얼
Kotlin과 JUnit으로 Java 코드 테스트하기 – 튜토리얼
Kotlin은 Java와 완벽하게 상호운용돼요. 그래서 Java 코드를 Kotlin으로 테스트를 작성해서, 같은 프로젝트 안에서 기존 Java 테스트와 함께 실행할 수 있답니다.
이 튜토리얼에서는 다음을 배워볼게요.
- Java–Kotlin 혼합 프로젝트를 구성해서 JUnit으로 테스트를 실행하는 법
- Java 코드를 검증하는 Kotlin 테스트를 추가하는 법
- Maven이나 Gradle로 테스트를 실행하는 법
참고: 시작하기 전에 준비물을 확인하세요.
- IntelliJ IDEA 또는 Kotlin 확장이 설치된 VS Code
- Java 17 이상
프로젝트 구성하기
- IDE에서 버전 관리로부터 샘플 프로젝트를 클론해요.
https://github.com/kotlin-hands-on/kotlin-junit-sample.git
initial모듈로 이동해서 프로젝트 구조를 살펴봐요.
kotlin-junit-sample/
├── initial/
│ ├── src/
│ │ ├── main/java/ # Java source code
│ │ └── test/java/ # JUnit test in Java
│ ├── pom.xml # Maven configuration
│ └── build.gradle.kts # Gradle configuration
initial 모듈에는 단일 테스트가 있는 간단한 Java Todo 애플리케이션이 들어 있어요.
- 같은 디렉터리에서 빌드 파일을 열고, Kotlin을 지원하도록 내용을 업데이트해요.
<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0</modelVersion>
<groupId>org.jetbrains.kotlin</groupId>
<artifactId>kotlin-junit-complete</artifactId>
<version>1.0-SNAPSHOT</version>
<name>kotlin-junit-complete</name>
<url>https://kotlinlang.org/docs/jvm-test-using-junit.htm</url>
<properties>
<project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
<maven.compiler.release>17</maven.compiler.release>
<jexer.version>1.6.0</jexer.version>
<kotlin.version>2.4.20</kotlin.version>
</properties>
<dependencyManagement>
<dependencies>
<dependency>
<groupId>org.junit</groupId>
<artifactId>junit-bom</artifactId>
<version>6.0.3</version>
<type>pom</type>
<scope>import</scope>
</dependency>
</dependencies>
</dependencyManagement>
<dependencies>
<dependency>
<groupId>org.junit.jupiter</groupId>
<artifactId>junit-jupiter-api</artifactId>
<scope>test</scope>
</dependency>
<!-- Add JUnit Jupiter engine for test runtime -->
<dependency>
<groupId>org.junit.jupiter</groupId>
<artifactId>junit-jupiter-engine</artifactId>
<scope>test</scope>
</dependency>
<!-- Optionally: parameterized tests support -->
<dependency>
<groupId>org.junit.jupiter</groupId>
<artifactId>junit-jupiter-params</artifactId>
<scope>test</scope>
</dependency>
<dependency>
<groupId>com.gitlab.klamonte</groupId>
<artifactId>jexer</artifactId>
<version>${jexer.version}</version>
</dependency>
</dependencies>
<build>
<pluginManagement><!-- Lock down plugin versions to avoid using Maven defaults (can be moved to a parent pom file) -->
<plugins>
<!-- Clean lifecycle, see https://maven.apache.org/ref/current/maven-core/lifecycles.html#clean_Lifecycle -->
<plugin>
<artifactId>maven-clean-plugin</artifactId>
<version>3.4.0</version>
</plugin>
<!-- Default lifecycle, jar packaging: see https://maven.apache.org/ref/current/maven-core/default-bindings.html#Plugin_bindings_for_jar_packaging -->
<plugin>
<artifactId>maven-resources-plugin</artifactId>
<version>3.3.1</version>
</plugin>
<plugin>
<artifactId>maven-surefire-plugin</artifactId>
<version>3.3.0</version>
</plugin>
<plugin>
<artifactId>maven-jar-plugin</artifactId>
<version>3.4.2</version>
</plugin>
<plugin>
<artifactId>maven-install-plugin</artifactId>
<version>3.1.2</version>
</plugin>
<plugin>
<artifactId>maven-deploy-plugin</artifactId>
<version>3.1.2</version>
</plugin>
<!-- Site lifecycle, see https://maven.apache.org/ref/current/maven-core/lifecycles.html#site_Lifecycle -->
<plugin>
<artifactId>maven-site-plugin</artifactId>
<version>3.12.1</version>
</plugin>
<plugin>
<artifactId>maven-project-info-reports-plugin</artifactId>
<version>3.6.1</version>
</plugin>
<!-- No maven-compiler-plugin needed with Kotlin extensions -->
</plugins>
</pluginManagement>
<plugins>
<!-- Activate Kotlin Maven plugin for main and test sources -->
<plugin>
<groupId>org.jetbrains.kotlin</groupId>
<artifactId>kotlin-maven-plugin</artifactId>
<version>${kotlin.version}</version>
<extensions>true</extensions>
</plugin>
</plugins>
</build>
</project>
<properties>섹션에서 Kotlin 버전을 설정해요.<dependencies>섹션에서 테스트 실행을 위한 JUnit Jupiter 의존성을 추가해요.<build><plugins>섹션에서<extensions>를true로 설정한kotlin-maven-plugin을 적용해요. 이 플러그인은 빌드에 해당 실행(execution)과kotlin-stdlib의존성을 자동으로 추가해 줘요.- Kotlin Maven 플러그인을 확장(extensions)과 함께 쓸 때는
<build><pluginManagement>에maven-compiler-plugin을 추가할 필요가 없어요.
// build.gradle.kts
group = "org.jetbrains.kotlin"
version = "1.0-SNAPSHOT"
description = "kotlin-junit-complete"
java.sourceCompatibility = JavaVersion.VERSION_17
plugins {
application
kotlin("jvm") version "2.4.20"
}
kotlin {
jvmToolchain(17)
}
application {
mainClass.set("org.jetbrains.kotlin.junit.App")
}
repositories {
mavenCentral()
}
dependencies {
implementation("com.gitlab.klamonte:jexer:1.6.0")
testImplementation(kotlin("test"))
testImplementation(libs.org.junit.jupiter.junit.jupiter.api)
testImplementation(libs.org.junit.jupiter.junit.jupiter.params)
testRuntimeOnly(libs.org.junit.jupiter.junit.jupiter.engine)
testRuntimeOnly(libs.org.junit.platform.junit.platform.launcher)
}
tasks.test {
useJUnitPlatform()
}
plugins {}블록에kotlin("jvm")플러그인을 추가해요.- JVM 툴체인 버전을 여러분의 Java 버전과 일치하도록 설정해요.
dependencies {}블록에 Kotlin의 테스트 유틸리티를 제공하고 JUnit과 통합되는kotlin.test라이브러리를 추가해요.
Kotlin/JVM은 최신 안정 버전인 JUnit 6을 지원해요. 그 버전은 gradle/libs.versions.toml 버전 카탈로그에서 찾을 수 있어요.
평소 버전 카탈로그를 선호한다면, kotlin("jvm") 플러그인도 카탈로그에 추가할 수 있어요.
# gradle/libs.versions.toml
[versions]
kotlin = "2.4.20"
junit = "6.0.3"
[libraries]
org-junit-jupiter-junit-jupiter-api = { module = "org.junit.jupiter:junit-jupiter-api", version.ref = "junit" }
org-junit-jupiter-junit-jupiter-params = { module = "org.junit.jupiter:junit-jupiter-params", version.ref = "junit" }
org-junit-jupiter-junit-jupiter-engine = { module = "org.junit.jupiter:junit-jupiter-engine", version.ref = "junit" }
org-junit-platform-junit-platform-launcher = { module = "org.junit.platform:junit-platform-launcher" }
[plugins]
kotlinJvm = { id = "org.jetbrains.kotlin.jvm", version.ref = "kotlin" }
- IDE에서 빌드 파일을 다시 불러와요.
빌드 파일 설정에 대한 더 자세한 안내는 프로젝트 구성 문서를 참고하세요.
첫 Kotlin 테스트 추가하기
initial/src/test/java의 TodoItemTest.java 테스트는 이미 앱의 기본 동작(항목 생성, 기본값, 고유 ID, 상태 변경)을 검증하고 있어요.
리포지토리 수준 동작을 검증하는 Kotlin 테스트를 추가해서 테스트 커버리지를 확장할 수 있어요.
- 같은 테스트 소스 디렉터리인
initial/src/test/java로 이동해요. - Java 테스트와 같은 패키지에
TodoRepositoryTest.kt파일을 만들어요. - 필드 선언과 설정 함수를 가진 테스트 클래스를 만들어요.
package org.jetbrains.kotlin.junit
import org.junit.jupiter.api.BeforeEach
import org.junit.jupiter.api.Assertions
import org.junit.jupiter.api.Test
import org.junit.jupiter.api.DisplayName
internal class TodoRepositoryTest {
lateinit var repository: TodoRepository
lateinit var testItem1: TodoItem
lateinit var testItem2: TodoItem
@BeforeEach
fun setUp() {
repository = TodoRepository()
testItem1 = TodoItem("Task 1", "Description 1")
testItem2 = TodoItem("Task 2", "Description 2")
}
}
JUnit 어노테이션은 Java에서와 똑같이 Kotlin에서도 동작해요.
- Kotlin의
lateinit키워드는 나중에 초기화되는 널 아닌 프로퍼티를 선언하게 해 줘요. 덕분에 테스트에서 nullable 타입(TodoRepository?)을 쓰지 않아도 돼요. TodoRepositoryTest클래스 안에 초기 리포지토리 상태와 그 크기를 확인하는 테스트를 추가해요.
@Test
@DisplayName("Should start with empty repository")
fun shouldStartEmpty() {
Assertions.assertEquals(0, repository.size())
Assertions.assertTrue(repository.all.isEmpty())
}
Java의 정적 import와 달리, Jupiter의 Assertions는 클래스로 import되어 assertion 함수의 한정자(qualifier)로 사용돼요.
.getAll()호출 대신, Kotlin에서는repository.all처럼 Java getter를 프로퍼티로 접근할 수 있어요.- 모든 항목의 복사 동작을 검증하는 또 다른 테스트를 작성해요.
@Test
@DisplayName("Should return defensive copy of items")
fun shouldReturnDefensiveCopy() {
repository.add(testItem1)
val items1 = repository.all
val items2 = repository.all
Assertions.assertNotSame(items1, items2)
Assertions.assertThrows(
UnsupportedOperationException::class.java
) { items1.clear() }
Assertions.assertEquals(1, repository.size())
}
- Kotlin 클래스에서 Java 클래스 객체를 얻으려면
::class.java를 사용해요. - 복잡한 assertion은 특별한 이어쓰기 문자 없이도 여러 줄로 나눌 수 있어요.
- ID로 항목을 찾는 것을 검증하는 테스트를 추가해요.
@Test
@DisplayName("Should find item by ID")
fun shouldFindItemById() {
repository.add(testItem1)
repository.add(testItem2)
val found = repository.getById(testItem1.id())
Assertions.assertTrue(found.isPresent)
Assertions.assertEquals(testItem1, found.get())
}
Kotlin은 Java Optional API와 매끄럽게 동작해요. getter 메서드를 자동으로 프로퍼티로 변환하니까, 여기서 isPresent() 메서드를 프로퍼티처럼 접근하는 거예요.
- 항목 제거 메커니즘을 검증하는 테스트를 작성해요.
@Test
@DisplayName("Should remove item by ID")
fun shouldRemoveItemById() {
repository.add(testItem1)
repository.add(testItem2)
val removed = repository.remove(testItem1.id())
Assertions.assertTrue(removed)
Assertions.assertEquals(1, repository.size())
Assertions.assertTrue(repository.getById(testItem1.id()).isEmpty)
Assertions.assertTrue(repository.getById(testItem2.id()).isPresent)
}
@Test
@DisplayName("Should return false when removing non-existent item")
fun shouldReturnFalseForNonExistentRemoval() {
repository.add(testItem1)
val removed = repository.remove("non-existent-id")
Assertions.assertFalse(removed)
Assertions.assertEquals(1, repository.size())
}
Kotlin에서는 repository.getById(id).isEmpty처럼 메서드 호출과 프로퍼티 접근을 이어서 쓸 수 있어요.
팁:
TodoRepositoryTest테스트 클래스에 더 많은 기능을 커버하는 테스트를 추가로 붙일 수도 있어요. 샘플 프로젝트의complete모듈에서 전체 소스 코드를 볼 수 있어요.
테스트 실행하기
Java와 Kotlin 테스트를 모두 실행해서 프로젝트가 예상대로 동작하는지 확인해 보세요.
- 거터(gutter) 아이콘으로 테스트를 실행해요. 아니면
initial디렉터리에서 명령줄로 프로젝트의 모든 테스트를 실행할 수도 있어요.
mvn test
./gradlew test
- 변수 값 하나를 바꿔서 테스트가 제대로 동작하는지 확인해요. 예를 들어,
shouldAddItem테스트가 잘못된 리포지토리 크기를 기대하도록 수정해 볼게요.
@Test
@DisplayName("Should add item to repository")
fun shouldAddItem() {
repository.add(testItem1)
Assertions.assertEquals(2, repository.size()) // Changed from 1 to 2
Assertions.assertTrue(repository.all.contains(testItem1))
}
- 테스트를 다시 실행하고, 실패하는지 확인해요.
팁: 테스트가 있는 완전히 구성된 프로젝트는 샘플 프로젝트의
complete모듈에서 찾을 수 있어요.
다음은 무엇?
Maven으로 Kotlin 프로젝트 테스트하기에 대해 더 알아보세요.