스레드 코드를 어떻게 단위 테스트해야합니까?


704

멀티 스레드 코드를 테스트하는 악몽을 피할 수 없었습니다. 사람들이 성공적인 실행을 위해 스레드를 사용하는 코드를 테스트하는 방법에 대해 어떻게 물어 보거나 두 스레드가 주어진 방식으로 상호 작용할 때 나타나는 종류의 문제를 테스트하는 방법에 대해 묻고 싶습니다.

이것은 오늘날 프로그래머에게 정말로 중요한 문제인 것 같습니다.이 지식에 대한 지식을 모으는 것이 유용 할 것입니다.


2
이 똑같은 문제에 대한 질문을 게시하려고 생각했습니다. 윌은 아래에 많은 장점을 제시하지만 더 잘할 수 있다고 생각합니다. 나는 이것을 깨끗하게 처리하는 단일 "접근법"이 없다는 데 동의합니다. 그러나 "가능한 한 최선의 테스트"는 막대를 매우 낮게 설정하는 것입니다. 내가 찾은 결과로 돌아올 것이다.
Zach Burlingame

Java : 패키지 java.util.concurrent에는 결정적인 JUnit-Tests를 작성하는 데 도움이 될 수있는 잘못된 클래스가 포함되어 있습니다. -CountDownLatch - Semaphore - Exchanger
Synox

이전 단위 테스트 관련 질문에 대한 링크를 제공 할 수 있습니까?
Andrew Grimm


7
이 질문은 8 살이며 응용 프로그램 라이브러리는 그 동안 꽤 먼 길을 왔다는 점에 유의해야한다고 생각합니다. "현대"(2016)에서 멀티 스레드 개발은 주로 임베디드 시스템에서 발생합니다. 그러나 데스크톱 또는 전화 앱에서 작업하는 경우 먼저 대안을 탐색하십시오. .NET과 같은 애플리케이션 환경에는 이제 일반적인 멀티 스레딩 시나리오의 90 %를 관리하거나 크게 단순화하는 도구가 포함되어 있습니다. (asnync / await, PLinq, IObservable, TPL ...). 멀티 스레드 코드는 어렵습니다. 휠을 재발 명하지 않으면 다시 테스트 할 필요가 없습니다.
Paul Williams

답변:


245

이걸하는 쉬운 방법은 없어 나는 본질적으로 멀티 스레드 프로젝트에서 일하고 있습니다. 운영 체제에서 이벤트가 발생하며 동시에 처리해야합니다.

복잡한 멀티 스레드 응용 프로그램 코드 테스트를 처리하는 가장 간단한 방법은 다음과 같습니다. 테스트하기에 너무 복잡하면 잘못하고 있습니다. 여러 개의 스레드가있는 단일 인스턴스가 있고 이러한 스레드가 서로 단계별로 진행되는 상황을 테스트 할 수없는 경우 설계를 다시 작성해야합니다. 이것만큼 간단하고 복잡합니다.

스레드가 동시에 인스턴스를 실행하는 것을 방지하는 멀티 스레딩 프로그래밍 방법에는 여러 가지가 있습니다. 가장 간단한 방법은 모든 객체를 불변으로 만드는 것입니다. 물론 일반적으로는 불가능합니다. 따라서 디자인에서 스레드가 동일한 인스턴스와 상호 작용하는 장소를 식별하고 해당 장소의 수를 줄여야합니다. 이렇게하면 멀티 스레딩이 실제로 발생하는 몇 가지 클래스를 격리하여 시스템 테스트의 전체적인 복잡성을 줄일 수 있습니다.

그러나이 작업을 수행해도 두 스레드가 서로 밟는 모든 상황을 테스트 할 수는 없다는 것을 알아야합니다. 이를 위해서는 동일한 테스트에서 두 개의 스레드를 동시에 실행 한 다음 주어진 순간에 어떤 행을 실행하는지 정확하게 제어해야합니다. 가장 좋은 방법은이 상황을 시뮬레이션하는 것입니다. 그러나 테스트를 위해 특별히 코드를 작성해야 할 수도 있으며 이는 진정한 솔루션을 향한 반 단계에 불과합니다.

스레딩 문제에 대한 코드를 테스트하는 가장 좋은 방법은 코드를 정적으로 분석하는 것입니다. 스레드 코드가 유한 스레드 안전 패턴 세트를 따르지 않으면 문제가있을 수 있습니다. VS의 Code Analysis에는 스레딩에 대한 지식이 포함되어 있다고 생각하지만 많지는 않습니다.

현재 상황이 다가오고 있으며 앞으로 다가올 좋은시기가 될 것이므로 멀티 스레드 앱을 테스트하는 가장 좋은 방법은 스레드 코드의 복잡성을 최대한 줄이는 것입니다. 스레드가 상호 작용하는 영역을 최소화하고 최대한 테스트하고 코드 분석을 사용하여 위험 영역을 식별하십시오.


1
코드 분석은이를 허용하는 언어 / 프레임 워크를 다루는 경우에 좋습니다. EG : Findbugs는 정적 변수와 매우 간단하고 쉽게 공유되는 동시성 문제를 발견합니다. 찾을 수없는 것은 싱글 톤 디자인 패턴이며 모든 객체를 여러 번 만들 수 있다고 가정합니다. 이 플러그인은 Spring과 같은 프레임 워크에는 적합하지 않습니다.
좀비

3
실제로 치료법이 있습니다. 활성 객체. drdobbs.com/parallel/prefer-using-active-objects-in-the-n-n/…
Dill

6
이것이 좋은 조언이지만 "여러 개의 스레드가 필요한 최소 영역을 어떻게 테스트합니까?"
Bryan Rayner

5
"테스트하기에 너무 복잡하면 잘못하고 있습니다."– 우리 모두는 작성하지 않은 레거시 코드로 뛰어 들어야합니다. 이 관찰은 정확히 누구에게 도움이됩니까?
Ronna

2
정적 분석은 좋은 생각이지만 테스트는 아닙니다. 이 게시물은 실제로 테스트 방법에 대한 질문에 대답하지 않습니다.
Warren Dew

