Kotlin Multiplatform 프로젝트에서 Lincheck 사용하기
Kotlin Multiplatform 프로젝트에서 Lincheck 사용하기
Lincheck는 Kotlin Multiplatform과 함께 사용하여 JVM을 대상으로 하는 코드를 테스트할 수 있어요. 이 문서에서는 Kotlin Multiplatform 프로젝트에서 Lincheck 테스트를 설정하는 방법을 안내해 드릴게요.
다음 내용을 배우게 됩니다:
- JVM을 사용하는 타깃을 위해 프로젝트에서 Lincheck 테스트를 실행하도록 설정합니다.
- 모든 타깃이 공유하는 카운터 데이터 구조 클래스를 만듭니다.
- 모든 타깃이 공유하는 카운터 테스트와 JVM 전용 테스트를 만듭니다.
- 테스트를 실행합니다.
본문
프로젝트 설정하기
-
IntelliJ IDEA에서 새 Kotlin Multiplatform 프로젝트를 만듭니다. 프로젝트를 만들 때 다음을 확인해 주세요:
- Include tests 체크박스가 선택되어 있어야 합니다.
- 프로젝트에 JVM 기반(서버, 데스크톱, 또는 Android) 타깃이 있어야 합니다.
shared/build.gradle.kts또는core/build.gradle.kts파일을 확인하여 코드가 JVM용으로 컴파일되는지 검증할 수 있어요:
kotlin { // ... jvm() // ... // If you have an Android target, the use of JVM is // configured in the `androidLibrary` section androidLibrary { // ... compilerOptions { jvmTarget = JvmTarget.JVM_11 } // ... } }Server/Desktop + Android 및 Server/Desktop + Android + iOS 프로젝트의 경우: Kotlin은 현재 JVM과 Android 타깃 간에 코드 공유를 지원하지 않습니다. 플랫폼 간 공유 테스트를 만들 수는 없지만, 플랫폼별 테스트를 사용해 Lincheck로 코드를 테스트할 수는 있어요.
Lincheck의 일반적인 테스트 설정에 대한 자세한 내용은 Lincheck 시작하기 문서를 읽어 보세요.
-
프로젝트의 타깃 플랫폼에 따라 테스트 폴더와 Lincheck 의존성을 설정합니다:
Desktop/Android + iOS
플랫폼별 테스트를 위한 디렉토리는 Kotlin Multiplatform 플러그인이 자동으로 만듭니다. 예를 들어 데스크톱 타깃의 경우 shared/src/jvmTest/kotlin입니다.
플랫폼별 테스트 소스 세트에 Lincheck 의존성을 추가하고, commonTest 소스 세트에 kotlin.test 의존성을 추가해 주세요:
// shared/build.gradle.kts
kotlin {
sourceSets {
commonTest.dependencies {
implementation(libs.kotlin.test)
}
jvmTest {
implementation("org.jetbrains.lincheck:lincheck:3.7")
}
}
}
// shared/build.gradle.kts
kotlin {
sourceSets {
commonTest.dependencies {
implementation(libs.kotlin.test)
}
getByName("androidHostTest").dependencies {
implementation("org.jetbrains.lincheck:lincheck:3.7")
}
}
}
Server + iOS, Server + Desktop, Server + Desktop + iOS
-
core/src/jvmTest/kotlin디렉토리를 만듭니다. Gradle은 JVM을 사용하는 모든 플랫폼에 대해 이 디렉토리의 테스트를 실행합니다.데스크톱 타깃이 있는 프로젝트에는 대신 사용할 수 있는
shared/src/jvmTest/kotlin디렉토리도 있습니다. 다만 서버와 데스크톱/iOS 타깃 모두에 관련된 코드는core디렉토리에 두는 것이 좋습니다. 데스크톱 타깃이 있고core/src/jvmTest/kotlin디렉토리를 만든다면shared/src/jvmTest/kotlin디렉토리는 삭제해 주세요. -
jvmTest소스 세트에 Lincheck 의존성을 추가하고commonTest소스 세트에kotlin.test의존성을 추가합니다:// core/build.gradle.kts kotlin { // ... sourceSets { // ... commonTest.dependencies { implementation(libs.kotlin.test) } jvmTest.dependencies { implementation("org.jetbrains.lincheck:lincheck:3.7") } } }
공유 클래스 만들기
모든 타깃이 공유하는 카운터 데이터 구조를 구현하는 클래스를 만듭니다:
-
core/src/commonMain또는shared/src/commonMain디렉토리에UnsafeCounter.kt파일을 만듭니다:class UnsafeCounter { private var value: Int = 0 fun inc() = value++ fun get(): Int = value } -
같은 디렉토리에
SafeCounter.kt파일을 만듭니다:import kotlin.concurrent.atomics.AtomicInt import kotlin.concurrent.atomics.ExperimentalAtomicApi @OptIn(ExperimentalAtomicApi::class) class SafeCounter { private var value = AtomicInt(0) fun inc() = value.addAndFetch(1) fun get(): Int = value.load() }
공유 테스트 작성하기
모든 타깃에서 실행되는 테스트를 작성합니다:
-
core/src/commonTest또는shared/src/commonTest디렉토리에UnsafeCounterTest.kt파일을 만듭니다:import kotlin.test.Test import kotlin.test.assertEquals class UnsafeCounterTest { @Test fun testIncrement() { val counter = UnsafeCounter() assertEquals(0, counter.get(), "Initial value should be 0") counter.inc() assertEquals(1, counter.get(), "Value after one increment should be 1") } } -
같은 디렉토리에
SafeCounterTest.kt파일을 만듭니다:import kotlin.test.Test import kotlin.test.assertEquals class SafeCounterTest { @Test fun testIncrement() { val counter = SafeCounter() assertEquals(0, counter.get(), "Initial value should be 0") counter.inc() assertEquals(1, counter.get(), "Value after one increment should be 1") } }
JVM 전용 테스트 작성하기
JVM을 대상으로 하는 플랫폼에서만 실행되는 테스트를 작성합니다:
-
JVM 전용 테스트 디렉토리에
UnsafeCounterConcurrentTest.kt파일을 만듭니다:- Android + iOS –
shared/src/androidHostTest/kotlin - Desktop + iOS –
shared/src/jvmTest/kotlin - 서버 타깃이 있는 모든 프로젝트 –
core/src/jvmTest/kotlin
import org.jetbrains.lincheck.datastructures.ModelCheckingOptions import org.jetbrains.lincheck.datastructures.Operation import kotlin.test.Test class UnsafeCounterConcurrentTest { private val c = UnsafeCounter() @Operation fun inc() = c.inc() @Operation fun get() = c.get() @Test fun modelCheckingTest() { ModelCheckingOptions().check(this::class) } } - Android + iOS –
-
같은 디렉토리에
SafeCounterConcurrentTest.kt파일을 만듭니다:import org.jetbrains.lincheck.datastructures.ModelCheckingOptions import org.jetbrains.lincheck.datastructures.Operation import kotlin.test.Test class SafeCounterConcurrentTest { private val c = SafeCounter() @Operation fun inc() = c.inc() @Operation fun get() = c.get() @Test fun modelCheckingTest() { ModelCheckingOptions().check(this::class) } }
테스트 실행하기
이 시점에서 프로젝트에는 카운터 데이터 구조의 공유 구현, 공유 테스트, 그리고 카운터용 JVM 전용 테스트가 있어야 합니다. 프로젝트의 디렉토리 구조는 다음과 같아야 합니다:
컨텍스트 메뉴, 단축키, 또는 Gradle 태스크 실행을 통해 개별 테스트를 실행할 수 있어요. allTests Gradle 태스크를 실행하면 프로젝트의 모든 테스트가 각각에 맞는 테스트 러너로 실행됩니다:
예를 들어 JVM 전용 테스트는 JVM 테스트 러너로 실행됩니다:
JVM 이외의 플랫폼 지원
Lincheck가 다른 플랫폼을 지원할 가능성은 낮습니다.
Lincheck는 바이트코드 조작에 JVM을 크게 의존하기 때문에, 다른 플랫폼에서 모델 체킹을 구현하는 것은 어렵습니다. 게다가 Lincheck는 결정적(deterministic) 샌드박스 환경에서 코드를 테스트하므로 여러 플랫폼을 동시에 모델 체킹하는 것은 가치가 거의 없습니다. 테스트 결과는 플랫폼에 관계없이 동일하기 때문이죠.
더 알아보기
- Kotlin Multiplatform에 대해 더 배워 보세요.
- Lincheck 문서를 둘러보세요.
- 모델 체킹
- API 레퍼런스