线程同步的单元测试(锁定)

时间:2017-03-31 14:32:19

标签: c# multithreading unit-testing

我正在研究多线程应用程序,并且有一部分代码只能由同一个线程同时运行。没什么复杂的。我用锁来同步它。它在生命系统中工作,但我想编写单元测试来检查是否只有一个线程在临界区。我写了一个,它工作但它停止了:) 我无法弄清楚如何以适当的方式编写测试。我使用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);
}

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.");
    }