Donnerstag, 13. März 2014

Asynchrone Commands testen leicht gemacht

Was war das immer für ein Krampf die Execute Methode eines Commands zu testen, das asynchonen Code ausführt. Das hat aber schon seit geraumer Zeit ein Ende. NUnit unterstützt seit der Version 2.6.2 das async await, das mit C# 5.0 der Entwicklerwelt zur Verfügung gestellt wurde.

Für alle Commands, die längere Aktionen ausführen sollen, nutze ich die folgende Klasse:
 public class AsyncCommand<TExecuteArg, TCanExecuteArg> : ICommand  
 {  
      public event EventHandler CanExecuteChanged  
      {  
           add  
           {  
                if (_canExecuteMethod == null)  
                     return;  
                CommandManager.RequerySuggested += value;  
           }  
           remove  
           {  
                if (_canExecuteMethod == null)  
                     return;  
                CommandManager.RequerySuggested -= value;  
           }  
      }  
      public AsyncCommand(Func<TExecuteArg, Task> executeMethod, Func<TCanExecuteArg, bool> canExecuteMethod = null)  
      {  
           if (executeMethod == null)  
                throw new ArgumentNullException("executeMethod");  
           _executeMethod = executeMethod;  
           _canExecuteMethod = canExecuteMethod;  
      }  
      public Task Execute(TExecuteArg arg)  
      {  
           return _executeMethod(arg);  
      }  
      public bool CanExecute(TCanExecuteArg arg)  
      {  
           return _canExecuteMethod == null || _canExecuteMethod(arg);  
      }  
      bool ICommand.CanExecute(object parameter)  
      {  
           return CanExecute((TCanExecuteArg) parameter);  
      }  
      async void ICommand.Execute(object parameter)  
      {  
           await Execute((TExecuteArg) parameter);  
      }  
      private readonly Func<TExecuteArg, Task> _executeMethod;  
      private readonly Func<TCanExecuteArg, bool> _canExecuteMethod;  
 }
Wie man sehen kann, ist die Execute Methode mit dem async Keyword versehen. Alles was in der Execute Action ausgeführt wird, kann also asynchron sein.

Die Benutzung ist einfach, so wie man es vom async/await bzw. von den synchronen Commands her kennt. Hier ein Beispiel in dem im MainViewModel drei Sekunden nach dem Ausführen des Commands eine Nachricht gesetzt werden soll.
 public class MainViewModel : ViewModelBase  
 {  
      public string Message  
      {  
           get { return _message; }  
           set  
           {  
                _message = value;  
                NotifyPropertyChanged("Message");  
           }  
      }  
      public AsyncCommand<object, object> LongRunningCommand { get; private set; }  
      public MainViewModel()  
      {  
           LongRunningCommand = new AsyncCommand<object, object>(OnLongRunningExecute);  
      }  
      private async Task OnLongRunningExecute(object args)  
      {  
           await Task.Run(() => Thread.Sleep(TimeSpan.FromSeconds(3)));  
           Message = "Test message";  
      }  
      private string _message;  
 }
Die OnLongRunningExecute Methode ist hier auch mit dem async Keyword versehen und in der Methode werden dann die drei Sekunden gewartet bevor die Nachricht gesetzt wird.
Diese Implementierung hat den großen Vorteil, das die Anwendung nicht einfriert nachdem der Button geklickt wurde.

Und nun zum eigentlichen Thema: Testen!
Wie schon geschrieben, unterstützt NUnit seit der Version 2.6.2 das Ausführen von asynchronen Tests. Dementsprechend einfach ist es das Command zu testen:
 [TestFixture]  
 public class LongRunningCommand  
 {  
      [Test]  
      public async void Usage()  
      {  
           var viewModel = new MainViewModel();  
           await viewModel.LongRunningCommand.Execute(null);  
           viewModel.Message.Should().Be("Test message");  
      }  
 }
Benutzt man eine ältere Version von NUnit, läuft der Test in weniger als einer Sekunde durch und wird beim Assert schief laufen.

Hier der Sourcecode mit Visual Studio 2012 Solution.