96

이 질문이 게시 된 지 오래되었지만 아직 답변되지 않았습니다 ...

kleolb02 의 답변은 좋은 것입니다. 자세한 내용을 살펴 보겠습니다.

C # 코드를 연습하는 방법이 있습니다. 단위 테스트의 경우 재현 가능한 테스트 를 프로그래밍 할 수 있어야합니다 . 이는 다중 스레드 코드에서 가장 큰 문제입니다. 작동 테스트 장치로 비동기 코드를 강제 대한 내 대답의 목표 그래서 기적 .

Gerard Meszardos의 저서 " xUnit Test Patterns " 에서 발췌 한 아이디어 이며 "Humble Object"(p. 695)라고합니다. 핵심 로직 코드와 비동기 코드처럼 냄새가 나는 것은 분리해야합니다. 결과적으로 핵심 로직의 클래스가 생겨 동기식 으로 작동 합니다. .

이를 통해 핵심 논리 코드를 동기식 으로 테스트 수 있습니다. 코어 로직에서 수행하는 통화 타이밍을 완전히 제어 할 수 있으므로 재현 가능한 테스트를 수행 할 수 있습니다 . 그리고 이것이 핵심 논리와 비동기 논리를 분리함으로써 얻는 이점입니다.

이 코어 로직은 다른 클래스에 의해 랩핑되어야하며,이 클래스는 코어 로직에 대한 호출을 비동기 적으로 수신하고 이러한 호출을 코어 로직에 위임 합니다. 생산 코드는 해당 클래스를 통해서만 핵심 로직에 액세스합니다. 이 클래스는 호출 만 위임해야하기 때문에 논리가 많지 않은 매우 "멍청한"클래스입니다. 따라서이 비동기 작업 클래스에 대한 단위 테스트를 최소한으로 유지할 수 있습니다.

위의 모든 것 (클래스 간의 상호 작용 테스트)은 구성 요소 테스트입니다. 또한이 경우 "Humble Object"패턴을 고수하면 타이밍을 절대적으로 제어 할 수 있어야합니다.


1
그러나 때로는 스레드가 서로 잘 협력한다면 테스트해야 할 것이 있습니다. 답을 읽은 후 핵심 부분을 비동기 부분과 분리합니다. 그러나 여전히 스레드에서 작업이 완료된 콜백으로 비동기 인터페이스를 통해 로직을 테스트 할 것입니다.
CopperCash

멀티 프로세서 시스템은 어떻습니까?
Technophile

65

참으로 힘든 것! 내 (C ++) 단위 테스트에서 사용 된 동시성 패턴의 선을 따라 여러 범주로 나누었습니다.

  1. 단일 스레드에서 작동하고 스레드를 인식하지 못하는 클래스에 대한 단위 테스트-평소처럼 쉽게 테스트합니다.

  2. 동기화 된 공개 API를 노출시키는 모니터 객체 (호출자의 제어 스레드에서 동기화 된 메소드를 실행하는)에 대한 단위 테스트 -API를 실행하는 여러 모의 스레드를 인스턴스화합니다. 수동 객체의 내부 조건을 행사하는 시나리오를 구성하십시오. 오랜 시간 동안 여러 스레드에서 기본적으로 이길 수있는 장기 실행 테스트를 하나 포함하십시오. 이것은 내가 아는 과학적이지 않지만 자신감을 쌓아줍니다.

  3. 클래스 디자인에 따라 변형 된 위의 # 2와 유사하게 활성 객체 (자체 스레드 또는 스레드를 캡슐화하는 객체)에 대한 단위 테스트 . 퍼블릭 API는 블로킹 또는 비 블로킹 일 수 있으며, 발신자는 미래를 얻거나, 데이터가 대기열에 도착하거나 대기열에서 제외 될 수 있습니다. 여기에는 가능한 많은 조합이 있습니다. 흰색 상자. 테스트중인 객체를 호출하려면 여전히 여러 모의 스레드가 필요합니다.

여담으로:

내가하는 내부 개발자 교육에서는 동시성 필러 와 이러한 두 가지 패턴을 동시성 문제를 생각하고 분해하는 기본 프레임 워크로 가르칩니다 . 분명히 더 고급 개념이 있지만이 기본 세트는 엔지니어가 수프를 피하는 데 도움이된다는 것을 알았습니다. 또한 위에서 설명한 것처럼 더 단위 테스트가 가능한 코드로 이어집니다.


51

최근 몇 년 동안 여러 프로젝트에 대한 스레드 처리 코드를 작성할 때이 문제에 여러 번 직면했습니다. 다른 답변의 대부분은 대안을 제공하면서 실제로 테스트에 대한 질문에 대답하지 않기 때문에 늦은 답변을 제공하고 있습니다. 내 대답은 멀티 스레드 코드에 대한 대안이없는 경우에 해결됩니다. 완전성을 위해 코드 디자인 문제를 다루고 단위 테스트에 대해서도 설명합니다.

테스트 가능한 다중 스레드 코드 작성

가장 먼저해야 할 일은 프로덕션 스레드 처리 코드를 실제 데이터 처리를 수행하는 모든 코드와 분리하는 것입니다. 이렇게하면 데이터 처리를 단일 스레드 코드로 테스트 할 수 있으며 멀티 스레드 코드가 수행하는 유일한 작업은 스레드를 조정하는 것입니다.

두 번째로 기억해야 할 것은 멀티 스레드 코드의 버그는 확률 적이라는 것입니다. 가장 빈번하게 나타나는 버그는 프로덕션 환경으로 몰래 들어가고 프로덕션 환경에서도 재현하기 어려운 버그로, 가장 큰 문제를 일으킬 것입니다. 이러한 이유로 코드를 빠르게 작성한 다음 작동 할 때까지 디버깅하는 표준 코딩 방식은 멀티 스레드 코드에는 좋지 않습니다. 쉬운 버그가 수정되고 위험한 버그가 여전히 존재하는 코드가 생성됩니다.

