迁移到ReactiveUI 6.5.0后,我的测试开始失败。Resharper Test Runner的行为非常奇怪。当我一个接一个地运行测试时——测试通过了,但如果我用大爆炸的方法运行它们——运行单元测试(我们有大约1k个单元测试)——有些测试失败了。这就是其中之一:
[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();
}
1返回CCD_ 2。
此测试通过了ReactiveUI 4.5。是否保证同步执行?如果没有,现在测试它的正确方法是什么?为什么在一个接一个地运行它们之间会有不同的行为?我很乐意有任何想法。
编辑:
我注意到RunImpl是在另一个线程上调用的(只有在运行多个单元测试时),如果我替换,它不会改变任何东西
_runProcessCommand.Subscribe(RunImpl);
带有
_runProcessCommand.ObserveOn(RxApp.MainThreadScheduler).Subscribe(RunImpl)
RunImpl仍在另一个线程上调用,因此在执行命令之前进行断言。
编辑2:这是我在NUnit [SetUp]
函数中调用的修复程序。如果我在Scheduler.Immediate
上观察,它将无法正常工作。有什么想法吗?当我运行多个测试时,为什么默认情况下不设置为立即?(它不适用于所有失败的测试)
RxApp.MainThreadScheduler = Scheduler.Immediate;
RxApp.TaskpoolScheduler = TaskPoolScheduler.Default;
编辑:在WaitForDispatcherScheduler
中对RX源进行深入调试后,我注意到:
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);
当为单个测试调用调度程序工厂时,它抛出一个异常并返回CurrentThreadScheduler.Instance
。当运行多个测试时,Dispatcher.Current
返回一个实例。有人能解释一下这里发生了什么吗?
简单的可复制测试:
[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.
}
编辑2:我已经找到导致DispatcherScheduler的原因。Current以返回不同的值。之前的一个测试是使用DelegateCommand,它在内部调用CommandManager.InvalidateRequerySuggested();
,CCD_8创建调度器,值由attemptToCreateScheduler()
返回,导致其余测试失败,因为它们不是在即时调度器上调用的,而是在调度器上调用(单元测试中是什么?它的行为不像即时调度器)
第3版:
我在工作中经历过这种行为,但在家里我无法重现。但我确信肯定是出了什么问题,除非我对Reactive有什么不理解的地方。我认为包含整个项目是没有意义的,这个例子确切地表明,如果使用该函数,调度器是调度器,而不是当前线程。请调试它,您会注意到这两个调用在WaitForDispatcherScheduler类的attemptToCreateScheduler()函数中的行为不同。ReactiveUI版本为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);
}
执行命令的异步性质与同步测试之间不匹配。
在幕后,_sut.Actions.Single().Command
0只是运行ExecuteAsync().Subscribe()
,所以所有这些都将启动正在运行的命令,无法保证该命令在返回时已完成执行。它在某些测试中的作用完全取决于所使用的调度器。
您应该做的是异步等待命令完成,然后再断言任何副作用:
[Test]
public async Task Test()
{
// arrange
await command.ExecuteAsync();
// assert
}
顺便说一句,正如在各种评论中所指出的,我不认为MainThreadScheduler
打算在单元测试场景中使用调度器。看起来这可能是一个错误。有关更多详细信息,请参阅本期github。