스레드 안전성을위한 단위 테스트?
클래스와 많은 단위 테스트를 작성했지만 스레드로부터 안전하게 만들지 못했습니다. 이제 클래스 스레드를 안전하게 만들고 싶지만이를 증명하고 TDD를 사용하기 위해 리팩토링을 시작하기 전에 실패한 단위 테스트를 작성하고 싶습니다.
이 작업을 수행하는 좋은 방법이 있습니까?
내 첫 번째 생각은 몇 개의 스레드를 만들고 모두 안전하지 않은 방식으로 클래스를 사용하도록 만드는 것입니다. 충분한 스레드로이 작업을 충분히 수행하면 끊어지는 것을 볼 수 있습니다.
도움이 될 수있는 두 가지 제품이 있습니다.
둘 다 (단위 테스트를 통해) 코드의 교착 상태를 확인하고 Chess도 경쟁 조건을 확인한다고 생각합니다.
두 도구를 모두 사용하는 것은 쉽습니다. 간단한 단위 테스트를 작성하고 코드를 여러 번 실행하고 코드에서 교착 상태 / 경쟁 조건이 가능한지 확인합니다.
편집 : Google은 thread-race-test 라는 테스트 중이 아닌 런타임에서 경쟁 조건을 확인하는 도구를 출시했습니다 .
위의 도구와 같은 모든 가능한 시나리오가 아닌 현재 실행 만 분석하기 때문에 모든 경쟁 조건을 찾지 못하지만 일단 발생하면 경쟁 조건을 찾는 데 도움이 될 수 있습니다.
업데이트 : Typemock 사이트에는 더 이상 Racer에 대한 링크가 없었으며 지난 4 년 동안 업데이트되지 않았습니다. 프로젝트가 종료 된 것 같습니다.
문제는 경쟁 조건과 같은 대부분의 멀티 스레딩 문제가 본질적으로 비 결정적이라는 것입니다. 에뮬레이트하거나 트리거 할 수없는 하드웨어 동작에 따라 달라질 수 있습니다.
즉, 여러 스레드로 테스트를 수행하더라도 코드에 결함이 있어도 지속적으로 실패하지 않습니다.
Dror의 대답은 이것을 명시 적으로 말하지는 않지만 적어도 Chess (그리고 아마도 Racer)는 가능한 모든 인터리빙을 통해 스레드 집합을 실행하여 재현 가능한 오류를 얻습니다. 오류가 발생하면 우연히 발생하기를 바라면서 잠시 스레드를 실행하지 않습니다.
예를 들어 Chess는 모든 인터리빙을 실행 한 다음 교착 상태가 발견 된 인터리빙을 나타내는 태그 문자열을 제공하므로 교착 상태 관점에서 흥미로운 특정 인터리빙으로 테스트를 어트 리뷰 션 할 수 있습니다.
이 도구의 정확한 내부 작동 방식을 모르고 교착 상태를 수정하기 위해 변경할 수있는 코드에 이러한 태그 문자열을 다시 매핑하는 방법을 모르지만 여기에 있습니다. 실제로이 도구를 기대하고 있습니다 ( 및 Pex) VS IDE의 일부가됩니다.
나는 사람들이 당신이 제안한대로 표준 단위 테스트로 이것을 테스트하는 것을 보았습니다. 테스트는 느리고 지금까지 우리 회사가 어려움을 겪고있는 동시성 문제 중 하나를 식별하지 못했습니다.
많은 실패 후, 그리고 단위 테스트에 대한 나의 사랑에도 불구하고 동시성의 오류가 단위 테스트의 강점 중 하나가 아니라는 사실을 받아들이게되었습니다. 나는 일반적으로 동시성이 주제 인 클래스에 대한 단위 테스트를 선호하는 분석 및 검토를 권장합니다. 시스템의 전체적인 개요를 통해 스레드 안전성에 대한 주장을 증명 / 위조 할 수있는 경우가 많습니다.
어쨌든 나는 누군가가 나에게 그 반대를 가리키는 무언가를 주었으면 좋겠다. 그래서 나는이 질문을 면밀히 본다.
최근에 같은 문제를 해결해야했을 때 이런 식으로 생각했습니다. 우선 기존 클래스에는 하나의 책임이 있으며 일부 기능을 제공하는 것입니다. 스레드로부터 안전한 것은 객체의 책임이 아닙니다. 스레드로부터 안전해야하는 경우이 기능을 제공하기 위해 다른 개체를 사용해야합니다. 그러나 다른 객체가 스레드 안전성을 제공하는 경우 코드가 스레드 안전성을 증명할 수 없기 때문에 선택 사항이 될 수 없습니다. 그래서 이것이 내가 그것을 처리하는 방법입니다.
// This interface is optional, but is probably a good idea.
public interface ImportantFacade
{
void ImportantMethodThatMustBeThreadSafe();
}
// This class provides the thread safe-ness (see usage below).
public class ImportantTransaction : IDisposable
{
public ImportantFacade Facade { get; private set; }
private readonly Lock _lock;
public ImportantTransaction(ImportantFacade facade, Lock aLock)
{
Facade = facade;
_lock = aLock;
_lock.Lock();
}
public void Dispose()
{
_lock.Unlock();
}
}
// I create a lock interface to be able to fake locks in my tests.
public interface Lock
{
void Lock();
void Unlock();
}
// This is the implementation I want in my production code for Lock.
public class LockWithMutex : Lock
{
private Mutex _mutex;
public LockWithMutex()
{
_mutex = new Mutex(false);
}
public void Lock()
{
_mutex.WaitOne();
}
public void Unlock()
{
_mutex.ReleaseMutex();
}
}
// This is the transaction provider. This one should replace all your
// instances of ImportantImplementation in your code today.
public class ImportantProvider<T> where T:Lock,new()
{
private ImportantFacade _facade;
private Lock _lock;
public ImportantProvider(ImportantFacade facade)
{
_facade = facade;
_lock = new T();
}
public ImportantTransaction CreateTransaction()
{
return new ImportantTransaction(_facade, _lock);
}
}
// This is your old class.
internal class ImportantImplementation : ImportantFacade
{
public void ImportantMethodThatMustBeThreadSafe()
{
// Do things
}
}
제네릭을 사용하면 테스트에서 가짜 잠금을 사용하여 트랜잭션이 생성 될 때 항상 잠금이 수행되고 트랜잭션이 폐기 될 때까지 해제되지 않는지 확인할 수 있습니다. 이제 중요한 메서드가 호출 될 때 잠금이 적용되었는지 확인할 수도 있습니다. 프로덕션 코드에서의 사용법은 다음과 같습니다.
// Make sure this is the only way to create ImportantImplementation.
// Consider making ImportantImplementation an internal class of the provider.
ImportantProvider<LockWithMutex> provider =
new ImportantProvider<LockWithMutex>(new ImportantImplementation());
// Create a transaction that will be disposed when no longer used.
using (ImportantTransaction transaction = provider.CreateTransaction())
{
// Access your object thread safe.
transaction.Facade.ImportantMethodThatMustBeThreadSafe();
}
ImportantImplementation이 다른 사람에 의해 생성 될 수 없도록 (예를 들어 공급자에서 생성하고 개인 클래스로 만드는 방식) 이제 트랜잭션 없이는 액세스 할 수없고 트랜잭션이 항상 생성 될 때 잠그고 폐기 될 때 해제합니다.
트랜잭션이 올바르게 처리되었는지 확인하는 것이 더 어려울 수 있으며 그렇지 않은 경우 애플리케이션에서 이상한 동작을 볼 수 있습니다. 도구를 Microsoft Chess (다른 답변에서 제안한대로)로 사용하여 이와 같은 것을 찾을 수 있습니다. 또는 공급자가 파사드를 구현하고 다음과 같이 구현하도록 할 수 있습니다.
public void ImportantMethodThatMustBeThreadSafe()
{
using (ImportantTransaction transaction = CreateTransaction())
{
transaction.Facade.ImportantMethodThatMustBeThreadSafe();
}
}
이것이 구현이지만 필요에 따라 이러한 클래스를 확인하는 테스트를 알아낼 수 있기를 바랍니다.
springframeworks 테스트 모듈 (또는 기타 확장)이있는 testNG 또는 Junit은 동시성 테스트에 대한 기본 지원을 제공합니다.
이 링크에 관심이있을 수 있습니다.
http://www.cs.rice.edu/~javaplt/papers/pppj2009.pdf
우려되는 각 동시성 시나리오에 대한 테스트 케이스를 구성해야합니다. 이를 위해서는 경합 가능성을 높이기 위해 효율적인 작업을 더 느린 등가물 (또는 모의)로 교체하고 루프에서 여러 테스트를 실행해야 할 수 있습니다.
특정 테스트 케이스가 없으면 특정 테스트를 제안하기 어렵습니다.
잠재적으로 유용한 참고 자료 :
Racer 또는 Chess와 같은 도구를 사용하는 것만 큼 우아하지는 않지만 스레드 안전성을 테스트하기 위해 이런 종류의 것을 사용했습니다.
// from linqpad
void Main()
{
var duration = TimeSpan.FromSeconds(5);
var td = new ThreadDangerous();
// no problems using single thread (run this for as long as you want)
foreach (var x in Until(duration))
td.DoSomething();
// thread dangerous - it won't take long at all for this to blow up
try
{
Parallel.ForEach(WhileTrue(), x =>
td.DoSomething());
throw new Exception("A ThreadDangerException should have been thrown");
}
catch(AggregateException aex)
{
// make sure that the exception thrown was related
// to thread danger
foreach (var ex in aex.Flatten().InnerExceptions)
{
if (!(ex is ThreadDangerException))
throw;
}
}
// no problems using multiple threads (run this for as long as you want)
var ts = new ThreadSafe();
Parallel.ForEach(Until(duration), x =>
ts.DoSomething());
}
class ThreadDangerous
{
private Guid test;
private readonly Guid ctrl;
public void DoSomething()
{
test = Guid.NewGuid();
test = ctrl;
if (test != ctrl)
throw new ThreadDangerException();
}
}
class ThreadSafe
{
private Guid test;
private readonly Guid ctrl;
private readonly object _lock = new Object();
public void DoSomething()
{
lock(_lock)
{
test = Guid.NewGuid();
test = ctrl;
if (test != ctrl)
throw new ThreadDangerException();
}
}
}
class ThreadDangerException : Exception
{
public ThreadDangerException() : base("Not thread safe") { }
}
IEnumerable<ulong> Until(TimeSpan duration)
{
var until = DateTime.Now.Add(duration);
ulong i = 0;
while (DateTime.Now < until)
{
yield return i++;
}
}
IEnumerable<ulong> WhileTrue()
{
ulong i = 0;
while (true)
{
yield return i++;
}
}
이론은 스레드 위험 상태가 매우 짧은 시간에 지속적으로 발생하도록 할 수 있다면 상태 손상을 관찰하지 않고 상대적으로 많은 시간 동안 대기하여 스레드 안전 상태를 발생시키고이를 검증 할 수 있어야한다는 것입니다.
나는 이것이 그것에 대해 진행하는 원시적 인 방법 일 수 있으며 복잡한 시나리오에서는 도움이되지 않을 수 있음을 인정합니다.
Here's my approach. This test is not concerned with deadlocks, it's concerned with consistency. I'm testing a method with a synchronized block, with code that looks something like this:
synchronized(this) {
int size = myList.size();
// do something that needs "size" to be correct,
// but which will change the size at the end.
...
}
It's tough to produce a scenario that will reliably produce a thread conflict, but here's what I did.
First, my unit test created 50 threads, launched them all at the same time, and had them all call my method. I use a CountDown Latch to start them all at the same time:
CountDownLatch latch = new CountDownLatch(1);
for (int i=0; i<50; ++i) {
Runnable runner = new Runnable() {
latch.await(); // actually, surround this with try/catch InterruptedException
testMethod();
}
new Thread(runner, "Test Thread " +ii).start(); // I always name my threads.
}
// all threads are now waiting on the latch.
latch.countDown(); // release the latch
// all threads are now running the test method at the same time.
This may or may not produce a conflict. My testMethod() should be capable of throwing an exception if a conflict occurs. But we can't yet be sure that this will generate a conflict. So we don't know if the test is valid. So here's the trick: Comment out your synchronized keyword(s) and run the test. If this produces a conflict, the test will fail. If it fails without the synchronized keyword, your test is valid.
That's what I did, and my test didn't fail, so it was not (yet) a valid test. But I was able to reliably produce a failure by putting the code above inside a loop, and running it 100 times consecutively. So I call the method 5000 times. (Yes, this will produce a slow test. Don't worry about it. Your customers won't be bothered by this, so you shouldn't either.)
Once I put this code inside an outer loop, I was able to reliably see a failure on about the 20th iteration of the outer loop. Now I was confident the test was valid, and I restored the synchronized keywords to run the actual test. (It worked.)
You may discover that the test is valid on one machine and not on another. If the test is valid on one machine and your methods pass the test, then it's presumably thread-safe on all machines. But you should test for validity on the machine that runs your nightly unit tests.
참고URL : https://stackoverflow.com/questions/1715822/unit-test-for-thread-safe-ness
'Development Tip' 카테고리의 다른 글
Objective-C 프로젝트에서 Swift Pod Framework를 가져오고 사용하는 방법 (0) | 2020.11.15 |
---|---|
C ++ 11에서 메모리를 정렬하는 권장 방법은 무엇입니까? (0) | 2020.11.15 |
C # (. NET)에서 SFTP 서버에 파일을 업로드하려면 어떻게해야합니까? (0) | 2020.11.15 |
가상 키보드가 활성화 된 경우 화면 스타일링 (0) | 2020.11.15 |
TypeScript 용 Eclipse 플러그인? (0) | 2020.11.15 |