대신 멀티 스레드 코드를 작성할 때는 먼저 버그를 작성하지 않으려는 자세로 코드를 작성해야합니다. 데이터 처리 코드를 올바르게 제거했다면 스레드 처리 코드는 충분히 작아야합니다 (바람직하게는 몇 줄, 최악의 경우 수십 줄). 스레딩을 이해하면 시간을내어 조심하십시오.

멀티 스레드 코드에 대한 단위 테스트 작성

멀티 스레드 코드를 최대한 신중하게 작성하면 해당 코드에 대한 테스트를 작성하는 것이 좋습니다. 테스트의 주요 목적은 타이밍에 의존하는 경쟁 조건 버그를 테스트하기위한 것이 아니라 그러한 경쟁 조건을 반복적으로 테스트하는 것이 불가능합니다. 대신 이러한 버그를 방지하기위한 잠금 전략이 여러 스레드가 의도 한대로 상호 작용할 수 있는지 테스트하는 것입니다. .

올바른 잠금 동작을 올바르게 테스트하려면 테스트에서 여러 스레드를 시작해야합니다. 테스트를 반복 가능하게하기 위해 스레드 간의 상호 작용이 예측 가능한 순서로 발생하기를 원합니다. 우리는 테스트에서 스레드를 외부에서 동기화하고 싶지 않습니다. 스레드가 외부에서 동기화되지 않은 프로덕션에서 발생할 수있는 버그를 가리기 때문입니다. 스레드 동기화에 타이밍 지연을 사용하는 것은 멀티 스레드 코드의 테스트를 작성해야 할 때마다 성공적으로 사용한 기술입니다.

지연 시간이 너무 짧으면 테스트가 실행될 수있는 다른 기계 간의 작은 타이밍 차이로 인해 타이밍이 꺼지고 테스트가 실패 할 수 있으므로 테스트가 취약 해집니다. 일반적으로 수행 한 작업은 테스트 실패를 유발하는 지연으로 시작하고 지연을 증가시켜 테스트가 내 개발 시스템에서 안정적으로 통과 한 다음 지연을 두 배로하여 테스트가 다른 시스템을 통과 할 가능성을 높입니다. 이것은 테스트에 거시적 시간이 걸리는 것을 의미하지만, 내 경험상 신중한 테스트 디자인은 그 시간을 12 초 이하로 제한 할 수 있습니다. 애플리케이션에 스레드 조정 코드가 필요한 장소가 많지 않아야하므로 테스트 스위트에 적합해야합니다.

마지막으로 테스트에서 잡은 버그 수를 추적하십시오. 테스트에 80 %의 코드 적용 범위가있는 경우 버그의 약 80 %가 발견 될 수 있습니다. 테스트가 잘 설계되었지만 버그가 발견되지 않으면 프로덕션에만 표시되는 추가 버그가 없을 가능성이 높습니다. 테스트에서 하나 또는 두 개의 버그가 발견되면 여전히 운이 좋을 수 있습니다. 그 외에도 스레드 처리 코드를주의 깊게 검토하거나 완전히 다시 작성하는 것이 좋습니다. 코드에는 여전히 코드가 생산 될 때까지 찾기가 매우 어려운 숨겨진 버그가 포함되어 있기 때문입니다. 그때 고치기 어렵다.


3
테스트는 버그가없는 것이 아니라 버그의 존재 만 밝혀 낼 수 있습니다. 원래 질문은 2 스레드 문제에 대해 묻습니다.이 경우 철저한 테스트가 가능하지만 종종 그렇지 않습니다. 가장 간단한 시나리오를 넘어서는 글 머리 기호를 물고 공식적인 방법을 사용해야하지만 단위 테스트를 건너 뛰지 마십시오! 올바른 멀티 스레드 코드를 작성하는 것은 처음에는 어렵지만, 마찬가지로 어려운 문제는 회귀에 대비하여 미래를 보장하는 것입니다.
Paul Williams

4
가장 잘 이해되지 않은 방법 중 하나에 대한 놀라운 요약. 귀하의 답변은 ppl이 일반적으로 간과하는 실제 분리에 영향을 미칩니다.
prash

1
당신이 그 길이의 수백 번의 테스트
만하

1
@TobySpeight 테스트는 일반 단위 테스트와 비교하여 길다. 스레드 코드가 가능한 한 간단하도록 적절하게 설계되어 있다면 수십 개의 테스트로 충분하다는 것을 알았습니다. 수백 개의 멀티 스레딩 테스트가 필요하다는 것은 거의 복잡한 스레딩 배열을 나타내는 것입니다.
Warren Dew

2
그것은 스레드 로직을 가능한 한 기능과 분리 할 수있게하는 좋은 주장입니다. 그리고 가능하면 테스트 스위트를 "모든 변경"및 "사전 커밋"세트로 나누십시오 (따라서 분 단위 테스트는 그다지 영향을받지 않습니다).
Toby Speight

22

또한 다중 스레드 코드를 테스트하는 데 심각한 문제가있었습니다. 그런 다음 Gerard Meszaros의 "xUnit Test Patterns"에서 정말 멋진 솔루션을 찾았습니다. 그가 묘사 한 패턴을 Humble object 라고 합니다 .

기본적으로 로직을 환경에서 분리 된 별도의 테스트하기 쉬운 구성 요소로 논리를 추출하는 방법에 대해 설명합니다. 이 로직을 테스트 한 후 복잡한 동작 (멀티 스레딩, 비동기 실행 등)을 테스트 할 수 있습니다.


20

주위에 몇 가지 도구가 있습니다. 다음은 일부 Java에 대한 요약입니다.

좋은 정적 분석 도구로는 FindBugs (유용한 힌트 제공), JLint , Java Pathfinder (JPF & JPF2) 및 Bogor가 있습니다.

MultithreadedTC 는 자체 테스트 사례를 설정해야하는 훌륭한 동적 분석 도구 (JUnit에 통합)입니다.

IBM Research의 ConTest 는 흥미 롭습니다. 모든 종류의 스레드 수정 동작 (예 : 절전 및 수율)을 삽입하여 버그를 무작위로 찾아내어 코드를 계측합니다.

