使用 MSTest 测试异步 WCF 服务

Testing an asynchronous WCF service using MSTest

我在 C# 中使用 async\await 方法实现了 WCF 服务。服务运营合约如下:

[OperationContract]
Task<ResponseMessage<ProcessMultipleTransactionsResponse>> ProcessMultipleTransactionsAsync(RequestMessage<ProcessMultipleTransactionsRequest> msgIn);

实现的方法签名是:

public async Task<ResponseMessage<ProcessMultipleTransactionsResponse>> ProcessMultipleTransactionsAsync(RequestMessage<ProcessMultipleTransactionsRequest> msgIn)

我正在尝试使用 VS 2012 中的单元测试来测试此服务。构建代理后,我使用该方法使用:

var actual = await target.ProcessMultipleTransactionsAsync(request);

测试方法有 async 关键字,任务类型为 return。执行测试时出现以下错误:

测试方法Services.Implementations.UnitTests.CoreBanking.CoreBankingServiceTest.ProcessMultipleTransactions_JMD_JMD_ACHTransactions_Test抛出异常: System.ArgumentException:向 'End' 方法提供了不正确的 IAsyncResult。传递给 'End' 的 IAsyncResult 对象必须是来自匹配 'Begin' 的 return 或传递给提供给 'Begin'. 的回调的对象 参数名称:result

结果 StackTrace:

at System.Runtime.AsyncResult.End[TAsyncResult](IAsyncResult result)
at System.ServiceModel.Channels.ServiceChannel.SendAsyncResult.End(SendAsyncResult result)
at System.ServiceModel.Channels.ServiceChannel.EndCall(String action, Object[] outs, IAsyncResult result)
at System.ServiceModel.Channels.ServiceChannelProxy.TaskCreator.<>c__DisplayClass2`1.<CreateGenericTask>b__3(IAsyncResult asyncResult)
at System.Threading.Tasks.TaskFactory`1.FromAsyncCoreLogic(IAsyncResult iar, Func`2 endFunction, Action`1 endAction, Task`1 promise, Boolean requiresSynchronization)
--- End of stack trace from previous location where exception was thrown ---
at System.Runtime.CompilerServices.TaskAwaiter.ThrowForNonSuccess(Task task)
at System.Runtime.CompilerServices.TaskAwaiter.HandleNonSuccessAndDebuggerNotification(Task task)
at System.Runtime.CompilerServices.TaskAwaiter`1.GetResult()
at Services.Implementations.UnitTests.CoreBanking.CoreBankingServiceTest.<ProcessMultipleTransactions_JMD_JMD_ACHTransactions_Test>d__10.MoveNext() in c:\...\CoreBankingServiceTest.cs:line 5716
--- End of stack trace from previous location where exception was thrown ---
at System.Runtime.CompilerServices.TaskAwaiter.ThrowForNonSuccess(Task task)
at System.Runtime.CompilerServices.TaskAwaiter.HandleNonSuccessAndDebuggerNotification(Task task)
at System.Runtime.CompilerServices.TaskAwaiter.GetResult()

知道是什么原因造成的吗?

如果您正在尝试测试服务,我认为您应该避免完全测试代理代码和 WCF 访问 - 它们给测试增加了不必要的复杂性,这意味着您正在测试比你预期的要多得多。只需在您的测试方法或测试初始化​​方法中构建您的服务,并避免 WCF 与绑定等的所有复杂性。

如果出于某种原因,您想在此测试中通过 WCF 调用您的服务 - 更多的是集成测试,那么您将不得不使用适当的绑定自行托管它。一旦测试初始化​​创建了准备好测试调用它的服务,我的第一个建议是仍然放弃代理并使用 System.ServiceModel.ChannelFactory<> 代替,因此您正在针对服务正在实现的完全相同的接口构建客户端代码(您不必记住在服务合同发生更改时重新生成代理)。它是这样的(这段代码对端点使用相同的 app.config 配置):

var factory = new ChannelFactory<IService>("NameOfIServiceEndpointDeclaredInConfig");    
var target = factory.CreateChannel();
//Now allowing you to do the following, without the complication of the old-style proxy code
var actual = await target.ProcessMultipleTransactionsAsync(request);

使用此代码,目标将被键入为 IService,而不是某些 ServiceProxy class。我希望删除关于 Begin 和 End 以及 IAsyncResult 的所有垃圾 - 这些东西都是 .NET 1.0 中首次引入的旧异步编程模型的一部分。

您已经在您的操作合同中使用 .NET 4 的 TAP - 但是我不知道任务可以安全地通过哪种协议 - 我非常怀疑您是否可以通过 WSHttpBinding 来传递它。因此,您需要考虑您的服务以及如何称呼它。也许对于单元测试,命名管道可能会起作用(我不知道,也许其他人可以发表评论)。但什么是正常情况?通过网络?在那种情况下,放弃任务 return 并使其成为同步方法可能更好,但随后生成具有异步方法的代理,然后使用 FromAsync 扩展方法将该 APM 约定转换为点击一个您可以在测试中等待的。或者更好,因为我只是建议放弃代理,异步调用同步方法,例如:

var actual = await Task.Run(() => target.ProcessMultipleTransactions(request));