2016-05-11 8 views
2

Nach der Migration zu ReactiveUI 6.5.0 begannen meine Tests fehlzuschlagen. Das Verhalten von Resharper Test Runner ist sehr seltsam. Wenn ich Tests nacheinander abspiele - die Tests bestehen, aber wenn ich sie mit Big Bang Ansatz laufe - Run Unit Tests (wir haben etwa 1k Unit Tests) einige Tests scheitern. Dies ist einer von ihnen:Reactive 6.5.0 ReactiveCommand nach der Migration

 [Test] 
     public void TestThat_CallingRun_CallsLogin() 
     { 
      //act 
      _sut.Actions.Single().Command.Execute(null); 

      //assert 
      _controller.AssertWasCalled(x => x.Login()); 
     } 

     public ViewModel(IWfController workflowController) 
     { 
      _workflowController = workflowController; 

      _runProcessCommand = ReactiveCommand.Create(CanRunProcess()); 
      _runProcessCommand.Subscribe(RunImpl); 
     } 

     private void RunImpl(object obj) 
     { 
      _workflowController.Login(); 
     } 

_sut.Actions.Single().Command kehrt _runProcessCommand.

Dieser Test wurde mit ReactiveUI 4.5 bestanden. Wird garantiert synchron ausgeführt? Wenn nicht - was ist der richtige Weg, es jetzt zu testen? Warum gibt es ein unterschiedliches Verhalten zwischen dem Ausführen von ihnen nacheinander? Ich würde mich für irgendwelche Ideen freuen.

Edit:

Ich habe bemerkt, dass RunImpl auf einem anderen Thread aufgerufen wird (nur wenn mehrere Komponententests ausgeführt wird) und es ändert nichts, wenn ich

ersetzen
_runProcessCommand.Subscribe(RunImpl); 

mit

_runProcessCommand.ObserveOn(RxApp.MainThreadScheduler).Subscribe(RunImpl) 

RunImpl wird immer noch auf einem anderen Thread aufgerufen, daher wird BEFORE ausgeführt, bevor der Befehl ausgeführt wird.

Bearbeiten 2: Dies ist die Korrektur, die ich in NUnit [SetUp] Funktion aufrufen. Wenn ich auf Scheduler.Immediate beobachte, wird es nicht so gut funktionieren. Irgendwelche Ideen, warum es nicht standardmäßig auf Immediate gesetzt ist, wenn ich mehrere Tests durchführe? (Es funktioniert nicht für all vorgeschriebenen Prüfungen nicht)

RxApp.MainThreadScheduler = Scheduler.Immediate; 
RxApp.TaskpoolScheduler = TaskPoolScheduler.Default; 

Edit: Nach tiefen Debuggen in RX Quellen in WaitForDispatcherScheduler Ich habe bemerkt, dass:

IScheduler attemptToCreateScheduler() 
     { 
      if (_innerScheduler != null) return _innerScheduler; 
      try { 
       _innerScheduler = _schedulerFactory(); 
       return _innerScheduler; 
      } catch (Exception) { 
       // NB: Dispatcher's not ready yet. Keep using CurrentThread 
       return CurrentThreadScheduler.Instance; 
      } 
     } 
RxApp.MainThreadScheduler = new WaitForDispatcherScheduler(() => DispatcherScheduler.Current); 

Wenn Scheduler Fabrik für einzelnen Test Aufruf Es wirft eine Ausnahme auf und gibt CurrentThreadScheduler.Instance zurück. Wenn mehrere Tests ausgeführt werden, gibt Dispatcher.Current eine Instanz zurück. Kann mir jemand erklären, was hier abgeht?

Einfacher reproduzierbarer Test:

[Test] 
    public void Test1() 
    { 
     var disp =DispatcherScheduler.Current; // throws exception (System.InvalidOperationException : The current thread has no Dispatcher associated with it.) when   running single tests, not throwing exception when running multiple tests. 
    } 

Edit 2: Ich habe gefunden, was DispatcherScheduler.Current unterschiedliche Werte zurückgeben verursacht. Einer der vorherigen Test verwendet DelegateCommand, die innerhalb CommandManager.InvalidateRequerySuggested();CommandManager.InvalidateRequerySuggested(); ruft Dispatcher und Wert wird von attemptToCreateScheduler() verursacht verursacht den Rest der Tests fehlgeschlagen, weil sie nicht auf Immediate Scheduler, sondern Dispatcher aufgerufen werden (was ist es in Unit-Tests? Es tut nicht verhalten sich wie sofort Scheduler)