SPIN 은 Java (및 기타) 구성 요소를 모델링하는 데 유용한 도구이지만 유용한 프레임 워크가 필요합니다. 그대로 사용하기는 어렵지만 사용 방법을 알고 있으면 매우 강력합니다. 꽤 많은 도구가 후드 아래에 SPIN을 사용합니다.

MultithreadedTC가 아마도 가장 주류이지만, 위에 나열된 정적 분석 도구 중 일부는 확실히 볼 가치가 있습니다.


16

결정 성은 단위 결정 테스트를 작성하는 데 도움이 될 수 있습니다. 시스템 어딘가의 상태가 업데이트 될 때까지 기다릴 수 있습니다. 예를 들면 다음과 같습니다.

await().untilCall( to(myService).myMethod(), greaterThan(3) );

또는

await().atMost(5,SECONDS).until(fieldIn(myObject).ofType(int.class), equalTo(1));

스칼라와 그루비도 지원합니다.

await until { something() > 4 } // Scala example

1
대기 시간은 훌륭합니다. 정확히 내가 찾던 것입니다!
Forge_7

14

스레드 코드 및 일반적으로 매우 복잡한 시스템을 테스트 (kinda)하는 또 다른 방법은 Fuzz Testing을 사용하는 것 입니다. 대단하지 않으며 모든 것을 찾을 수는 없지만 유용하고 간단합니다.

인용문:

퍼지 테스트 또는 퍼징은 프로그램의 입력에 임의의 데이터 ( "퍼지")를 제공하는 소프트웨어 테스트 기술입니다. 프로그램이 실패하거나 (예 : 충돌 또는 내장 코드 어설 션 실패) 결함을 확인할 수 있습니다. 퍼즈 테스트의 가장 큰 장점은 테스트 디자인이 매우 간단하고 시스템 동작에 대한 선입견이 없다는 것입니다.

...

퍼즈 테스트는 종종 블랙 박스 테스트를 사용하는 대규모 소프트웨어 개발 프로젝트에서 사용됩니다. 이러한 프로젝트에는 일반적으로 테스트 도구를 개발하기위한 예산이 있으며 퍼지 테스트는 비용 대비 높은 이익을 제공하는 기술 중 하나입니다.

...

그러나 퍼즈 테스트는 철저한 테스트 나 공식적인 방법을 대신 할 수는 없습니다. 시스템 동작의 무작위 샘플 만 제공 할 수 있으며, 퍼즈 테스트를 통과하면 많은 소프트웨어가 충돌없이 예외를 처리하는 것만 입증 할 수 있습니다. 올바르게 동작합니다. 따라서 퍼지 테스트는 품질을 보증하기보다는 버그 찾기 도구로만 간주 될 수 있습니다.


13

나는 이것을 많이했고, 그렇습니다.

몇 가지 팁 :

  • 여러 테스트 스레드를 실행하기위한 GroboUtils
  • 인터리빙이 반복마다 달라 지도록 인스트루먼트 클래스에 대한 alphaWorks ConTest
  • throwable필드를 작성 하고 체크인 tearDown하십시오 (목록 1 참조). 다른 스레드에서 잘못된 예외를 발견하면 예외를 throwable에 할당하십시오.
  • 필자는 Listing 2에서 utils 클래스를 작성했으며 특히 waitForVerify 및 waitForCondition이 매우 중요하다는 사실을 발견했다. 이는 테스트 성능을 크게 향상시킨다.
  • AtomicBoolean테스트에서 잘 활용 하십시오. 스레드 안전하고 콜백 클래스 등의 값을 저장하려면 최종 참조 유형이 필요한 경우가 종종 있습니다. 목록 3의 예를 참조하십시오.
  • @Test(timeout=60*1000)동시성 테스트가 중단 될 때 때때로 영원히 중단 될 수 있으므로 항상 테스트에 시간 초과 (예 :)를 제공해야합니다 .

목록 1 :

@After
public void tearDown() {
    if ( throwable != null )
        throw throwable;
}

목록 2 :

import static org.junit.Assert.fail;
import java.io.File;
import java.lang.reflect.InvocationHandler;
import java.lang.reflect.Proxy;
import java.util.Random;
import org.apache.commons.collections.Closure;
import org.apache.commons.collections.Predicate;
import org.apache.commons.lang.time.StopWatch;
import org.easymock.EasyMock;
import org.easymock.classextension.internal.ClassExtensionHelper;
import static org.easymock.classextension.EasyMock.*;

import ca.digitalrapids.io.DRFileUtils;

/**
 * Various utilities for testing
 */
