我正在构建一个类来使用并行循环来访问消息队列中的消息,为了解释我的问题,我创建了一个简化版本的代码:
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 回答
问题是你的模拟
GetFromMessageQueue()
不是线程安全的,但你同时从多个线程调用它 .++
本质上是线程不安全的操作 .相反,你应该使用锁定或Interlocked.Increment() .
此外,在您的代码中,您可能不会从并行性中受益,因为启动和停止
Parallel.ForEach()
会有一些开销 . 更好的方法是在Parallel.ForEach()
内部设置while
(或do
-while
),而不是相反 .我的方法是重组 . 在测试计时或并发等内容时,通常谨慎的做法是将您的调用(在本例中为PLINQ的使用)抽象为一个接受大量委托的单独类 . 然后,您可以测试正在对新类进行的正确调用 . 然后,因为新类更简单(只有一个PLINQ调用)并且不包含逻辑,所以可以不进行测试 .
我主张不在这种情况下进行测试,因为除非你正在研究超级关键的东西(生命支持系统,飞机等),否则它会变得比测试它更值钱 . 信任框架将按预期执行PLINQ查询 . 您应该只测试那些有意义的测试,并为您的项目或客户提供 Value .