2016-11-26 52 views
0

我已经编写了一个位于WPF应用程序的UI和异步消息传递系统之间的C#类。我现在正在为这门课写单元测试,并遇到调度员遇到的问题。以下方法属于我正在测试的类,它创建一个订阅处理程序。所以我从单元测试 - 测试方法调用这个Set_Listing_Records_ResponseHandler方法。单元测试多线程WPF应用程序中使用的类

public async Task<bool> Set_Listing_Records_ResponseHandler(
    string responseChannelSuffix, 
    Action<List<AIDataSetListItem>> successHandler, 
    Action<Exception> errorHandler) 
{ 
    // Subscribe to Query Response Channel and Wire up Handler for Query Response 
    await this.ConnectAsync(); 
    return await this.SubscribeTo_QueryResponseChannelAsync(responseChannelSuffix, new FayeMessageHandler(delegate (FayeClient client, FayeMessage message) { 
     Application.Current.Dispatcher.BeginInvoke(new Action(() => 
     { 
      try 
      { 
       ... 
      } 
      catch (Exception e) 
      { 
       ... 
      } 
     })); 
    })); 
} 

执行流程返回到Application.Current.Dispatcher ....线,但随后引发错误:

Object reference not set to an instance of an object.

当我调试,我可以看到Application.Current为空。

我已经做了一些搜索,发现了在单元测试 - 测试方法中使用调度程序的一些例子,并且我尝试了其中的一些方法,它们可以防止错误,但调度程序中的代码永远不会运行。

我一直没有找到任何有测试方法调用的方法中使用Dispatcher的例子。

我正在使用Windows 10机器上的.NET 4.5.2。

任何援助将不胜感激。

谢谢你的时间。

+0

看看这里提供的答案,让我知道是否需要更多的解释http://stackoverflow.com/a/38994745/5233410 – Nkosi

+1

如果你想让你的课是单元测试,你应该遵循定期与所谓的依赖注入(DI)有关的良好实践。在这种情况下,您不应该使用WPF特定的类(Dispatcher),_especially_通过WPF特定的_static_类应用程序。相反,将Dispatcher视为依赖项并将其注入到您的类中,而不是Dispatcher本身,而是一些提供您所需方法的接口。你应该创建和实现你自己的接口,并在其中包装WPF分派器。然后在单元测试中,您只需提供另一个此IDispatcher的非WPF实现。 – Evk

回答

0

感谢所有回复的人,您的反馈非常感谢。

这里是我落得这样做:

我在UICommsManager类创建了一个私有变量:

private SynchronizationContext _MessageHandlerContext = null; 

,并在UICommsManager的构造函数初始化这一点。 然后我更新了我的消息处理程序使用新_MessageHandlerContext而不是接线员:

public async Task<bool> Set_Listing_Records_ResponseHandler(string responseChannelSuffix, Action<List<AIDataSetListItem>> successHandler, Action<Exception> errorHandler) 
{ 
    // Subscribe to Query Response Channel and Wire up Handler for Query Response 
    await this.ConnectAsync(); 
    return await this.SubscribeTo_QueryResponseChannelAsync(responseChannelSuffix, new FayeMessageHandler(delegate (FayeClient client, FayeMessage message) { 
     _MessageHandlerContext.Post(delegate { 
      try 
      { 
       ... 
      } 
      catch (Exception e) 
      { 
       ... 
      } 
     }, null); 
    })); 
} 

当从UICommsManager类获取SynchronizationContext.Current传递到构造函数的UI使用。

我更新了我的单元测试,添加以下私有变量:

private SynchronizationContext _Context = null; 

和初始化下面的方法吧:

#region Test Class Initialize 

[TestInitialize] 
public void TestInit() 
{ 
    SynchronizationContext.SetSynchronizationContext(new SynchronizationContext()); 
    _Context = SynchronizationContext.Current; 
} 

#endregion 

然后是UICommsManager被传递到它的构造函数中_Context变量从测试方法。

现在,它适用于两种情况,WPF调用时以及Unit Test调用时。

0

合适的现代代码将永远不会使用Dispatcher。它将您与特定的用户界面联系起来,并且很难进行单元测试(正如您发现的那样)。

best方法是隐式捕获上下文并使用await继续。例如,如果知道Set_Listing_Records_ResponseHandler将始终从UI线程调用的,那么你可以做这样的事情:

public async Task<bool> Set_Listing_Records_ResponseHandler(
    string responseChannelSuffix, 
    Action<List<AIDataSetListItem>> successHandler, 
    Action<Exception> errorHandler) 
{ 
    // Subscribe to Query Response Channel and Wire up Handler for Query Response 
    await this.ConnectAsync(); 
    var tcs = new TaskCompletionSource<FayeMessage>(); 
    await this.SubscribeTo_QueryResponseChannelAsync(responseChannelSuffix, new FayeMessageHandler(
     (client, message) => tcs.TrySetResult(message))); 
    var message = await tcs.Task; 
    // We are already back on the UI thread here; no need for Dispatcher. 
    try 
    { 
    ... 
    } 
    catch (Exception e) 
    { 
    ... 
    } 
} 

但是,如果你处理的事件类型的系统,其中通知可以意外到达,那么你不能总是使用隐式捕获await。在这种情况下,下一个最好的方法是在某个时间点(例如,对象构造)捕获当前的SynchronizationContext,并且将您的工作排队,而不是直接发送给调度员。例如,

private readonly SynchronizationContext _context; 
public Constructor() // Called from UI thread 
{ 
    _context = SynchronizationContext.Current; 
} 
public async Task<bool> Set_Listing_Records_ResponseHandler(
    string responseChannelSuffix, 
    Action<List<AIDataSetListItem>> successHandler, 
    Action<Exception> errorHandler) 
{ 
    // Subscribe to Query Response Channel and Wire up Handler for Query Response 
    await this.ConnectAsync(); 
    return await this.SubscribeTo_QueryResponseChannelAsync(responseChannelSuffix, new FayeMessageHandler(delegate (FayeClient client, FayeMessage message) { 
    _context.Post(new SendOrPostCallback(() => 
    { 
     try 
     { 
      ... 
     } 
     catch (Exception e) 
     { 
      ... 
     } 
    }, null)); 
    })); 
} 

但是,如果你觉得你必须使用调度程序(或只是不希望立即清理代码),你可以使用它提供了一个Dispatcher执行单元测试的WpfContext。请注意,您仍然无法使用Application.Current.Dispatcher(除非您使用MSFakes) - 您必须在某个点捕获调度程序。