public abstract class DRTestUtils
{
    static private Random random = new Random();

/** Calls {@link #waitForCondition(Integer, Integer, Predicate, String)} with
 * default max wait and check period values.
 */
static public void waitForCondition(Predicate predicate, String errorMessage) 
    throws Throwable
{
    waitForCondition(null, null, predicate, errorMessage);
}

/** Blocks until a condition is true, throwing an {@link AssertionError} if
 * it does not become true during a given max time.
 * @param maxWait_ms max time to wait for true condition. Optional; defaults
 * to 30 * 1000 ms (30 seconds).
 * @param checkPeriod_ms period at which to try the condition. Optional; defaults
 * to 100 ms.
 * @param predicate the condition
 * @param errorMessage message use in the {@link AssertionError}
 * @throws Throwable on {@link AssertionError} or any other exception/error
 */
static public void waitForCondition(Integer maxWait_ms, Integer checkPeriod_ms, 
    Predicate predicate, String errorMessage) throws Throwable 
{
    waitForCondition(maxWait_ms, checkPeriod_ms, predicate, new Closure() {
        public void execute(Object errorMessage)
        {
            fail((String)errorMessage);
        }
    }, errorMessage);
}

/** Blocks until a condition is true, running a closure if
 * it does not become true during a given max time.
 * @param maxWait_ms max time to wait for true condition. Optional; defaults
 * to 30 * 1000 ms (30 seconds).
 * @param checkPeriod_ms period at which to try the condition. Optional; defaults
 * to 100 ms.
 * @param predicate the condition
 * @param closure closure to run
 * @param argument argument for closure
 * @throws Throwable on {@link AssertionError} or any other exception/error
 */
static public void waitForCondition(Integer maxWait_ms, Integer checkPeriod_ms, 
    Predicate predicate, Closure closure, Object argument) throws Throwable 
{
    if ( maxWait_ms == null )
        maxWait_ms = 30 * 1000;
    if ( checkPeriod_ms == null )
        checkPeriod_ms = 100;
    StopWatch stopWatch = new StopWatch();
    stopWatch.start();
    while ( !predicate.evaluate(null) ) {
        Thread.sleep(checkPeriod_ms);
        if ( stopWatch.getTime() > maxWait_ms ) {
            closure.execute(argument);
        }
    }
}

/** Calls {@link #waitForVerify(Integer, Object)} with <code>null</code>
 * for {@code maxWait_ms}
 */
static public void waitForVerify(Object easyMockProxy)
    throws Throwable
{
    waitForVerify(null, easyMockProxy);
}

/** Repeatedly calls {@link EasyMock#verify(Object[])} until it succeeds, or a
 * max wait time has elapsed.
 * @param maxWait_ms Max wait time. <code>null</code> defaults to 30s.
 * @param easyMockProxy Proxy to call verify on
 * @throws Throwable
 */
static public void waitForVerify(Integer maxWait_ms, Object easyMockProxy)
    throws Throwable
{
    if ( maxWait_ms == null )
        maxWait_ms = 30 * 1000;
    StopWatch stopWatch = new StopWatch();
    stopWatch.start();
    for(;;) {
        try
        {
            verify(easyMockProxy);
            break;
        }
        catch (AssertionError e)
        {
            if ( stopWatch.getTime() > maxWait_ms )
                throw e;
            Thread.sleep(100);
        }
    }
}

/** Returns a path to a directory in the temp dir with the name of the given
 * class. This is useful for temporary test files.
 * @param aClass test class for which to create dir
 * @return the path
 */
static public String getTestDirPathForTestClass(Object object) 
{

    String filename = object instanceof Class ? 
        ((Class)object).getName() :
        object.getClass().getName();
    return DRFileUtils.getTempDir() + File.separator + 
        filename;
}

static public byte[] createRandomByteArray(int bytesLength)
{
    byte[] sourceBytes = new byte[bytesLength];
    random.nextBytes(sourceBytes);
    return sourceBytes;
}

/** Returns <code>true</code> if the given object is an EasyMock mock object 
 */
static public boolean isEasyMockMock(Object object) {
    try {
        InvocationHandler invocationHandler = Proxy
                .getInvocationHandler(object);
        return invocationHandler.getClass().getName().contains("easymock");
    } catch (IllegalArgumentException e) {
        return false;
    }
}
}

목록 3 :

@Test
public void testSomething() {
    final AtomicBoolean called = new AtomicBoolean(false);
    subject.setCallback(new SomeCallback() {
        public void callback(Object arg) {
            // check arg here
            called.set(true);
        }
    });
    subject.run();
    assertTrue(called.get());
}

2
시간 초과는 좋은 생각이지만 테스트 시간이 초과되면 나중에 해당 실행 결과가 의심됩니다. 시간 초과 된 테스트에서 여전히 일부 스레드가 실행되어 엉망이 될 수 있습니다.
Don Kirkby

12

이미 언급했듯이 MT 코드의 정확성을 테스트하는 것은 매우 어려운 문제입니다. 결국 코드에 동기화되지 않은 데이터 레이스가 없도록 보장합니다. 이것의 문제점은 많은 제어 권한이없는 스레드 실행 (인터리빙) 가능성이 무한히 많다는 것입니다 ( 기사 를 읽으십시오 ). 간단한 시나리오에서는 추론을 통해 실제로 정확성을 입증 할 수 있지만 일반적으로 그렇지 않습니다. 특히 동기화를 피하거나 최소화하고 가장 분명하고 쉬운 동기화 옵션을 사용하지 않으려는 경우에 특히 그렇습니다.

내가 따르는 접근법은 잠재적으로 감지되지 않은 데이터 경쟁이 발생할 가능성을 높이기 위해 동시 테스트 코드를 작성하는 것입니다. 그런 다음 한 번 그 테스트를 실행했습니다.) 컴퓨터 과학자가 이런 종류의 도구를 과시하는 이야기 (사양에서 무작위로 테스트를 고안 한 다음 정의 된 불변성을 검사하는 동시에 야생으로 실행하는 이야기)를 우연히 발견했습니다. 파손될 수 있습니다).

