멀티 스레드 코드를 테스트하는 악몽을 피할 수 없었습니다. 사람들이 성공적인 실행을 위해 스레드를 사용하는 코드를 테스트하는 방법에 대해 어떻게 물어 보거나 두 스레드가 주어진 방식으로 상호 작용할 때 나타나는 종류의 문제를 테스트하는 방법에 대해 묻고 싶습니다.
이것은 오늘날 프로그래머에게 정말로 중요한 문제인 것 같습니다.이 지식에 대한 지식을 모으는 것이 유용 할 것입니다.
멀티 스레드 코드를 테스트하는 악몽을 피할 수 없었습니다. 사람들이 성공적인 실행을 위해 스레드를 사용하는 코드를 테스트하는 방법에 대해 어떻게 물어 보거나 두 스레드가 주어진 방식으로 상호 작용할 때 나타나는 종류의 문제를 테스트하는 방법에 대해 묻고 싶습니다.
이것은 오늘날 프로그래머에게 정말로 중요한 문제인 것 같습니다.이 지식에 대한 지식을 모으는 것이 유용 할 것입니다.
답변:
이걸하는 쉬운 방법은 없어 나는 본질적으로 멀티 스레드 프로젝트에서 일하고 있습니다. 운영 체제에서 이벤트가 발생하며 동시에 처리해야합니다.
복잡한 멀티 스레드 응용 프로그램 코드 테스트를 처리하는 가장 간단한 방법은 다음과 같습니다. 테스트하기에 너무 복잡하면 잘못하고 있습니다. 여러 개의 스레드가있는 단일 인스턴스가 있고 이러한 스레드가 서로 단계별로 진행되는 상황을 테스트 할 수없는 경우 설계를 다시 작성해야합니다. 이것만큼 간단하고 복잡합니다.
스레드가 동시에 인스턴스를 실행하는 것을 방지하는 멀티 스레딩 프로그래밍 방법에는 여러 가지가 있습니다. 가장 간단한 방법은 모든 객체를 불변으로 만드는 것입니다. 물론 일반적으로는 불가능합니다. 따라서 디자인에서 스레드가 동일한 인스턴스와 상호 작용하는 장소를 식별하고 해당 장소의 수를 줄여야합니다. 이렇게하면 멀티 스레딩이 실제로 발생하는 몇 가지 클래스를 격리하여 시스템 테스트의 전체적인 복잡성을 줄일 수 있습니다.
그러나이 작업을 수행해도 두 스레드가 서로 밟는 모든 상황을 테스트 할 수는 없다는 것을 알아야합니다. 이를 위해서는 동일한 테스트에서 두 개의 스레드를 동시에 실행 한 다음 주어진 순간에 어떤 행을 실행하는지 정확하게 제어해야합니다. 가장 좋은 방법은이 상황을 시뮬레이션하는 것입니다. 그러나 테스트를 위해 특별히 코드를 작성해야 할 수도 있으며 이는 진정한 솔루션을 향한 반 단계에 불과합니다.
스레딩 문제에 대한 코드를 테스트하는 가장 좋은 방법은 코드를 정적으로 분석하는 것입니다. 스레드 코드가 유한 스레드 안전 패턴 세트를 따르지 않으면 문제가있을 수 있습니다. VS의 Code Analysis에는 스레딩에 대한 지식이 포함되어 있다고 생각하지만 많지는 않습니다.
현재 상황이 다가오고 있으며 앞으로 다가올 좋은시기가 될 것이므로 멀티 스레드 앱을 테스트하는 가장 좋은 방법은 스레드 코드의 복잡성을 최대한 줄이는 것입니다. 스레드가 상호 작용하는 영역을 최소화하고 최대한 테스트하고 코드 분석을 사용하여 위험 영역을 식별하십시오.
이 질문이 게시 된 지 오래되었지만 아직 답변되지 않았습니다 ...
kleolb02 의 답변은 좋은 것입니다. 자세한 내용을 살펴 보겠습니다.
C # 코드를 연습하는 방법이 있습니다. 단위 테스트의 경우 재현 가능한 테스트 를 프로그래밍 할 수 있어야합니다 . 이는 다중 스레드 코드에서 가장 큰 문제입니다. 작동 테스트 장치로 비동기 코드를 강제 대한 내 대답의 목표 그래서 기적 .
Gerard Meszardos의 저서 " xUnit Test Patterns " 에서 발췌 한 아이디어 이며 "Humble Object"(p. 695)라고합니다. 핵심 로직 코드와 비동기 코드처럼 냄새가 나는 것은 분리해야합니다. 결과적으로 핵심 로직의 클래스가 생겨 동기식 으로 작동 합니다. .
이를 통해 핵심 논리 코드를 동기식 으로 테스트 할 수 있습니다. 코어 로직에서 수행하는 통화 타이밍을 완전히 제어 할 수 있으므로 재현 가능한 테스트를 수행 할 수 있습니다 . 그리고 이것이 핵심 논리와 비동기 논리를 분리함으로써 얻는 이점입니다.
이 코어 로직은 다른 클래스에 의해 랩핑되어야하며,이 클래스는 코어 로직에 대한 호출을 비동기 적으로 수신하고 이러한 호출을 코어 로직에 위임 합니다. 생산 코드는 해당 클래스를 통해서만 핵심 로직에 액세스합니다. 이 클래스는 호출 만 위임해야하기 때문에 논리가 많지 않은 매우 "멍청한"클래스입니다. 따라서이 비동기 작업 클래스에 대한 단위 테스트를 최소한으로 유지할 수 있습니다.
위의 모든 것 (클래스 간의 상호 작용 테스트)은 구성 요소 테스트입니다. 또한이 경우 "Humble Object"패턴을 고수하면 타이밍을 절대적으로 제어 할 수 있어야합니다.
참으로 힘든 것! 내 (C ++) 단위 테스트에서 사용 된 동시성 패턴의 선을 따라 여러 범주로 나누었습니다.
단일 스레드에서 작동하고 스레드를 인식하지 못하는 클래스에 대한 단위 테스트-평소처럼 쉽게 테스트합니다.
동기화 된 공개 API를 노출시키는 모니터 객체 (호출자의 제어 스레드에서 동기화 된 메소드를 실행하는)에 대한 단위 테스트 -API를 실행하는 여러 모의 스레드를 인스턴스화합니다. 수동 객체의 내부 조건을 행사하는 시나리오를 구성하십시오. 오랜 시간 동안 여러 스레드에서 기본적으로 이길 수있는 장기 실행 테스트를 하나 포함하십시오. 이것은 내가 아는 과학적이지 않지만 자신감을 쌓아줍니다.
클래스 디자인에 따라 변형 된 위의 # 2와 유사하게 활성 객체 (자체 스레드 또는 스레드를 캡슐화하는 객체)에 대한 단위 테스트 . 퍼블릭 API는 블로킹 또는 비 블로킹 일 수 있으며, 발신자는 미래를 얻거나, 데이터가 대기열에 도착하거나 대기열에서 제외 될 수 있습니다. 여기에는 가능한 많은 조합이 있습니다. 흰색 상자. 테스트중인 객체를 호출하려면 여전히 여러 모의 스레드가 필요합니다.
여담으로:
내가하는 내부 개발자 교육에서는 동시성 필러 와 이러한 두 가지 패턴을 동시성 문제를 생각하고 분해하는 기본 프레임 워크로 가르칩니다 . 분명히 더 고급 개념이 있지만이 기본 세트는 엔지니어가 수프를 피하는 데 도움이된다는 것을 알았습니다. 또한 위에서 설명한 것처럼 더 단위 테스트가 가능한 코드로 이어집니다.
최근 몇 년 동안 여러 프로젝트에 대한 스레드 처리 코드를 작성할 때이 문제에 여러 번 직면했습니다. 다른 답변의 대부분은 대안을 제공하면서 실제로 테스트에 대한 질문에 대답하지 않기 때문에 늦은 답변을 제공하고 있습니다. 내 대답은 멀티 스레드 코드에 대한 대안이없는 경우에 해결됩니다. 완전성을 위해 코드 디자인 문제를 다루고 단위 테스트에 대해서도 설명합니다.
테스트 가능한 다중 스레드 코드 작성
가장 먼저해야 할 일은 프로덕션 스레드 처리 코드를 실제 데이터 처리를 수행하는 모든 코드와 분리하는 것입니다. 이렇게하면 데이터 처리를 단일 스레드 코드로 테스트 할 수 있으며 멀티 스레드 코드가 수행하는 유일한 작업은 스레드를 조정하는 것입니다.
두 번째로 기억해야 할 것은 멀티 스레드 코드의 버그는 확률 적이라는 것입니다. 가장 빈번하게 나타나는 버그는 프로덕션 환경으로 몰래 들어가고 프로덕션 환경에서도 재현하기 어려운 버그로, 가장 큰 문제를 일으킬 것입니다. 이러한 이유로 코드를 빠르게 작성한 다음 작동 할 때까지 디버깅하는 표준 코딩 방식은 멀티 스레드 코드에는 좋지 않습니다. 쉬운 버그가 수정되고 위험한 버그가 여전히 존재하는 코드가 생성됩니다.
대신 멀티 스레드 코드를 작성할 때는 먼저 버그를 작성하지 않으려는 자세로 코드를 작성해야합니다. 데이터 처리 코드를 올바르게 제거했다면 스레드 처리 코드는 충분히 작아야합니다 (바람직하게는 몇 줄, 최악의 경우 수십 줄). 스레딩을 이해하면 시간을내어 조심하십시오.
멀티 스레드 코드에 대한 단위 테스트 작성
멀티 스레드 코드를 최대한 신중하게 작성하면 해당 코드에 대한 테스트를 작성하는 것이 좋습니다. 테스트의 주요 목적은 타이밍에 의존하는 경쟁 조건 버그를 테스트하기위한 것이 아니라 그러한 경쟁 조건을 반복적으로 테스트하는 것이 불가능합니다. 대신 이러한 버그를 방지하기위한 잠금 전략이 여러 스레드가 의도 한대로 상호 작용할 수 있는지 테스트하는 것입니다. .
올바른 잠금 동작을 올바르게 테스트하려면 테스트에서 여러 스레드를 시작해야합니다. 테스트를 반복 가능하게하기 위해 스레드 간의 상호 작용이 예측 가능한 순서로 발생하기를 원합니다. 우리는 테스트에서 스레드를 외부에서 동기화하고 싶지 않습니다. 스레드가 외부에서 동기화되지 않은 프로덕션에서 발생할 수있는 버그를 가리기 때문입니다. 스레드 동기화에 타이밍 지연을 사용하는 것은 멀티 스레드 코드의 테스트를 작성해야 할 때마다 성공적으로 사용한 기술입니다.
지연 시간이 너무 짧으면 테스트가 실행될 수있는 다른 기계 간의 작은 타이밍 차이로 인해 타이밍이 꺼지고 테스트가 실패 할 수 있으므로 테스트가 취약 해집니다. 일반적으로 수행 한 작업은 테스트 실패를 유발하는 지연으로 시작하고 지연을 증가시켜 테스트가 내 개발 시스템에서 안정적으로 통과 한 다음 지연을 두 배로하여 테스트가 다른 시스템을 통과 할 가능성을 높입니다. 이것은 테스트에 거시적 시간이 걸리는 것을 의미하지만, 내 경험상 신중한 테스트 디자인은 그 시간을 12 초 이하로 제한 할 수 있습니다. 애플리케이션에 스레드 조정 코드가 필요한 장소가 많지 않아야하므로 테스트 스위트에 적합해야합니다.
마지막으로 테스트에서 잡은 버그 수를 추적하십시오. 테스트에 80 %의 코드 적용 범위가있는 경우 버그의 약 80 %가 발견 될 수 있습니다. 테스트가 잘 설계되었지만 버그가 발견되지 않으면 프로덕션에만 표시되는 추가 버그가 없을 가능성이 높습니다. 테스트에서 하나 또는 두 개의 버그가 발견되면 여전히 운이 좋을 수 있습니다. 그 외에도 스레드 처리 코드를주의 깊게 검토하거나 완전히 다시 작성하는 것이 좋습니다. 코드에는 여전히 코드가 생산 될 때까지 찾기가 매우 어려운 숨겨진 버그가 포함되어 있기 때문입니다. 그때 고치기 어렵다.
주위에 몇 가지 도구가 있습니다. 다음은 일부 Java에 대한 요약입니다.
좋은 정적 분석 도구로는 FindBugs (유용한 힌트 제공), JLint , Java Pathfinder (JPF & JPF2) 및 Bogor가 있습니다.
MultithreadedTC 는 자체 테스트 사례를 설정해야하는 훌륭한 동적 분석 도구 (JUnit에 통합)입니다.
IBM Research의 ConTest 는 흥미 롭습니다. 모든 종류의 스레드 수정 동작 (예 : 절전 및 수율)을 삽입하여 버그를 무작위로 찾아내어 코드를 계측합니다.
SPIN 은 Java (및 기타) 구성 요소를 모델링하는 데 유용한 도구이지만 유용한 프레임 워크가 필요합니다. 그대로 사용하기는 어렵지만 사용 방법을 알고 있으면 매우 강력합니다. 꽤 많은 도구가 후드 아래에 SPIN을 사용합니다.
MultithreadedTC가 아마도 가장 주류이지만, 위에 나열된 정적 분석 도구 중 일부는 확실히 볼 가치가 있습니다.
결정 성은 단위 결정 테스트를 작성하는 데 도움이 될 수 있습니다. 시스템 어딘가의 상태가 업데이트 될 때까지 기다릴 수 있습니다. 예를 들면 다음과 같습니다.
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
스레드 코드 및 일반적으로 매우 복잡한 시스템을 테스트 (kinda)하는 또 다른 방법은 Fuzz Testing을 사용하는 것 입니다. 대단하지 않으며 모든 것을 찾을 수는 없지만 유용하고 간단합니다.
인용문:
퍼지 테스트 또는 퍼징은 프로그램의 입력에 임의의 데이터 ( "퍼지")를 제공하는 소프트웨어 테스트 기술입니다. 프로그램이 실패하거나 (예 : 충돌 또는 내장 코드 어설 션 실패) 결함을 확인할 수 있습니다. 퍼즈 테스트의 가장 큰 장점은 테스트 디자인이 매우 간단하고 시스템 동작에 대한 선입견이 없다는 것입니다.
...
퍼즈 테스트는 종종 블랙 박스 테스트를 사용하는 대규모 소프트웨어 개발 프로젝트에서 사용됩니다. 이러한 프로젝트에는 일반적으로 테스트 도구를 개발하기위한 예산이 있으며 퍼지 테스트는 비용 대비 높은 이익을 제공하는 기술 중 하나입니다.
...
그러나 퍼즈 테스트는 철저한 테스트 나 공식적인 방법을 대신 할 수는 없습니다. 시스템 동작의 무작위 샘플 만 제공 할 수 있으며, 퍼즈 테스트를 통과하면 많은 소프트웨어가 충돌없이 예외를 처리하는 것만 입증 할 수 있습니다. 올바르게 동작합니다. 따라서 퍼지 테스트는 품질을 보증하기보다는 버그 찾기 도구로만 간주 될 수 있습니다.
나는 이것을 많이했고, 그렇습니다.
몇 가지 팁 :
throwable
필드를 작성 하고 체크인 tearDown
하십시오 (목록 1 참조). 다른 스레드에서 잘못된 예외를 발견하면 예외를 throwable에 할당하십시오.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());
}
이미 언급했듯이 MT 코드의 정확성을 테스트하는 것은 매우 어려운 문제입니다. 결국 코드에 동기화되지 않은 데이터 레이스가 없도록 보장합니다. 이것의 문제점은 많은 제어 권한이없는 스레드 실행 (인터리빙) 가능성이 무한히 많다는 것입니다 ( 이 기사 를 읽으십시오 ). 간단한 시나리오에서는 추론을 통해 실제로 정확성을 입증 할 수 있지만 일반적으로 그렇지 않습니다. 특히 동기화를 피하거나 최소화하고 가장 분명하고 쉬운 동기화 옵션을 사용하지 않으려는 경우에 특히 그렇습니다.
내가 따르는 접근법은 잠재적으로 감지되지 않은 데이터 경쟁이 발생할 가능성을 높이기 위해 동시 테스트 코드를 작성하는 것입니다. 그런 다음 한 번 그 테스트를 실행했습니다.) 컴퓨터 과학자가 이런 종류의 도구를 과시하는 이야기 (사양에서 무작위로 테스트를 고안 한 다음 정의 된 불변성을 검사하는 동시에 야생으로 실행하는 이야기)를 우연히 발견했습니다. 파손될 수 있습니다).
그건 그렇고, MT 코드 테스트 의이 측면은 여기에서 언급되지 않았다고 생각합니다 : 무작위로 확인할 수있는 코드의 변형을 식별하십시오. 불행하게도, 이러한 불변량을 찾는 것도 상당히 어려운 문제입니다. 또한 실행하는 동안 항상 유지되지 않을 수도 있으므로 실행 지점이 사실이라고 예상 할 수있는 실행 지점을 찾아서 적용해야합니다. 코드 실행을 그러한 상태로 만드는 것도 어려운 문제입니다 (그리고 동시성 문제가 발생할 수도 있습니다. 휴, 어렵습니다!
읽을 흥미로운 링크 :
병렬 스레드에서 실행할 두 개 이상의 테스트 메소드를 작성하고 각각 테스트 할 오브젝트를 호출합니다. 다른 스레드의 호출 순서를 조정하기 위해 Sleep () 호출을 사용했지만 실제로 신뢰할 수는 없습니다. 타이밍이 정상적으로 작동하기에 충분히 오래 자야하기 때문에 속도가 훨씬 느립니다.
FindBugs를 작성한 동일한 그룹에서 Multithreaded TC Java 라이브러리 를 찾았습니다 . Sleep ()을 사용하지 않고 이벤트 순서를 지정할 수 있으며 안정적입니다. 아직 시도하지 않았습니다.
이 방법의 가장 큰 한계는 문제가 발생할 것으로 의심되는 시나리오 만 테스트 할 수 있다는 것입니다. 다른 사람들이 말했듯이 멀티 스레드 코드를 소수의 간단한 클래스로 분리하여 철저히 테스트 할 수 있기를 바랍니다.
문제가 발생할 것으로 예상되는 시나리오를주의 깊게 테스트 한 후에는 클래스에서 동시에 여러 개의 동시 요청을 발생시키는 비과학적인 테스트는 예기치 않은 문제를 찾는 좋은 방법입니다.
업데이트 : Multithreaded TC Java 라이브러리로 조금 연주했는데 잘 작동합니다. 또한 일부 기능을 TickingTest 라는 .NET 버전으로 이식 했습니다 .
제어 및 격리 프레임 워크가 반전되는 모든 단위 테스트를 처리하는 것과 동일한 방식으로 스레드 구성 요소의 단위 테스트를 처리합니다. 나는 .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 등의 것들에 대해서도 동일한 접근 방식을 사용합니다.
관련 답변을 살펴보십시오.
Java에 편향되어 있지만 옵션에 대한 합리적인 요약이 있습니다.
요약하자면 (IMO) 정확성을 보장하는 멋진 프레임 워크를 사용하는 것이 아니라 멀티 스레드 코드를 디자인하는 방법을 설명합니다. 관심사 (동시성 및 기능성)를 나누는 것은 자신감을 높이는 데 큰 도움이됩니다. 테스트에 따라 성장하는 객체 지향 소프트웨어 는 내가 할 수있는 것보다 몇 가지 옵션을 더 잘 설명합니다.
정적 분석 및 공식 방법 ( 동시성 : 상태 모델 및 Java 프로그램 참조 )은 옵션이지만 상용 개발에 제한적으로 사용됩니다.
로드 / 담금 스타일 테스트가 문제를 강조하는 경우는 거의 없습니다.
행운을 빕니다!
tempus-fugit
, 여기 라이브러리를하는 helps write and test concurrent code
)
방금 최근 Threadsafe라는 도구를 발견했습니다. 그것은 findbugs와 매우 유사하지만 특히 멀티 스레딩 문제를 발견하기위한 정적 분석 도구입니다. 테스트를 대체하지는 않지만 안정적인 멀티 스레드 Java 작성의 일부로 권장 할 수 있습니다.
또한 클래스 하위 가정, 동시 클래스를 통해 안전하지 않은 개체에 액세스하고 이중 검사 잠금 패러다임을 사용할 때 누락 된 휘발성 수정자를 발견하는 등의 문제에 대해 매우 미묘한 잠재적 문제를 포착합니다.
멀티 스레드 Java를 작성 하면 샷을 제공하십시오.
다음 기사에서는 두 가지 솔루션을 제안합니다. 세마포어 (CountDownLatch)를 래핑하고 내부 스레드에서 데이터를 외부화하는 것과 같은 기능을 추가합니다. 이 목적을 달성하는 또 다른 방법은 스레드 풀을 사용하는 것입니다 (관심 지점 참조).
나는 지난주 대부분의 시간을 대학 도서관에서 동시 코드 디버깅을 연구하는 데 보냈다. 가장 큰 문제는 동시 코드가 결정적이지 않다는 것입니다. 일반적으로 아카데믹 디버깅은 다음 세 가지 캠프 중 하나에 속합니다.
위의 주석가들이 알 수 있듯이 동시 시스템을보다 결정적인 상태로 설계 할 수 있습니다. 그러나 제대로하지 않으면 순차 시스템을 다시 설계하는 것입니다.
내 제안은 스레드되는 것과 스레드되지 않는 것에 대해 매우 엄격한 디자인 프로토콜을 갖는 데 집중하는 것입니다. 요소 간 종속성이 최소화되도록 인터페이스를 제한하면 훨씬 쉽습니다.
행운을 빌어 계속 문제를 해결하십시오.
"멀티 스레드"코드 하에서
다른 말로, 우리는 오늘날 상태가 매우 드 물어야 할 커스텀 스테이트 풀 스레드 안전 클래스 / 메소드 / 유닛 테스트에 대해 이야기하고 있습니다.
이 짐승은 드물기 때문에, 우선 우리는 그것을 쓸 유효한 변명이 있는지 확인해야합니다.
1 단계. 동일한 동기화 컨텍스트에서 상태 수정을 고려하십시오.
오늘날 IO 또는 기타 느린 작업이 백그라운드로 오프로드되지만 공유 상태가 하나의 동기화 컨텍스트에서 업데이트 및 쿼리되는 작성 가능한 동시 및 비동기 코드를 작성하는 것이 쉽습니다. 예를 들어 async / await 작업 및 .NET의 Rx 등-모두 설계 상 테스트가 가능합니다. "실제"작업과 스케줄러를 대체하여 테스트를 결정적으로 수행 할 수 있습니다 (단, 문제의 범위를 벗어남).
매우 제한적으로 들릴지 모르지만이 방법은 놀랍게 잘 작동합니다. 스레드 안전 상태를 만들 필요 없이이 스타일로 전체 앱을 작성할 수 있습니다.
2 단계. 단일 동기화 컨텍스트에서 공유 상태를 조작 할 수없는 경우 절대 불가능합니다.
바퀴가 재발 명되지 않도록하십시오 / 작업에 적용 할 수있는 표준 대안이 없습니다. 코드는 매우 응집력이 높고 한 단위 내에 포함되어있을 가능성이 높습니다. 예를 들어 해시 맵 또는 컬렉션과 같은 표준 스레드 안전 데이터 구조의 특별한 경우입니다.
참고 : 코드가 크거나 여러 클래스에 걸쳐 있고 멀티 스레드 상태 조작이 필요한 경우 디자인이 좋지 않을 가능성이 매우 높습니다 .1 단계를 다시 고려하십시오.
3 단계. 이 단계에 도달하면 자체 사용자 정의 상태 저장 스레드 안전 클래스 / 방법 / 단위 를 테스트해야합니다 .
나는 정직하게 죽을 것이다. 나는 그런 코드에 대해 적절한 테스트를 작성할 필요가 없었다. 대부분의 경우 1 단계에서, 때로는 2 단계에서 나옵니다. 지난 몇 년 전에 사용자 정의 스레드 안전 코드를 작성해야했을 때 단위 테스트를 채택하기 전에 / 아마도 작성할 필요가 없었습니다. 어쨌든 현재의 지식으로.
그런 코드를 실제로 테스트해야한다면 ( 마지막으로 실제 답변 ) 아래 몇 가지를 시도해보십시오.
비 결정적 스트레스 테스트. 예를 들어 100 개의 스레드를 동시에 실행하고 최종 결과가 일치하는지 확인하십시오. 이는 여러 사용자 시나리오의 고급 / 통합 테스트에보다 일반적이지만 단위 수준에서도 사용할 수 있습니다.
하나의 스레드가 다른 스레드보다 먼저 작업을 수행해야하는 결정적인 시나리오를 만드는 데 도움이되도록 테스트에 일부 코드를 삽입 할 수있는 일부 테스트 '후크'를 노출하십시오. 추악한 것처럼 나는 더 나은 것을 생각할 수 없다.
스레드를 실행하고 특정 순서대로 작업을 수행하기위한 지연 기반 테스트 엄밀히 말하면 그러한 테스트는 결정적이지 않습니다 (시스템 정지 / 세계 GC 수집 가능성으로 인해 조정 지연을 왜곡 할 수 있음). 또한 추악하지만 후크를 피할 수 있습니다.
J2E 코드의 경우 스레드의 동시성 테스트에 SilkPerformer, LoadRunner 및 JMeter를 사용했습니다. 그들은 모두 같은 일을합니다. 기본적으로 TCP / IP 데이터 스트림을 분석하고 앱 서버에 동시에 요청하는 여러 사용자를 시뮬레이션하기 위해 필요한 프록시 서버 버전을 관리하기위한 비교적 간단한 인터페이스를 제공합니다. 프록시 서버는 요청을 처리 한 후 서버로 전송 된 전체 페이지 및 URL과 서버의 응답을 제공하여 요청을 분석하는 등의 작업을 수행 할 수 있습니다.
안전하지 않은 http 모드에서 버그를 발견 할 수 있습니다. 여기서 적어도 전송되는 양식 데이터를 분석하고 각 사용자에 대해 체계적으로 변경할 수 있습니다. 그러나 실제 테스트는 https (Secureed Socket Layers)에서 실행할 때입니다. 그런 다음, 세션 및 쿠키 데이터를 체계적으로 변경하는 것과 경쟁해야하는데, 이는 조금 더 복잡 할 수 있습니다.
동시성을 테스트하는 동안 발견 한 가장 좋은 버그는 개발자가 로그인시 LDAP 서버에 연결 요청을 닫기 위해 Java 가비지 수집에 의존했음을 발견했을 때 발생했습니다. 이로 인해 사용자가 노출되었습니다. 서버가 무릎을 꿇었을 때 무슨 일이 있었는지 분석하려고 할 때 다른 사용자의 세션과 매우 혼란스러운 결과로 몇 초마다 하나의 트랜잭션을 거의 완료 할 수 없습니다.
결국, 당신이나 누군가가 아마 방금 언급 한 것과 같은 실수에 대한 코드를 버클 링하고 분석해야 할 것입니다. 그리고 위에서 설명한 문제를 풀었을 때 발생한 부서와 같은 부서 간 공개 토론이 가장 유용합니다. 그러나 이러한 도구는 멀티 스레드 코드를 테스트하기위한 최상의 솔루션입니다. JMeter는 오픈 소스입니다. SilkPerformer 및 LoadRunner는 독점적입니다. 앱이 스레드로부터 안전한지 정말로 알고 싶다면 큰 소년이 그렇게합니다. 나는 대기업을 위해 전문적 으로이 작업을 수행 했으므로 추측하지 않습니다. 나는 개인적인 경험에서 말하고 있습니다.
주의 사항 : 이러한 도구를 이해하는 데 시간이 걸립니다. 이미 멀티 스레드 프로그래밍에 노출되지 않은 경우 소프트웨어를 설치하고 GUI를 실행하는 것은 문제가되지 않습니다. 이해해야 할 3 가지 주요 범주 (양식, 세션 및 쿠키 데이터)를 식별하려고 노력했습니다. 최소한 이러한 주제를 이해하는 것으로 시작하면 빠른 결과에 집중하는 데 도움이되기를 바랍니다. 전체 문서.
동시성은 메모리 모델, 하드웨어, 캐시 및 코드 간의 복잡한 상호 작용입니다. Java의 경우 이러한 테스트는 주로 jcstress에 의해 부분적으로 해결되었습니다 . 해당 라이브러리의 작성자는 많은 JVM, GC 및 Java 동시성 기능의 작성자로 알려져 있습니다.
그러나이 라이브러리조차도 우리가 테스트하고있는 것을 정확히 알 수 있도록 Java 메모리 모델 사양에 대한 지식이 필요합니다. 그러나이 노력의 초점은 mircobenchmarks라고 생각합니다. 거대한 비즈니스 응용 프로그램이 아닙니다.
예제 코드에서 Rust를 언어로 사용하는 주제에 대한 기사가 있습니다.
https://medium.com/@polyglot_factotum/rust-concurrency-five-easy-pieces-871f1c62906a
요컨대, 동시 로직을 작성하여 채널 및 condvars와 같은 도구를 사용하여 여러 실행 스레드와 관련된 비결정론에 강력합니다.
그런 다음 이것이 "구성 요소"를 구성한 방법 인 경우이를 테스트하는 가장 쉬운 방법은 채널을 사용하여 메시지를 보낸 다음 다른 채널을 차단하여 구성 요소가 특정 메시지를 보내도록하는 것입니다.
링크 된 기사는 단위 테스트를 사용하여 완전히 작성되었습니다.
간단한 새 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
}