2011-01-06 4 views
2

En voici un pour les drogués du threading. J'ai cette méthode:Test de problème NMock par rapport à WPF et Dispatcher

public void RefreshMelts() 
    { 
     MeltsAvailable.Clear(); 

     ThreadPool.QueueUserWorkItem(delegate 
     { 
      Dispatcher.BeginInvoke((ThreadStart)delegate 
      { 
       eventAggregator.GetEvent<BusyEvent>().Publish(true); 
       eventAggregator.GetEvent<StatusMessageEvent>().Publish(
        new StatusMessage("Loading melts...", MessageSeverity.Low)); 
      }); 

      try 
      { 
       IList<MeltDto> meltDtos = meltingAppService.GetActiveMelts(); 

       Dispatcher.Invoke((ThreadStart)delegate 
       { 
        foreach (MeltDto availableMelt in meltDtos) 
        { 
         MeltsAvailable.Add(availableMelt); 
        } 
        OnPropertyChanged("MeltsAvailable"); 

        eventAggregator.GetEvent<BusyEvent>().Publish(false); 
        eventAggregator.GetEvent<StatusMessageEvent>().Publish(
         new StatusMessage("Melts loaded", MessageSeverity.Low)); 
       }); 
      } 
      catch (ApplicationException ex) 
      { 
       log.Error("An error occurred in MeltsViewModel when attempting to load melts", ex); 

       Dispatcher.Invoke((ThreadStart)delegate 
       { 
        MeltsAvailable.Clear(); 

        eventAggregator.GetEvent<StatusMessageEvent>().Publish(
         new StatusMessage("Melt data could not be loaded because an error occurred; " + 
          "see the application log for detail", 
          MessageSeverity.High)); 
        eventAggregator.GetEvent<BusyEvent>().Publish(false); 
       }); 
      } 

     }); 

    } 

Ceci est défini dans un contrôle utilisateur WPF. MeltsAvailable est une ObservableCollection de MeltDtos. Ce code fonctionne très bien lors de l'exécution dans l'application elle-même. Le problème est que je voudrais créer un test unitaire, en utilisant NMock, pour vérifier les résultats de cette méthode - spécifiquement, qu'une fois appelée, la propriété MeltsAvailable a quelques éléments. Voici la méthode de test:

[TestMethod] 
    public void GetAvailableMeltsTest() 
    { 
     MeltDto mockMelt1 = new MeltDto(); 
     MeltDto mockMelt2 = new MeltDto(); 

     mockMelt1.MeltIdentifier = "TST0001"; 
     mockMelt2.MeltIdentifier = "TST0002"; 

     IList<MeltDto> availableMelts = new List<MeltDto>(); 
     availableMelts.Add(mockMelt1); 
     availableMelts.Add(mockMelt2); 

     Expect.Exactly(1).On(service).Method("GetActiveMelts").Will(Return.Value(availableMelts)); 


     MeltsViewModel vm = new MeltsViewModel(aggregator, logger, service, configManagerFactory); // All of these are mock objects 

     vm.RefreshMelts(); 
     Thread.Sleep(millisecondDelayForEventPublish * 100); 

     mockery.VerifyAllExpectationsHaveBeenMet(); 

     Assert.AreEqual(vm.MeltsAvailable.Count, 2); 
     Assert.AreEqual(vm.MeltsAvailable[0].MeltIdentifier, "TST0001"); 
     Assert.AreEqual(vm.MeltsAvailable[1].MeltIdentifier, "TST0002"); 

    } 

Le test échoue systématiquement sur le premier Assert.AreEqual. vm.MeltsAvailable est vide à ce stade.

Si je bande tout le filetage et le laisser comme:

public void RefreshMelts() 
    { 
     MeltsAvailable.Clear(); 
     IList<MeltDto> meltDtos = meltingAppService.GetActiveMelts(); 
     foreach (MeltDto availableMelt in meltDtos) 
     { 
      MeltsAvailable.Add(availableMelt); 
     } 
     OnPropertyChanged("MeltsAvailable"); 
    } 

Le test passe. Donc, évidemment, il y a quelque chose qu'il n'aime pas dans les threads - mais même en activant Debug-> Exceptions-> CLR Exceptions-> Thrown, et en désactivant Just My Code, je ne reçois aucune exception dans RefreshMelts .

La partie la plus étrange est que l'appel Dispatcher.Invoke où je charge les objets MeltDto dans la collection MeltsAvailable ne semble jamais être appelé. Je peux couvrir toute la section avec des points d'arrêt, et ils ne sont jamais touchés. Boosting the Thread.Sleep temps dans mon test pour même aussi haut que dix secondes ne change rien.

Pourquoi? Pourquoi cette section ne s'exécute-t-elle pas? Pourquoi ne puis-je pas y entrer ou y pénétrer, pourquoi ne reçois-je pas d'exceptions, pourquoi cela fonctionne-t-il bien dans l'exécution, mais pas dans un test?

Merci beaucoup, Steve

Répondre

4

Le répartiteur est une boucle de message qui est lié au fil d'exécution. Il traite les éléments de sa file d'attente lorsque le thread principal est inactif. Dans un test unitaire, cela n'arrive jamais. Le thread est occupé et il se ferme lorsque le test est terminé.

Si vous utilisez Visual Studio pour exécuter vos tests, vous pouvez activer la surbrillance de la couverture de code et vous verrez ce code dans Dispatcher.Invoke() n'est jamais appelé (il sera affiché en rouge). Un DispatcherFrame peut être utilisé pour déclencher le Dispatcher pour traiter les messages mis en file d'attente. Ajoutez la classe d'aide suivante à votre projet de test unitaire:

public static class DispatcherHelper 
{ 
    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; 
    } 
} 

A la fin de votre test (avant les assertions) DispatcherHelper.DoEvents d'appel(). Cela déclenchera le Dispatcher pour traiter les événements en attente, tels que ceux qui ajoutent des éléments à la collection observable du modèle de vue. Vous pouvez ensuite inspecter les propriétés du modèle de vue pour vérifier qu'elles ont été correctement définies.

Questions connexes