그건 그렇고, MT 코드 테스트 의이 측면은 여기에서 언급되지 않았다고 생각합니다 : 무작위로 확인할 수있는 코드의 변형을 식별하십시오. 불행하게도, 이러한 불변량을 찾는 것도 상당히 어려운 문제입니다. 또한 실행하는 동안 항상 유지되지 않을 수도 있으므로 실행 지점이 사실이라고 예상 할 수있는 실행 지점을 찾아서 적용해야합니다. 코드 실행을 그러한 상태로 만드는 것도 어려운 문제입니다 (그리고 동시성 문제가 발생할 수도 있습니다. 휴, 어렵습니다!

읽을 흥미로운 링크 :


저자는 테스트에서 무작위를 의미합니다. 여러 언어로 포팅 된 QuickCheck 일 수 있습니다 . 동시 시스템에 대한 이러한 테스트에 대한 이야기를 여기서
Max

6

Pete Goodliffe스레드 코드 의 단위 테스트 에 대한 시리즈를 제공합니다 .

어렵다. 더 쉬운 방법을 취하고 스레딩 코드를 실제 테스트에서 추상화 된 상태로 유지하려고합니다. Pete는 내가하는 방식이 잘못되었다고 언급하지만 분리가 제대로되었거나 방금 운이 좋았습니다.


6
지금까지 출판 된 두 기사를 읽었지만 그다지 도움이되지는 않았습니다. 그는 많은 구체적인 조언을 제공하지 않고 어려움에 대해 이야기합니다. 아마도 미래의 기사가 개선 될 것입니다.
돈 커크비

6

Java의 경우 JCIP의 12 장을 확인하십시오 . 최소한 동시 코드의 정확성과 불변성을 테스트하기 위해 결정 론적 멀티 스레드 단위 테스트를 작성하는 구체적인 예가 있습니다.

단위 테스트로 스레드 안전성을 입증하는 것이 훨씬 더 복잡합니다. 제 생각에는 이것이 다양한 플랫폼 / 구성에 대한 자동화 된 통합 테스트를 통해 더 잘 제공된다는 것입니다.


6

병렬 스레드에서 실행할 두 개 이상의 테스트 메소드를 작성하고 각각 테스트 할 오브젝트를 호출합니다. 다른 스레드의 호출 순서를 조정하기 위해 Sleep () 호출을 사용했지만 실제로 신뢰할 수는 없습니다. 타이밍이 정상적으로 작동하기에 충분히 오래 자야하기 때문에 속도가 훨씬 느립니다.

FindBugs를 작성한 동일한 그룹에서 Multithreaded TC Java 라이브러리 를 찾았습니다 . Sleep ()을 사용하지 않고 이벤트 순서를 지정할 수 있으며 안정적입니다. 아직 시도하지 않았습니다.

이 방법의 가장 큰 한계는 문제가 발생할 것으로 의심되는 시나리오 만 테스트 할 수 있다는 것입니다. 다른 사람들이 말했듯이 멀티 스레드 코드를 소수의 간단한 클래스로 분리하여 철저히 테스트 할 수 있기를 바랍니다.

문제가 발생할 것으로 예상되는 시나리오를주의 깊게 테스트 한 후에는 클래스에서 동시에 여러 개의 동시 요청을 발생시키는 비과학적인 테스트는 예기치 않은 문제를 찾는 좋은 방법입니다.

업데이트 : Multithreaded TC Java 라이브러리로 조금 연주했는데 잘 작동합니다. 또한 일부 기능을 TickingTest 라는 .NET 버전으로 이식 했습니다 .


5

제어 및 격리 프레임 워크가 반전되는 모든 단위 테스트를 처리하는 것과 동일한 방식으로 스레드 구성 요소의 단위 테스트를 처리합니다. 나는 .Net-arena에서 개발하고 상자에서 스레딩 (다른 것들 중에서)은 완전히 격리하기가 매우 어렵습니다 (거의 불가능하다고 말할 것입니다).

따라서 다음과 같은 모양의 래퍼를 작성했습니다 (간체).

public interface IThread
{
    void Start();
    ...
}

public class ThreadWrapper : IThread
{
    private readonly Thread _thread;

    public ThreadWrapper(ThreadStart threadStart)
    {
        _thread = new Thread(threadStart);
    }

    public Start()
    {
        _thread.Start();
    }
}

public interface IThreadingManager
{
    IThread CreateThread(ThreadStart threadStart);
}

public class ThreadingManager : IThreadingManager
{
    public IThread CreateThread(ThreadStart threadStart)
    {
         return new ThreadWrapper(threadStart)
    }
}

여기에서 IThreadingManager를 구성 요소에 쉽게 주입하고 선택한 격리 프레임 워크를 사용하여 테스트 중에 예상 한대로 스레드가 작동하도록 할 수 있습니다.

그것은 지금까지 훌륭하게 작동했으며 스레드 풀, System.Environment, Sleep 등의 것들에 대해서도 동일한 접근 방식을 사용합니다.


5

관련 답변을 살펴보십시오.

커스텀 배리어를위한 테스트 클래스 디자인

Java에 편향되어 있지만 옵션에 대한 합리적인 요약이 있습니다.

요약하자면 (IMO) 정확성을 보장하는 멋진 프레임 워크를 사용하는 것이 아니라 멀티 스레드 코드를 디자인하는 방법을 설명합니다. 관심사 (동시성 및 기능성)를 나누는 것은 자신감을 높이는 데 큰 도움이됩니다. 테스트에 따라 성장하는 객체 지향 소프트웨어 는 내가 할 수있는 것보다 몇 가지 옵션을 더 잘 설명합니다.

정적 분석 및 공식 방법 ( 동시성 : 상태 모델 및 Java 프로그램 참조 )은 옵션이지만 상용 개발에 제한적으로 사용됩니다.

로드 / 담금 스타일 테스트가 문제를 강조하는 경우는 거의 없습니다.

행운을 빕니다!


또한 언급해야한다 tempus-fugit, 여기 라이브러리를하는 helps write and test concurrent code)
Idolon

4

방금 최근 Threadsafe라는 도구를 발견했습니다. 그것은 findbugs와 매우 유사하지만 특히 멀티 스레딩 문제를 발견하기위한 정적 분석 도구입니다. 테스트를 대체하지는 않지만 안정적인 멀티 스레드 Java 작성의 일부로 권장 할 수 있습니다.

또한 클래스 하위 가정, 동시 클래스를 통해 안전하지 않은 개체에 액세스하고 이중 검사 잠금 패러다임을 사용할 때 누락 된 휘발성 수정자를 발견하는 등의 문제에 대해 매우 미묘한 잠재적 문제를 포착합니다.

멀티 스레드 Java를 작성 하면 샷을 제공하십시오.


3

다음 기사에서는 두 가지 솔루션을 제안합니다. 세마포어 (CountDownLatch)를 래핑하고 내부 스레드에서 데이터를 외부화하는 것과 같은 기능을 추가합니다. 이 목적을 달성하는 또 다른 방법은 스레드 풀을 사용하는 것입니다 (관심 지점 참조).

스프링클러-고급 동기화 개체


3
여기에서 접근 방식을 설명하십시오. 향후 외부 링크가 작동하지 않을 수 있습니다.
Uooo

2

