Lincheck 시작하기
Lincheck 시작하기
이 퀵스타트에서는 Lincheck를 설정하고, 첫 Lincheck 테스트를 작성하고, 테스트 리포트를 해석하는 방법을 안내해 드릴게요.
다음 내용을 배우게 됩니다:
- 새 IntelliJ IDEA 프로젝트를 만들고 Lincheck를 설치합니다.
- 첫 동시성(concurrent) 테스트를 작성하고 Lincheck로 실행합니다.
- 동시성 데이터 구조를 만들고 두 가지 테스트 전략으로 Lincheck로 테스트합니다.
본문
프로젝트 만들기
IntelliJ IDEA에서 기존 Kotlin 프로젝트를 열거나 새 프로젝트를 만들고 시작해 주세요.
의존성 추가하기
프로젝트에서 Lincheck를 사용하려면 빌드 설정에 해당 의존성을 추가해 주세요.
첫 테스트 작성하기
기본적인 동시성 테스트를 위해, 각 스레드에서 어떤 작업을 실행해야 하는지와 기대하는 검증(assertion)을 설명하는 테스트 함수를 만들게요. Lincheck는 프로그램의 가능한 스레드 인터리빙(interleaving)을 모델 체킹으로 탐색하고, 잘못된 동작이 있으면 오류 리포트를 제공합니다.
-
src/test디렉토리에CounterTest.kt파일을 만듭니다. -
org.jetbrains.lincheck,kotlinx.concurrent,kotlin.test라이브러리를 임포트합니다:import org.jetbrains.lincheck.Lincheck import kotlin.test.* import kotlin.concurrent.thread -
변수를 하나 만들고 그 변수를 조작하는 두 스레드를 만드는 테스트를 작성합니다:
class CounterTest { @Test // Test function declaration fun test() = Lincheck.runConcurrentTest { var counter = 0 // Increments the counter concurrently val t1 = thread { counter++ } val t2 = thread { counter++ } // Waits for the threads to finish t1.join() t2.join() // Checks that both increments have been applied assertEquals(2, counter) } } -
테스트를 실행합니다. Lincheck는 잘못된 동작을 유발한 스레드 인터리빙이 담긴 리포트를 생성합니다:
Lincheck 플러그인을 설치하면 오류 트레이스를 시각화할 수 있어요.
Lincheck는
inc()작업 중 하나가counter값을 덮어쓰는 스레드 인터리빙을 찾아냈습니다.단계별 리포트 설명:
- 스레드 2에서 JVM이 초기
counter값을 읽습니다. - 실행이 스레드 2에서 스레드 1로 전환됩니다.
- 스레드 1에서 JVM이 counter를 증가시킵니다.
inc()작업의 모든 단계(변수에서 값 읽기, 값 증가시키기, 변수에 값 쓰기)가 중단 없이 수행됩니다. - 실행이 스레드 2로 다시 전환됩니다.
- 스레드 2에서 JVM이 1단계에서 가져온 값을 증가시키고 결과를
counter변수에 씁니다.
| ------------------------------------------------------------------------------- | | Main Thread | Thread 1 | Thread 2 | | ------------------------------------------------------------------------------- | | thread(block = Lambda#2): Thread#1 | | | | thread(block = Lambda#3): Thread#2 | | | | switch (reason: waiting for Thread 1 to finish) | | | | | | run() | | | | counter ➜ 0 | | | | switch | | | run() | | | | counter ➜ 0 | | | | counter = 1 | | | | | counter = 1 | | Thread#1.join() | | | | Thread#2.join() | | | | counter.element ➜ 1 | | | | assertEquals(2, 1): threw AssertionFailedError | | | | ------------------------------------------------------------------------------- | - 스레드 2에서 JVM이 초기
데이터 구조 테스트 작성하기
기본적인 동시성 테스트 외에도, Lincheck는 동시성 데이터 구조를 테스트하는 선언적 접근 방식을 지원합니다.
Lincheck에서 데이터 구조를 테스트하려면 구조의 동시성 메서드와 테스트 함수만 선언하면 됩니다. Lincheck가 무작위 동시성 시나리오를 생성하고, 지정된 테스트 전략으로 실행한 뒤 오류 리포트를 제공합니다.
이 섹션에서는 간단한 카운터를 테스트해 볼게요:
-
src/test디렉토리에CounterStructureTest.kt파일을 만듭니다. -
lincheck.datastructures와kotlin.test라이브러리를 임포트합니다:import org.jetbrains.lincheck.datastructures.* import kotlin.test.* -
Counter구조를 만듭니다:class Counter { var value = 0 fun inc(): Int = ++value fun dec(): Int = --value } -
CounterStructureTest클래스를 만들고 구조의 초기 상태를 설정한 뒤, 구조의 동시성 작업을@Operation어노테이션으로 표시합니다:class CounterStructureTest { private val c = Counter() @Operation fun inc() = c.inc() @Operation fun dec() = c.dec() .iterations(100) // Specify the number of generated scenarios -
CounterStructureTest클래스에서ModelCheckingOptions()를 사용해 테스트 함수를 선언합니다:테스트 전략 문서에서 모델 체킹이 어떻게 동작하는지 배워 보세요.
@Test fun test() = ModelCheckingOptions().check(this::class) -
테스트를 실행합니다. Lincheck는 잘못된 동작을 유발한 동시성 시나리오와 특정 스레드 인터리빙을 담은 오류 리포트를 생성합니다:
| ------------------- | | Thread 1 | Thread 2 | | ------------------- | | inc(): 1 | inc(): 1 | | ------------------- || ------------------------ | | Thread 1 | Thread 2 | | ------------------------ | | | inc(): 1 | | | c.inc(): 1 | | | value ➜ 0 | | | switch | | inc(): 1 | | | | value = 1 | | | value ➜ 1 | | | result: 1 | | ------------------------ |
더 알아보기
- Lincheck에서 동시성 데이터 구조를 테스트하는 선언적 접근 방식에 대해 배워 보세요.
- 개요
- 임의 코드 테스트