wpf 在测试期间使用Application.Current.Dispatcher

rmbxnbpk  于 2023-06-30  发布在  其他
关注(0)|答案(2)|浏览(167)

在我们的很多视图模型中,我们的代码看起来像这样:

void Process()
{
    // do some work
    ...

    // Dispatch some UI notification (purely visual related)
    Application.Current.Dispatcher.BeginInvoke(() => ...);
}

我们使用Application.Dispatcher来确保它在UI线程上运行,因为Dispatcher.Current可能不是UI调度器。
调度调用的原因是无关紧要的,不会影响控制流或逻辑,但是,当我们测试视图模型时,我们显然遇到了Application.Current为null的问题。
假设被调度的代码对测试没有影响。你认为什么是最好的方法来解决它是空的问题?
我能想到几个
1.简单地使用Application.Current?.Dispatcher()。这是代码的味道吗
1.拥有某种可模拟的IApplicationDispatcher,每个ViewModel都可以访问。这为我们提供了最强大的功能,但同时,现在为每个视图模型提供一个额外的构造函数参数是令人讨厌的。
1.确保测试在初始化时与创建的新应用程序一起运行。这是丑陋的,并且需要是所有UI测试共享的静态应用程序。

5f0d552i

5f0d552i1#

Dispatcher在视图模型中通常是不需要的。这是因为视图模型一般不应该处理UI元素。换句话说,扩展DispatcherObject的类型不应该在视图之外引用。
当视图模型类实现INotifyPropertyChanged时,从后台线程设置属性不需要Dispatcher。绑定引擎将把INotifyPropertyChanged.PropertyChanged事件封送到正确的调度器线程(UI线程)。
这不适用于INotifyCollectionChanged.CollectionChanged事件。您必须显式配置绑定引擎,以便通过使用BindingOperations.EnableCollectionSynchronization方法将事件封送到调度器线程。这样,绑定引擎会在正确的调度器线程上引发INotifyCollectionChanged.CollectionChanged事件:

class ViewModel
{
  public ObservableCollection<object> BindingSourceCollection { get; }
  private object SyncLock { get; }

  public ViewModel()
  {
    this.SyncLock = new object();
    this.BindingSourceCollection = new ObservableCollection<object>();    

    // The call must occur on the UI thread.
    // The call must occur before using the collection on a different thread. 
    // The call must occur before attaching the collection to the ItemsControl.
    BindingOperations.EnableCollectionSynchronization(this.BindingSourceCollection, this.SyncLock);
  }
}

这样就消除了视图模型中最常见的Dispatcher用法(设置作为绑定源的属性,并向作为绑定源的集合添加项)。这将从代码中删除静态Singleton引用,以实现完全的可测试性。

sxissh06

sxissh062#

您应该使用IApplicationDispatcher接口(选项2)。它不一定是构造函数参数。例如,您可以将其作为属性添加到基本视图模型类中,并在测试中根据需要进行设置。
只要确保在真实的应用中将该属性设置为Application.Current.Dispatcher的 Package 器即可。
我担心在视图模型中调用静态方法不会使它们对单元测试非常友好。
检查空引用(选项1)不是代码气味。你也应该这样做。

相关问题