나는 지난주 대부분의 시간을 대학 도서관에서 동시 코드 디버깅을 연구하는 데 보냈다. 가장 큰 문제는 동시 코드가 결정적이지 않다는 것입니다. 일반적으로 아카데믹 디버깅은 다음 세 가지 캠프 중 하나에 속합니다.

  1. 이벤트 추적 / 재생 이를 위해서는 이벤트 모니터가 필요하며 전송 된 이벤트를 검토하십시오. UT 프레임 워크에서는 테스트의 일부로 이벤트를 수동으로 전송 한 후 사후 검토를 수행해야합니다.
  2. 스크립트 가능. 여기서 일련의 트리거로 실행중인 코드와 상호 작용합니다. "x> foo, baz ()"에서. 이는 특정 조건에서 특정 테스트를 트리거하는 런타임 시스템이있는 UT 프레임 워크로 해석 될 수 있습니다.
  3. 인터렉티브. 자동 테스트 상황에서는 분명히 작동하지 않습니다. ;)

위의 주석가들이 알 수 있듯이 동시 시스템을보다 결정적인 상태로 설계 할 수 있습니다. 그러나 제대로하지 않으면 순차 시스템을 다시 설계하는 것입니다.

내 제안은 스레드되는 것과 스레드되지 않는 것에 대해 매우 엄격한 디자인 프로토콜을 갖는 데 집중하는 것입니다. 요소 간 종속성이 최소화되도록 인터페이스를 제한하면 훨씬 쉽습니다.

행운을 빌어 계속 문제를 해결하십시오.


2

스레드 코드를 테스트하는 불행한 작업을 수행했으며 필자가 작성한 가장 어려운 테스트입니다.

테스트를 작성할 때 델리게이트와 이벤트를 조합하여 사용했습니다. 기본적으로 모든 종류의 PropertyNotifyChanged이벤트를 사용하는 것에 관한 WaitCallback것입니다.ConditionalWaiter 투표 .

이것이 최선의 접근 방법인지 확실하지 않지만 나에게 도움이되었습니다.


1

"멀티 스레드"코드 하에서

  • 상태 저장 및 변경 가능
  • 여러 스레드가 동시에 액세스 / 수정

다른 말로, 우리는 오늘날 상태가 매우 드 물어야 할 커스텀 스테이트 풀 스레드 안전 클래스 / 메소드 / 유닛 테스트에 대해 이야기하고 있습니다.

이 짐승은 드물기 때문에, 우선 우리는 그것을 쓸 유효한 변명이 있는지 확인해야합니다.

1 단계. 동일한 동기화 컨텍스트에서 상태 수정을 고려하십시오.

오늘날 IO 또는 기타 느린 작업이 백그라운드로 오프로드되지만 공유 상태가 하나의 동기화 컨텍스트에서 업데이트 및 쿼리되는 작성 가능한 동시 및 비동기 코드를 작성하는 것이 쉽습니다. 예를 들어 async / await 작업 및 .NET의 Rx 등-모두 설계 상 테스트가 가능합니다. "실제"작업과 스케줄러를 대체하여 테스트를 결정적으로 수행 할 수 있습니다 (단, 문제의 범위를 벗어남).

매우 제한적으로 들릴지 모르지만이 방법은 놀랍게 잘 작동합니다. 스레드 안전 상태를 만들 필요 없이이 스타일로 전체 앱을 작성할 수 있습니다.

2 단계. 단일 동기화 컨텍스트에서 공유 상태를 조작 할 수없는 경우 절대 불가능합니다.

바퀴가 재발 명되지 않도록하십시오 / 작업에 적용 할 수있는 표준 대안이 없습니다. 코드는 매우 응집력이 높고 한 단위 내에 포함되어있을 가능성이 높습니다. 예를 들어 해시 맵 또는 컬렉션과 같은 표준 스레드 안전 데이터 구조의 특별한 경우입니다.

참고 : 코드가 크거나 여러 클래스에 걸쳐 있고 멀티 스레드 상태 조작이 필요한 경우 디자인이 좋지 않을 가능성이 매우 높습니다 .1 단계를 다시 고려하십시오.

3 단계. 이 단계에 도달하면 자체 사용자 정의 상태 저장 스레드 안전 클래스 / 방법 / 단위 를 테스트해야합니다 .

나는 정직하게 죽을 것이다. 나는 그런 코드에 대해 적절한 테스트를 작성할 필요가 없었다. 대부분의 경우 1 단계에서, 때로는 2 단계에서 나옵니다. 지난 몇 년 전에 사용자 정의 스레드 안전 코드를 작성해야했을 때 단위 테스트를 채택하기 전에 / 아마도 작성할 필요가 없었습니다. 어쨌든 현재의 지식으로.

그런 코드를 실제로 테스트해야한다면 ( 마지막으로 실제 답변 ) 아래 몇 가지를 시도해보십시오.

  1. 비 결정적 스트레스 테스트. 예를 들어 100 개의 스레드를 동시에 실행하고 최종 결과가 일치하는지 확인하십시오. 이는 여러 사용자 시나리오의 고급 / 통합 테스트에보다 일반적이지만 단위 수준에서도 사용할 수 있습니다.

  2. 하나의 스레드가 다른 스레드보다 먼저 작업을 수행해야하는 결정적인 시나리오를 만드는 데 도움이되도록 테스트에 일부 코드를 삽입 할 수있는 일부 테스트 '후크'를 노출하십시오. 추악한 것처럼 나는 더 나은 것을 생각할 수 없다.

  3. 스레드를 실행하고 특정 순서대로 작업을 수행하기위한 지연 기반 테스트 엄밀히 말하면 그러한 테스트는 결정적이지 않습니다 (시스템 정지 / 세계 GC 수집 가능성으로 인해 조정 지연을 왜곡 할 수 있음). 또한 추악하지만 후크를 피할 수 있습니다.


0

J2E 코드의 경우 스레드의 동시성 테스트에 SilkPerformer, LoadRunner 및 JMeter를 사용했습니다. 그들은 모두 같은 일을합니다. 기본적으로 TCP / IP 데이터 스트림을 분석하고 앱 서버에 동시에 요청하는 여러 사용자를 시뮬레이션하기 위해 필요한 프록시 서버 버전을 관리하기위한 비교적 간단한 인터페이스를 제공합니다. 프록시 서버는 요청을 처리 한 후 서버로 전송 된 전체 페이지 및 URL과 서버의 응답을 제공하여 요청을 분석하는 등의 작업을 수행 할 수 있습니다.

