如何对等待异步消息响应的方法进行单元测试

时间:2017-01-13 21:44:52

标签: wcf unit-testing asynchronous

我有一个WCF服务,它通过异步消息传递协议(MQTT)向远程设备发送消息,然后它必须等待来自设备的响应才能模拟同步操作。

我这样做是通过创建一个TaskCompletionSource(和一个CancellationTokenSource来处理超时),将它存储在ConcurrentDictionary中,然后在设置后返回TCS.Task.Result。同时,当响应进来时,另一种方法通过在字典中查找TCS并相应地设置其结果来处理响应。

这一切似乎都在实践中起作用,但在尝试对此方法进行单元测试时遇到了问题。我正在尝试设置一个异步任务,等待SendMessage方法生成TCS并将其添加到字典中,然后通过将其拉出字典并在超时之前设置结果来模拟响应。

出于单元测试的目的,我使用500毫秒的超时时间。我试过这个:

Task.Run(() =>
{
    Thread.Sleep(450);
    ctsDictionary.Values.Single().SetResult(theResponse);
});
MessageResponse response = service.SendMessage(...);

我也试过这个:

MessageResponse response = null;
Parallel.Invoke(
    async () =>
    {
        await Task.Delay(250);
        ctsDictionary.Values.Single().SetResult(theResponse);
    },
    () =>
    {
        response = service.SendMessage(...)
    }
);

这两种策略在运行这一单元测试时甚至在此单元测试类中运行所有测试时都能正常工作。

运行解决方案的所有单元测试时出现问题(在几十个UnitTest项目中总共进行了2307次测试)。在“运行所有测试”操作的一部分时,在异步任务设置响应之前,SendMessage方法超时时,此测试始终失败。据推测,这是因为并行运行的所有其他单元测试都会抛弃任务的调度,并且时间不会结束。我已经尝试过延迟执行任务以及大大增加超时时间,但是当所有测试都运行时,我仍然无法让它始终通过。

那我怎么解决这个问题呢?有什么方法可以确保SendMessage调用和设置响应的任务安排在同一时间运行?或者是否有其他策略可以用来确保时间安排?

1 个答案:

答案 0 :(得分:1)

  

然后它必须等待来自设备的响应才能模拟同步操作。

那个小伙子,伙计。只需说出来 - 保持async。它不仅更自然,更容易进行单元测试!

您可以通过先排队SendMessage,然后快速查询点击字典的请求来最小化SendMessage等待的时间。在没有更改SendMessage的情况下(例如,将其设为async),您可以尽可能地获得它:

// Start SendMessage
var sendTask = Task.Run(() => service.SendMessage(...));

// SendMessage may not actually be running yet, so we busy-wait for it.
while (ctsDictionary.Values.Count == 0) ;

// Let SendMessage know it can return.
ctsDictionary.Values.Single().SetResult(theResponse);

// Retrieve the result.
var result = await sendTask;

如果您在超时前仍然遇到问题,那么您只需要限制单元测试(例如SemaphoreSlim)。

同样,如果SendMessageAsync存在于同步在字典等待之前添加到字典中的语义,这将更容易:

// Start SendMessage
var sendTask = service.SendMessageAsync(...);

// Let SendMessage know it can return.
ctsDictionary.Values.Single().SetResult(theResponse);

// Retrieve the result.
var result = await sendTask;

没有忙碌等待,没有延迟,没有额外的线程。更干净。