테스트 전략 구성하기
테스트 전략 구성하기
Lincheck는 테스트 전략을 위한 다양한 구성 옵션을 지원해요. 시나리오 생성, 중단된 실행 감지, 검증 등이 여기에 포함돼요.
본문
옵션 활성화하는 방법
테스트 전략에 대한 옵션을 활성화하려면 전략 클래스에서 설정해요:
@Test
fun testWithIterations() = ModelCheckingOptions()
.iterations(100) // Specify the number of generated scenarios
.check(this::class)
시나리오 최소화 (Scenario minimization)
기본적으로 Lincheck는 테스트 동작을 바꾸지 않는 연산을 제거해서 실패한 시나리오를 최소화하려고 해요. 최소화되지 않은 전체 실패 시나리오를 보려면 minimizeFailedScenario 옵션을 false로 설정해요:
| ------------------------- |
| Thread 1 | Thread 2 |
| --------------------------|
| inc(): 1 | |
| get(): 1 | |
| get(): 1 | |
| --------------------------|
| inc(): 4 [0,1] | inc(): 2 |
| get(): 4 [1,1] | inc(): 4 |
| get(): 4 [2,1] | get(): 4 |
| --------------------------|
| get(): 4 | |
| get(): 4 | |
| get(): 4 | |
| --------------------------|
최소화된 동일한 시나리오는 다음과 같아요:
| ------------------- |
| Thread 1 | Thread 2 |
| ------------------- |
| inc(): 1 | inc(): 1 |
| ------------------- |
시나리오 생성 (Scenario generation)
| 옵션 | 기본값 | 설명 |
|---|---|---|
iterations |
100 |
생성할 동시성 시나리오의 수예요. |
invocationsPerIteration |
10_000 |
각 동시성 시나리오의 호출(invocation) 수예요. |
threads |
2 |
각 시나리오의 스레드 수예요. |
actorsBefore |
5 |
시나리오의 병렬 섹션 앞에서 호출되는 연산 수예요. |
actorsPerThread |
5 |
시나리오 병렬 섹션에서 각 스레드의 연산 수예요. |
actorsAfter |
5 |
시나리오의 병렬 섹션 뒤에서 호출되는 연산 수예요. |
customScenarios |
– | 커스텀 동시성 시나리오 목록이에요. 커스텀 시나리오는 무작위로 생성된 시나리오보다 먼저 실행돼요. |
커스텀 시나리오 정의하기
Lincheck는 커스텀 시나리오를 정의하기 위한 도메인 특화 언어(DSL)를 사용해요:
@Test
fun test() = StressOptions()
.addCustomScenario {
initial {
actor(Structure::noArgsOp)
}
parallel {
thread {
actor(Structure::argsOp, 1)
actor(Structure::argsOp, 2)
}
thread {
actor(Structure::argsOp, 3)
}
}
post {
actor(Structure::noArgsOp)
}
}
// Report the scenarios even if the test has not failed
// The custom scenario should be first in the list of scenarios
.logLevel(LoggingLevel.INFO)
.check(this::class)
각 시나리오는 세 개의 선택 섹션으로 구성돼요:
initial– 병렬 부분 이전에 실행되는 연산이에요.parallel– 스레드 정의예요. 스레드는thread블록을 사용해 정의하며, 병렬 섹션에는 여러thread블록이 있을 수 있어요.post– 병렬 부분 이후에 실행되는 연산이에요.
연산은 actor(function, arg1, arg2, ...) 함수로 정의해요. 하나의 블록 안에 있는 연산은 순차적으로 실행돼요.
중단된 실행 감지 (Stalled execution detection)
| 옵션 | 기본값 | 설명 |
|---|---|---|
timeoutMs |
3000 |
호출 타임아웃(밀리초)이에요. 이 시간이 지나면 Lincheck가 중단된 실행(stalled execution)을 보고해요. |
loopBound |
50 |
이 횟수만큼 루프를 반복하면 Lincheck가 중단된 실행을 보고해요. 긴 루프에서 Lincheck가 중단을 잘못 보고한다면 loopBound 값을 늘려요. 이 옵션은 모델 체킹에만 적용할 수 있어요. |
recursionBound |
20 |
이 횟수만큼 재귀 호출하면 Lincheck가 중단된 실행을 보고해요. loopIterationsBeforeThreadSwitch 값은 loopBound보다 작아야 해요. 이 옵션은 모델 체킹에만 적용할 수 있어요. |
루프에서의 스레드 전환 (Thread switching in loops)
| 옵션 | 기본값 | 설명 |
|---|---|---|
loopIterationsBeforeThreadSwitch |
10 |
스레드가 다른 스레드로 전환을 시도하기 전에 수행할 수 있는 루프 반복 횟수예요. loopIterationsBeforeThreadSwitch 값은 loopBound보다 작아야 해요. 이 옵션은 모델 체킹에만 적용할 수 있어요. |
검증 (Verification)
| 옵션 | 기본값 | 설명 |
|---|---|---|
verifierClass |
LinearizabilityVerifier |
검증 과정에서 사용되는 검증기(verifier) 클래스예요: LinearizabilityVerifier, SerializabilityVerifier, QuiescentConsistencyVerifier |
sequentialSpecification |
테스트 중인 데이터 구조와 동일 | 테스트 중인 데이터 구조의 순차 버전이에요. 이 구조는 검증 과정에서 사용돼요. |
진행 보장 (Progress guarantees)
| 옵션 | 기본값 | 설명 |
|---|---|---|
checkObstructionFreedom |
false |
이 옵션을 true로 설정하면 데이터 구조 연산의 obstruction-freedom 보장을 검증해요. 이 옵션은 모델 체킹에만 적용할 수 있어요. |
라이브러리 분석 (Library analysis)
| 옵션 | 기본값 | 설명 |
|---|---|---|
stdLibAnalysisEnabled |
false |
기본적으로 Lincheck는 표준 라이브러리의 연산 동작을 검증하지 않고 스레드 안전한 것으로 취급해요. 이 옵션을 true로 설정하면 표준 라이브러리 함수/클래스 분석을 활성화해요. 이 옵션은 모델 체킹에만 적용할 수 있어요. |
addGuarantee |
– | 스레드 안전하거나 분석과 무관한 메서드에 대해 addGuarantee 옵션으로 보장을 정의해 모델 체킹에서 제외할 수 있어요. 이 옵션은 모델 체킹에만 적용할 수 있어요. |
보장 정의하기
보장을 정의하려면 빌더 체인을 사용해요. 클래스를 선택하고, 메서드를 선택한 다음, 보장 유형을 선택해요.
@Test
fun modelCheckingWithGuaranteesTest() = ModelCheckingOptions()
.addGuarantee(
forClasses("java.util.concurrent.ConcurrentHashMap")
.allMethods()
.treatAsAtomic()
)
.check(this::class)
-
forClasses오버로드 중 하나로 클래스를 선택해요:forClasses(vararg fullClassNames: String)— 전체 이름이fullClassNames문자열에 있으면 클래스와 일치해요.forClasses(vararg classes: KClass<*>)— 참조로 클래스를 일치시켜요.forClasses(classPredicate: (fullClassName: String) -> Boolean)— 전체 클래스 이름에 대한 조건자(predicate)로 클래스를 일치시켜요.
-
보장을 적용할 메서드를 선택해요:
methods(methodNames: String)— 이름이methodNames문자열에 있으면 메서드와 일치해요.methods(methodPredicate: (methodName: String) -> Boolean)— 조건자로 메서드를 일치시켜요.allMethods()— 선택한 클래스의 모든 메서드를 일치시켜요.
-
보장 유형을 선택해요:
treatAsAtomic()— 각 메서드를 원자적(atomic) 연산으로 취급해요. Lincheck는 메서드 호출 내부에는 스위치 지점을 삽입하지 않지만, 호출 앞이나 뒤에는 추가할 수 있어요. 스레드 안전한 것으로 알려진 메서드에는treatAsAtomic()을 사용해요.ignore()— 메서드를 분석에서 제외해요. Lincheck는 메서드 호출 내부, 앞, 뒤에 스위치 지점을 삽입하지 않아요. 메서드가 내부적으로 동기화 프리미티브(예:synchronized블록)를 사용한다면, 메서드를 무시하면 Lincheck가 교착 상태에 빠질 수 있어요. 로깅이나 디버깅 유틸리티처럼 분석과 무관한 메서드에는ignore()를 사용해요.
다음 단계
Lincheck 실행 시나리오에 사용되는 연산의 인자 생성을 구성하는 방법을 배워요.
더 알아보기
- 연산 실행 옵션 구성하기 (Configuring operation execution options)
- 논블로킹 진행 보장 확인하기 (Checking for non-blocking progress guarantees)
- 알고리즘의 순차 명세 정의하기 (Defining a sequential specification of an algorithm)