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

Wcf 如何对等待异步消息响应的方法进行单元测试,wcf,unit-testing,asynchronous,Wcf,Unit Testing,Asynchronous,我有一个WCF服务,它通过异步消息传递协议MQTT向远程设备发送消息,然后它必须等待设备的响应以模拟同步操作 为此,我创建了一个TaskCompletionSource和一个CancellationTokenSource来处理超时,将其存储在ConcurrentDictionary中,然后在设置完成后返回TCS.Task.Result。同时,当响应传入时,另一种方法通过在字典中查找TCS并相应地设置其结果来处理响应 这一切在实践中似乎都是可行的,但我在尝试对这个方法进行单元测试时遇到了一些问题。

我有一个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(...)
    }
);
我也试过:

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(...)
    }
);
当只运行这一个单元测试或者甚至运行这个单元测试类中的所有测试时,这两种策略都可以很好地工作

当在几十个单元测试项目中运行解决方案2307测试的所有单元测试时,问题就出现了。当运行所有测试操作的一部分时,此测试始终失败,SendMessage方法在异步任务设置响应之前超时。这大概是因为任务的调度被并行运行的所有其他单元测试所抛出,并且最终没有实现时间安排。我尝试过处理任务的延迟,并大大增加超时时间,但在运行所有测试时,我仍然无法使其一致通过

那么我该如何解决这个问题呢?是否有某种方法可以确保SendMessage调用和设置响应的任务计划在完全相同的时间运行?或者,我是否可以使用其他策略来确保时间安排顺利

然后,它必须等待设备的响应,以模拟同步操作

那是霍基,伙计。只需要说一句——保持异步。这样不仅更自然,而且单元测试也更容易

您可以通过先将SendMessage排队,然后快速轮询请求以命中字典,来最小化SendMessage等待的时间。在不更改SendMessage(例如,使其异步)的情况下,这是您可以获得的最紧密的消息:

如果在超时之前仍然无法进入,则只需限制单元测试,例如,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;

没有繁忙的等待,没有延迟,没有额外的线程。干净多了。

你说得不错。我将SendMessage重写为一个异步方法,并更新了调用它的类,使其只等待结果。同样的基本逻辑。只是将等待移到了需要进行单元测试的方法之外。