为什么我的WCF异步与Rx单元测试不稳定?

时间:2013-01-15 22:59:37

标签: system.reactive reactiveui

我在MVVM项目中使用Rx和RxUI,并且有一个视图模型,可以异步地从WCF服务查询其数据。在单元测试中,我创建了一个模拟对象,它返回一个具有预期值的Task。

以下是我的视图模型的概述

public class ViewModel : ReactiveObject
{
    private IContext _context;

    public ViewModel(IContext context)
    {
        _context = context;

        Observable.FromAsync(() => _context.WcfServiceCall())
              .Subscribe(result => {
                   Children.AddRange(results.Select(r => new ChildViewModel(r).ToList()));
               });
     }

     public ObservableCollection<ChildViewModel> { get; private set;}
}

我的单元测试看起来像这样

[TestFixture]
public class ViewModelTest : AssertionHelper
{
    [Test]
    public void ShouldSetChildren()
    {
        var c = new Mock<IContext>();
        c.Setup(q => q.WcfServiceCall())
            .Returns(Task.Run(() => new []{ 1,2,3,4,5,6 })):

        var vm = new ViewModel(c.Object);
        var p = vm.Children.First(); // this call sometimes fails

        ... 
     }
}

我遇到的问题是我有超过400个测试做这种事情,他们大部分时间都在工作但是我随机地得到失败的测试,一次一个或两个,报告序列没有值。这是不可预测和随机的。我可以在失败后再次运行测试并且全部成功。我按照here所述添加了TestScheduler,但问题仍然存在。

有没有更好的方法来测试进行异步方法调用的方法?

编辑保罗贝特的意见: 我看到FromAsync没有采用IScheduler参数,但我确实有SubscribeOn和ObserveOn可用。

或者,我可以直接调用WCF异步方法并将返回的Task转换为observable。我不确定我什么时候更适合使用Observable.FromAsync而不是使用它。

2 个答案:

答案 0 :(得分:1)

Observable.FromAsync是否采用IScheduler参数?它是你的测试调度程序吗?

答案 1 :(得分:0)

我意识到我的单元测试不正确,因为他们触摸了太多的活动部件。我有测试验证模拟的Web服务调用是在预期时进行的,我真的不应该在不关注该测试点的测试中包含这种复杂性。

导航到视图模型时,我正在调用wcf服务。这是我的视图模型的更好表示,其中有关于指定IScheduler的微小更改:

public class ViewModel : ReactiveObject, IRoutableViewModel
{
    private IContext _context;
    public ViewModel(IContext context)
    {
        _context = context;
        Weeks = new ReactiveCollection<WeekViewModel>();

        this.WhenNavigatedTo(() =>
        {
            _context.Service.GetWeeksAsync()
                .ToObservable()
                .ObserveOn(RxApp.DeferredScheduler)
                .Subscribe(result =>
                {
                    Weeks.AddRange(result.Select(w => WeekViewModel(w)));
                });
        });

        // ...
    }

    public ReactiveCollection<WeekViewModel> Weeks { get; private set; }
}

而不是设置上下文来填充Weeks集合,然后使用路由器导航到ViewModel,我现在在单元测试中添加对象到Weeks集合,并跳过导航和异步Web服务调用。这有点减少了测试,消除了我所知道的不稳定性,并缩短了测试套件的执行时间。

所以我的单元测试看起来像这样

[TestFixture]
public class ViewModelTest : AssertionHelper
{
    [Test]
    public void ShouldSetChildren()
    {
        var c = new Mock<IContext>();
        var vm = new ViewModel(c.Object);
        vm.Children.AddRange((new []{1,2,3,4,5,6}).Select(i => new ChildViewModel(i)));

        var p = vm.Children.First(); // this is valid now - no timing issues

        ... 
    }
}

代码在应用程序中表现正确但在测试运行器中存在问题,所以我相信这解决了我的直接问题。