2012-02-17 12 views
5

Próbuję wykonać sugestie od Using the WPF Dispatcher in unit tests, aby uruchomić test nUnit.Prawidłowa metoda korzystania z programu rozsyłającego WPF w testach jednostkowych

Kiedy piszę badanej jednostki, jak poniżej, to działa:

[Test] 
public void Data_Should_Contain_Items() 
{ 
    DispatcherFrame frame = new DispatcherFrame(); 
     PropertyChangedEventHandler waitForModelHandler = delegate(object sender, PropertyChangedEventArgs e) 
     { 
      if (e.PropertyName == "Data") 
      { 
      frame.Continue = false; 
      } 
     }; 
    _myViewModel.PropertyChanged += waitForModelHandler; 
    Dispatcher.PushFrame(frame); 

    Assert.IsTrue(_myViewModel.Data.Count > 0, "Data item counts do not match"); 
} 

Jednakże jeśli próbuję użyć sugestię DispatcherUtil, to nie działa:

[Test] 
public void Data_Should_Contain_Items() 
{ 
    DispatcherUtil.DoEvents(); 
    Assert.IsTrue(_myViewModel.Data.Count > 0, "Data item counts do not match"); 
} 

public static class DispatcherUtil 
{ 
    [SecurityPermissionAttribute(SecurityAction.Demand, Flags = SecurityPermissionFlag.UnmanagedCode)] 
    public static void DoEvents() 
    { 
     DispatcherFrame frame = new DispatcherFrame(); 
     Dispatcher.CurrentDispatcher.BeginInvoke(DispatcherPriority.Background, 
      new DispatcherOperationCallback(ExitFrame), frame); 
     Dispatcher.PushFrame(frame); 
    } 

    private static object ExitFrame(object frame) 
    { 
     ((DispatcherFrame)frame).Continue = false; 
     return null; 
    } 
} 

Kiedy Korzystam z DispatcherUtil, wygląda na to, że wywołanie ExitFrame dzieje się zbyt wcześnie, zanim dane są gotowe.

Czy nie używam poprawnie narzędzia DispatcherUtil? Wydaje się, że jest to lepsza metoda do obsługi dyspozytora niż czekania na wywołania zwrotne z modelu widoku.

+0

Co chcesz przetestować, tylko jeśli PropertyChangedEventHandler jest wywoływany dla własności „Dane”? Jeśli tak, dlaczego potrzebujesz zaangażować dyspozytora? Nie używam również _ModelViewModel do dołączania programu obsługi. – Phil

+0

@Phil: Po utworzeniu instancji _myViewModel jej konstruktor wykonuje wywołanie asyn. Po zakończeniu wywołania funkcja _myViewModel.Data powinna mieć pewne wartości. Próbuję przetestować, że dane są w rzeczywistości zapełnione, ale fakt, że dane są zapełniane w wyniku wywołania asyn, powoduje pewne kłopoty. Chciałbym uniknąć konieczności słuchania zdarzeń PropertyChanged w dowolnym teście jednostkowym, który może mieć do czynienia z Dispatcherem. – Flack

Odpowiedz

7

Ponieważ dyspozytor jest problematyczny w testach jednostkowych, moim rozwiązaniem byłoby złamanie zależności modelu widoku od dyspozytora. Zakładam, że obecnie masz ciężko kodowane odniesienia, takie jak:

Dispatcher.CurrentDispatcher.BeginInvoke(.. 

dyspozytor jest zewnętrznym zależność i nie powinny być częścią testów jednostkowych - możemy założyć dyspozytor prace.

Używałbym zastrzyku zależności (biedny mans, Jedność, itp.). Utwórz odpowiedni interfejs reprezentujący dyspozytora. Stwórz realną implementację, która otoczy prawdziwego dyspozytora. Utwórz fałszywą implementację, która używa Action.BeginInvoke. W fałszywej rejestracji wszystkie IAsyncResults wróciły do ​​wywołań BeginInvoke.
Następnie należy użyć metody pomocnika, która będzie czekać na wszystkie połączenia, które można wykorzystać w teście, aby czekać na zakończenie.

Lub mieć klasę podstawową modelu widoku, która robi to samo. W normalnych warunkach wywołuje dyspozytora, ale można go skierować, aby wywołać fałszywkę podczas testów.

0

Znalazłem proste rozwiązanie. Zamiast przetwarzania Asynchronizacji klatek w Dispatcherze dodałem kolejną zsynchronizowaną metodę do klasy DispatcherUtil. Wywołanie tej DoEventsSync() - metoda powraca gdy wszystkie ramki zostały przetworzone, myślę, że to powinno pomóc tutaj:

public static class DispatcherUtil 
    { 
     [SecurityPermission(SecurityAction.Demand, Flags = SecurityPermissionFlag.UnmanagedCode)] 
     public static void DoEvents() 
     { 
      var frame = new DispatcherFrame(); 
      Dispatcher.CurrentDispatcher.BeginInvoke(DispatcherPriority.Background, 
       new DispatcherOperationCallback(ExitFrame), frame); 
      Dispatcher.PushFrame(frame); 
     } 

     public static void DoEventsSync() 
     { 
      var frame = new DispatcherFrame(); 
      Dispatcher.CurrentDispatcher.Invoke(DispatcherPriority.Background, 
       new DispatcherOperationCallback(ExitFrame), frame); 
      Dispatcher.PushFrame(frame); 
     } 

     private static object ExitFrame(object frame) 
     { 
      ((DispatcherFrame)frame).Continue = false; 
      return null; 
     } 
    } 
Powiązane problemy