안전하지 않은 http 모드에서 버그를 발견 할 수 있습니다. 여기서 적어도 전송되는 양식 데이터를 분석하고 각 사용자에 대해 체계적으로 변경할 수 있습니다. 그러나 실제 테스트는 https (Secureed Socket Layers)에서 실행할 때입니다. 그런 다음, 세션 및 쿠키 데이터를 체계적으로 변경하는 것과 경쟁해야하는데, 이는 조금 더 복잡 할 수 있습니다.

동시성을 테스트하는 동안 발견 한 가장 좋은 버그는 개발자가 로그인시 LDAP 서버에 연결 요청을 닫기 위해 Java 가비지 수집에 의존했음을 발견했을 때 발생했습니다. 이로 인해 사용자가 노출되었습니다. 서버가 무릎을 꿇었을 때 무슨 일이 있었는지 분석하려고 할 때 다른 사용자의 세션과 매우 혼란스러운 결과로 몇 초마다 하나의 트랜잭션을 거의 완료 할 수 없습니다.

결국, 당신이나 누군가가 아마 방금 언급 한 것과 같은 실수에 대한 코드를 버클 링하고 분석해야 할 것입니다. 그리고 위에서 설명한 문제를 풀었을 때 발생한 부서와 같은 부서 간 공개 토론이 가장 유용합니다. 그러나 이러한 도구는 멀티 스레드 코드를 테스트하기위한 최상의 솔루션입니다. JMeter는 오픈 소스입니다. SilkPerformer 및 LoadRunner는 독점적입니다. 앱이 스레드로부터 안전한지 정말로 알고 싶다면 큰 소년이 그렇게합니다. 나는 대기업을 위해 전문적 으로이 작업을 수행 했으므로 추측하지 않습니다. 나는 개인적인 경험에서 말하고 있습니다.

주의 사항 : 이러한 도구를 이해하는 데 시간이 걸립니다. 이미 멀티 스레드 프로그래밍에 노출되지 않은 경우 소프트웨어를 설치하고 GUI를 실행하는 것은 문제가되지 않습니다. 이해해야 할 3 가지 주요 범주 (양식, 세션 및 쿠키 데이터)를 식별하려고 노력했습니다. 최소한 이러한 주제를 이해하는 것으로 시작하면 빠른 결과에 집중하는 데 도움이되기를 바랍니다. 전체 문서.


0

동시성은 메모리 모델, 하드웨어, 캐시 및 코드 간의 복잡한 상호 작용입니다. Java의 경우 이러한 테스트는 주로 jcstress에 의해 부분적으로 해결되었습니다 . 해당 라이브러리의 작성자는 많은 JVM, GC 및 Java 동시성 기능의 작성자로 알려져 있습니다.

그러나이 라이브러리조차도 우리가 테스트하고있는 것을 정확히 알 수 있도록 Java 메모리 모델 사양에 대한 지식이 필요합니다. 그러나이 노력의 초점은 mircobenchmarks라고 생각합니다. 거대한 비즈니스 응용 프로그램이 아닙니다.


0

예제 코드에서 Rust를 언어로 사용하는 주제에 대한 기사가 있습니다.

https://medium.com/@polyglot_factotum/rust-concurrency-five-easy-pieces-871f1c62906a

요컨대, 동시 로직을 작성하여 채널 및 condvars와 같은 도구를 사용하여 여러 실행 스레드와 관련된 비결정론에 강력합니다.

그런 다음 이것이 "구성 요소"를 구성한 방법 인 경우이를 테스트하는 가장 쉬운 방법은 채널을 사용하여 메시지를 보낸 다음 다른 채널을 차단하여 구성 요소가 특정 메시지를 보내도록하는 것입니다.

링크 된 기사는 단위 테스트를 사용하여 완전히 작성되었습니다.


-1

간단한 새 Thread (runnable) .run ()을 테스트하는 경우 Thread를 조롱하여 실행 가능 파일을 순차적으로 실행할 수 있습니다.

예를 들어, 테스트 된 객체의 코드가 이와 같은 새로운 스레드를 호출하는 경우

Class TestedClass {
    public void doAsychOp() {
       new Thread(new myRunnable()).start();
    }
}

그런 다음 새 스레드를 조롱하고 실행 가능한 인수를 순차적으로 실행하면 도움이 될 수 있습니다.

@Mock
private Thread threadMock;

@Test
public void myTest() throws Exception {
    PowerMockito.mockStatic(Thread.class);
    //when new thread is created execute runnable immediately 
    PowerMockito.whenNew(Thread.class).withAnyArguments().then(new Answer<Thread>() {
        @Override
        public Thread answer(InvocationOnMock invocation) throws Throwable {
            // immediately run the runnable
            Runnable runnable = invocation.getArgumentAt(0, Runnable.class);
            if(runnable != null) {
                runnable.run();
            }
            return threadMock;//return a mock so Thread.start() will do nothing         
        }
    }); 
    TestedClass testcls = new TestedClass()
    testcls.doAsychOp(); //will invoke myRunnable.run in current thread
    //.... check expected 
}

-3

(가능한 경우) 스레드를 사용하지 말고 액터 / 액티브 오브젝트를 사용하십시오. 테스트하기 쉽습니다.


2
@OMTheEternity 어쩌면 가장 좋은 대답은 imo입니다.
Dill

-5

EasyMock.makeThreadSafe를 사용하여 테스트 인스턴스를 스레드로부터 안전하게 만들 수 있습니다


멀티 스레드 코드를 테스트 할 수있는 방법은 아닙니다. 문제는 테스트 코드가 멀티 스레드를 실행하는 것이 아니라 일반적으로 멀티 스레드를 실행하는 코드를 테스트한다는 것입니다. 그리고 데이터 레이스를 더 이상 테스트하지 않기 때문에 모든 것을 동기화 할 수 없습니다.
bennidi
당사 사이트를 사용함과 동시에 당사의 쿠키 정책개인정보 보호정책을 읽고 이해하였음을 인정하는 것으로 간주합니다.
Licensed under cc by-sa 3.0 with attribution required.