我正在研究多线程应用程序,并且有一部分代码只能由同一个线程同时运行。没什么复杂的。我用锁来同步它。它在生命系统中工作,但我想编写单元测试来检查是否只有一个线程在临界区。我写了一个,它工作但它停止了:) 我无法弄清楚如何以适当的方式编写测试。我使用NSubstitute来创建模拟。
要测试的课程:
public interface IMultiThreadClass
{
void Go();
}
public class Lock02 : IMultiThreadClass
{
private readonly IProcessor _processor;
private readonly string _threadName;
private static readonly object Locker = new Object();
public Lock02(IProcessor processor, string threadName)
{
_processor = processor;
_threadName = threadName;
}
public void Go()
{
//critical section
lock (Locker)
{
_processor.Process(_threadName);
}
}
}
测试:
[TestMethod()]
public void Run_Test()
{
//Only one thread should run Processor.Process, but we allow max 2 threads to catch locking erorrs
SemaphoreSlim semaphore = new SemaphoreSlim(1, 2);
//Semaphore to synchronize asserts
SemaphoreSlim synchroSemaphore = new SemaphoreSlim(0, 1);
IProcessor procesor = Substitute.For<IProcessor>();
procesor.When(x => x.Process(Arg.Any<string>())).Do(y =>
{
//increment counter to check if method was called
Interlocked.Increment(ref _counter);
//release synchro semaphore
synchroSemaphore.Release();
//stop thread and wait for release
semaphore.Wait();
});
Lock02 locker1 = new Lock02(procesor, "1");
Lock02 locker2 = new Lock02(procesor, "2");
Lock02 locker3 = new Lock02(procesor, "3");
Task.Run(() => locker1.Go());
Task.Run(() => locker2.Go());
Task.Run(() => locker3.Go());
//ASSERT
//Thread.Sleep(1000);
synchroSemaphore.Wait();
Assert.AreEqual(1, _counter);
semaphore.Release(1);
synchroSemaphore.Wait();
Assert.AreEqual(2, _counter);
semaphore.Release(1);
synchroSemaphore.Wait();
Assert.AreEqual(3, _counter);
semaphore.Release(1);
}
答案 0 :(得分:1)
一种可能的(简单但不是防弹的)方法是在单元测试中生成一些线程/任务,每次获取并临时存储一个int变量(可能是静态的),等待一点(延迟),递增值并写入它回到变量。如果没有线程同步(锁定),许多(如果不是所有)线程将获取相同的数字,并且它将与线程/任务的数量不相等(
)。这是不是防弹,因为仍有竞争条件使其无法再现(有臭味的代码是50毫秒的延迟),虽然它似乎(对我而言)所有踏板都不太可能以完美的方式相互等待并产生正确的结果。
我认为这是一个有臭味的解决方法,但它很简单且有效。
[TestMethod]
public async Task APossibleTest()
{
int importantNumber = 0;
var proc = Substitute.For<IProcessor>();
proc.WhenForAnyArgs(processor => processor.Process(Arg.Any<string>()))
.Do(callInfo =>
{
int cached = importantNumber;
// Wait for other threads to fetch the number too (if they were not synchronized).
Thread.Sleep(TimeSpan.FromMilliseconds(50));
// This kind of incrementation will check the thread synchronization.
// Using a thread-safe Interlocked or other here does not make sense.
importantNumber = cached + 1;
});
var locker = new Locker(proc, "da horror");
// Create 10 tasks all attempting to increment the important number.
Task[] tasks =
Enumerable
.Range(0, 10)
// You could create multiple lockers here (with their own processors).
.Select(i => Task.Run(() => locker.Go()))
.ToArray();
await Task.WhenAll(tasks);
Assert.AreEqual(10, importantNumber, "Exactly 10 increments were expected since we have 10 tasks.");
}