테스트 전략

테스트 전략

Lincheck는 동시성 데이터 구조를 테스트하기 위한 두 가지 전략을 제공해요. 모델 체킹(model checking)과 스트레스 테스트(stress testing)예요. 이 글에서는 두 전략의 차이와 테스트 전략을 고를 때 염두에 둬야 할 점을 배워요.

출처: Testing strategies

본문

모델 체킹 (Model checking)

모델 체킹을 사용하면 Lincheck가 가능한 스레드 인터리빙(thread interleaving)을 시뮬레이션하고, 잘못된 동작을 일으키는 인터리빙을 보고해요. 데이터 구조 테스트에 모델 체킹을 사용하려면 ModelCheckingOptions()를 사용해 테스트 함수를 선언해요:

@Test
fun test() = ModelCheckingOptions().check(this::class)

모델 체킹 전략을 사용할 때 Lincheck는 공유 메모리 접근(읽기와 쓰기) 지점이나 동기화 지점(락 획득과 해제, park/unpark, wait/notify 등)에 명시적인 스레드 전환(thread-switch) 명령을 삽입해요. 스레드 전환을 제어하면 Lincheck는 다음을 할 수 있어요:

  • 프로그램의 가능한 여러 실행 스케줄을 결정론적으로 탐색해요.
  • 자세한 실행 추적(execution trace)을 제공해요.

현재 모델 체킹은 Lincheck가 실행의 순차 일관성(sequentially consistent) 메모리 모델을 가정해야 해요. 즉, Lincheck는 명령 재정렬, 메모리 캐시 동작, 그리고 그런 유사한 문제들을 시뮬레이션하지 못하고 잡아내지도 못해요. 자세한 내용은 Model checking을 참조해요.

스트레스 테스트 (Stress testing)

스트레스 테스트를 사용하면 Lincheck가 각 시나리오를 여러 번 실행해서 오류를 찾을 확률을 높여요. 스트레스 테스트를 사용하려면 StressOptions()를 사용해 테스트 함수를 선언해요:

@Test
fun stressTest() = StressOptions().check(this::class)

모델 체킹과 달리, Lincheck는 스레드 전환을 제어하거나 추적하지 않아요. 그 덕분에 스트레스 테스트는 더 빠르고, Lincheck가 메모리 모델에 대해 어떤 가정도 하지 않아도 돼요. 다만 스트레스 테스트는 재현이 불가능하고, Lincheck가 실행 추적을 제공하지 못해요.

전략 고르기

전략을 고를 때는 다음을 고려해요:

모델 체킹 스트레스 테스트
속도 더 느려요. 더 빨라요.
재현성 입력 데이터가 바뀌지 않으면 테스트가 정확히 같은 결과를 반환해요. 실행마다 스레드 스케줄이 바뀔 수 있어 테스트가 다른 결과를 반환할 수 있어요.
가정 순차 일관성 메모리 모델을 가정해요. 그 모델 밖의 잘못된 동작으로 인한 버그는 놓칠 수 있어요. 메모리 모델에 대해 어떤 가정도 하지 않아요. 근본 원인과 무관하게 어떤 잘못된 동작이든 잡아낼 기회가 있어요.
자세함 잘못된 동작으로 이끈 동시성 시나리오와 실행 추적을 모두 보고해요. 동시성 시나리오만 보고해요.
표준 라이브러리 커버리지 약한 참조(weak reference) 같은 일부 표준 라이브러리 기능의 동작을 시뮬레이션하지 않아요. 그런 기능으로 인한 버그는 놓칠 수 있어요. 어떤 기능 사용으로 인한 버그든 잡아낼 기회가 있어요.

다음 단계

시나리오 생성을 커스터마이즈하고, 중단된 실행 감지를 활성화하고, 라이브러리에 스레드 안전 보장을 제공해서 테스트 전략을 구성하는 방법을 배워요.

더 알아보기

  • 연산 인자 생성하기 (Generating operation arguments)
  • 연산 실행 옵션 구성하기 (Configuring operation execution options)
  • 논블로킹 진행 보장 확인하기 (Checking for non-blocking progress guarantees)
  • 알고리즘의 순차 명세 정의하기 (Defining sequential specification of the algorithm)