首页 文章

不稳定的单元测试结果 - 模拟消息队列行为和并行循环

提问于
浏览
0

我正在构建一个类来使用并行循环来访问消息队列中的消息,为了解释我的问题,我创建了一个简化版本的代码:

public class Worker
{
    private IMessageQueue mq;
    public Worker(IMessageQueue mq)
    {
        this.mq = mq;
    }

    public int Concurrency
    {
        get
        {
            return 5;
        }
    }

    public void DoWork()
    {
        int totalFoundMessage = 0;

        do
        {
            // reset for every loop
            totalFoundMessage = 0;

            Parallel.For<int>(
                0,
                this.Concurrency,
                () => 0,
                (i, loopState, localState) =>
                {
                    Message data = this.mq.GetFromMessageQueue("MessageQueueName");

                    if (data != null)
                    {
                        return localState + 1;
                    }
                    else
                    {
                        return localState + 0;
                    }
                },
                localState =>
                {
                    Interlocked.Add(ref totalFoundMessage, localState);
                });
        }
        while (totalFoundMessage >= this.Concurrency);
    }
}

我们的想法是将worker类设置为并发值以控制并行循环 . 如果在每个循环之后要从消息队列中检索的消息数等于并发数,我假设队列中可能存在更多消息并继续从队列中获取,直到消息号小于并发 . TPL代码也受到TPL Data Parallelism Issue帖子的启发 .

我有消息队列和消息对象的接口 .

public interface IMessageQueue
{
    Message GetFromMessageQueue(string queueName);
}

public class Message
{
}

因此我创建了单元测试代码,并使用Moq来模拟 IMessageQueue 接口

[TestMethod()]
    public void DoWorkTest()
    {
        Mock<IMessageQueue> mqMock = new Mock<IMessageQueue>();

        Message data = new Message();

        Worker w = new Worker(mqMock.Object);

        int callCounter = 0;
        int messageNumber = 11;
        mqMock.Setup(x => x.GetFromMessageQueue("MessageQueueName")).Returns(() =>
        {
            callCounter++;
            if (callCounter < messageNumber)
            {
                return data;
            }
            else
            {
                // simulate MSMQ's behavior last call to empty queue returns null
                return (Message)null;
            }
        }
        );

        w.DoWork();

        int expectedCallTimes = w.Concurrency * (messageNumber / w.Concurrency);
        if (messageNumber % w.Concurrency > 0)
        {
            expectedCallTimes += w.Concurrency;
        }

        mqMock.Verify(x => x.GetFromMessageQueue("MessageQueueName"), Times.Exactly(expectedCallTimes));
    }

我使用了Moq to set up a function return based on called times中的想法来设置基于响应的呼叫时间 .

在单元测试期间,我注意到测试结果是不稳定的,如果你多次运行它会在大多数情况下看到测试通过,但偶尔测试因各种原因而失败 .

我不知道是什么导致了这种情况,并寻求你的一些意见 . 谢谢

2 回答

  • 1

    问题是你的模拟 GetFromMessageQueue() 不是线程安全的,但你同时从多个线程调用它 . ++ 本质上是线程不安全的操作 .

    相反,你应该使用锁定或Interlocked.Increment() .

    此外,在您的代码中,您可能不会从并行性中受益,因为启动和停止 Parallel.ForEach() 会有一些开销 . 更好的方法是在 Parallel.ForEach() 内部设置 while (或 do - while ),而不是相反 .

  • 0

    我的方法是重组 . 在测试计时或并发等内容时,通常谨慎的做法是将您的调用(在本例中为PLINQ的使用)抽象为一个接受大量委托的单独类 . 然后,您可以测试正在对新类进行的正确调用 . 然后,因为新类更简单(只有一个PLINQ调用)并且不包含逻辑,所以可以不进行测试 .

    我主张不在这种情况下进行测试,因为除非你正在研究超级关键的东西(生命支持系统,飞机等),否则它会变得比测试它更值钱 . 信任框架将按预期执行PLINQ查询 . 您应该只测试那些有意义的测试,并为您的项目或客户提供 Value .

相关问题