Edit3:

Dieses Verhalten, das ich bei der Arbeit erlebt habe und ich konnte es nicht zu Hause reproduzieren. Aber ich bin mir sicher, dass etwas nicht in Ordnung ist, es sei denn, es gibt etwas, was ich nicht über Reactive verstehe. Ich denke, dass es keinen Sinn macht, das gesamte Projekt einzubeziehen. Dieses Beispiel zeigt genau, dass der Scheduler Dispatcher ist und nicht der aktuelle Thread, wenn Sie diese Funktion verwenden. Bitte debuggen Sie es und Sie werden ein anderes Verhalten zwischen diesen beiden Aufrufen in der Funktion "versuchenToCreateScheduler()" in der WaitForDispatcherScheduler-Klasse bemerken. ReactiveUI ist Version 7.0.

[Test] 
     public void TestMethod1() 
     { 
      RxApp.MainThreadScheduler.Schedule<string>(null, (x, y) => Disposable.Empty); 
      CommandManager.InvalidateRequerySuggested(); 
      RxApp.MainThreadScheduler.Schedule<string>(null, (x, y) => Disposable.Empty); 
     } 
+0

Es sollte nicht den Dispatcher überhaupt verwenden - Sie können [in der Quelle] (https://github.com/reaceuci/ReactiveUI/blob/rxui6-master/ReactiveUI/RxApp.cs#L66) sehen, dass Es sollte erkennen, dass Sie sich in einem Komponententest befinden und 'CurrentThreadScheduler' verwenden. Was gibt 'ModeDetector.InUnitTestRunner()' in Ihren Unit-Tests zurück? –

+0

Falsch. Das Problem ruft CommandManager.InvalidateRequerySuggested() auf; Das erzeugt einen Dispatcher, der als Ergebnis beim Aufrufen von RaiseAndSetIfChanged diese Funktion aufruft und den Dispatcher findet. Sie können das testen - machen Sie einen Komponententest ein invoke CommandManager.InvalidateRequerySuggested(); und dann RaiseAndSetIfChanged aufrufen. Wenn Sie nach dem Aufruf von OnNext innerhalb von ScheduledSubject einen Haltepunkt setzen, wird er versuchToCreateScheduler() aufrufen und den Dispatcher zurückgeben. – MistyK

+0

@up es ist nicht falsch. – MistyK

Antwort

1

Es gibt eine Nichtübereinstimmung zwischen der asynchronen Natur eines Befehls und der Synchrontest ausführt.

Unter den Abdeckungen Execute nur ExecuteAsync().Subscribe() läuft, so wird dies alles tun wird der Startbefehl ausgeführt wird, gibt es keine Garantie, dass der Befehl von der es wieder Zeit der Ausführung beendet haben. Die Tatsache, dass es in bestimmten Tests geschieht, hängt vollständig von den verwendeten Schedulern ab.

Was sollten Sie asynchron tun wird für den Befehl zu beenden warten, bevor sie irgendwelche Nebenwirkungen zu behaupten:

[Test] 
public async Task Test() 
{ 
    // arrange 

    await command.ExecuteAsync(); 

    // assert 
} 

Als Nebenwirkung, und wie sie in den verschiedenen Kommentaren erwähnt, ich glaube nicht die Absicht ist, für MainThreadScheduler, um den Dispatcher im Unit-Test-Szenario zu verwenden. Es scheint, dass dies ein Fehler sein könnte. Weitere Einzelheiten finden Sie unter this github issue.

+0

Bitte schauen Sie sich edit3 an – MistyK

+0

@Zbigniew Sie müssen den Splat-Modus-Detektor debuggen, um herauszufinden, warum er nicht glaubt, dass Sie in einem Unit-Test-Runner laufen. Die Tatsache, dass der Befehl einen Dispatcher erzeugt, ist nicht so wichtig - "MainThreadScheduler" sollte nicht an erster Stelle auf "WaitForDispatcherScheduler" gesetzt sein. –

+0

Sind Sie sicher? Dies ist die Zeile aus Registrations.cs #if! MONO &&! NETFX_CORE RxApp.MainThreadScheduler = neu WaitForDispatcherScheduler (() => DispatcherScheduler.Current); Ich verstehe nicht, warum Sie über Splay sagen. Mode.IsUnitTestRunner() gibt jedes Mal True zurück – MistyK