测试调度程序/节流阀的推荐方法 [英] Recommended way to test Scheduler/Throttle

查看:49
本文介绍了测试调度程序/节流阀的推荐方法的处理方法,对大家解决问题具有一定的参考价值,需要的朋友们下面随着小编来一起学习吧!

问题描述

我正在重写我编写的一个小 WPF 应用程序以使用 ReactiveUI,以了解该库.

到目前为止我真的很喜欢它!

现在我偶然发现了 Throttle 方法,并希望在对集合应用过滤器时使用它.

这是我的视图模型:

命名空间 ReactiveUIThrottle{公共类 MainViewModel : ReactiveObject{私人字符串 _filter;公共字符串过滤器 { get =>_筛选;设置 =>this.RaiseAndSetIfChanged(ref _filter, value);}private readonly ReactiveList_persons = new ReactiveList();private readonly ObservableAsPropertyHelper>_filteredPersons;public IReactiveDerivedList人 =>_filteredPersons.Value;公共主视图模型(){过滤器 = string.Empty;_persons.AddRange(新[]{新人(彼得"),新人(简"),新人(乔恩"),新人(马克"),新人(亨氏")});var filterPersonsCommand = ReactiveCommand.CreateFromTask>(FilterPersons);this.WhenAnyValue(x => x.Filter)//查看问题.Throttle(TimeSpan.FromMilliseconds(2000), RxApp.MainThreadScheduler).InvokeCommand(filterPersonsCommand);_filteredPersons = filterPersonsCommand.ToProperty(this, vm => vm.Persons, _persons.CreateDerivedCollection(p => p));}私有异步任务<IReactiveDerivedList<Person>>FilterPersons(字符串过滤器){等待 Task.Delay(500);//假设这需要一些时间return _persons.CreateDerivedCollection(p => p, p => p.Name.Contains(filter));}}}

在使用 GUI 时,过滤本身就像一种魅力,也是一种限制.

但是,我想对过滤的行为进行单元测试,这是我的第一次尝试:

 [测试]public void FilterPersonsByName(){var sut = new MainViewModel();sut.Persons.Should().HaveCount(5);sut.Filter = "J";sut.Persons.Should().HaveCount(2);}

此测试失败,因为该集合仍有 5 个人.

当我摆脱 FilterPersons 中的 await Task.Delay(500) 时,测试将通过,但需要 2 秒(从油门开始).

1) 有没有办法让测试中的油门瞬间加速以加速单元测试?

2) 我将如何测试过滤器中的异步行为?

我使用的是 ReactiveUI 7.x

解决方案

简短回答:

  1. 是的,确保在测试下运行时使用 CurrentThreadScheduler.Instance
  2. 不要使用 CurrentThreadScheduler,而是使用 TestScheduler 并手动推进它

更长的答案是您需要确保您的单元测试可以控制您的被测系统 (SUT) 正在使用的调度程序.默认情况下,您通常希望使用 CurrentThreadScheduler.Instance 使事情立即"发生,而无需手动推进调度程序.但是当你想编写验证时间的测试时,你可以使用 TestScheduler 代替.

如果,如您所见,您正在使用 RxApp.*Scheduler,请查看 With 扩展方法,它可以像这样使用:

(new TestScheduler()).With(sched => {//在这里写测试逻辑,RxApp.*Scheduler 会解析到选中的TestScheduler});

我倾向于完全避免使用 RxApp 环境上下文,原因与我避免所有环境上下文的原因相同:它们是共享状态,因此可能会导致问题.相反,我将一个 IScheduler(或两个)作为依赖项注入到我的 SUT 中.

I'm in the process of rewriting one little WPF-App I wrote to make use of ReactiveUI, to get a feeling about the library.

I really like it so far!

Now I've stumbled upon the Throttle method and want to use it when applying a filter to a collection.

This is my ViewModel:

namespace ReactiveUIThrottle
{
    public class MainViewModel : ReactiveObject
    {
        private string _filter;

        public string Filter { get => _filter; set => this.RaiseAndSetIfChanged(ref _filter, value); }

        private readonly ReactiveList<Person> _persons = new ReactiveList<Person>();

        private readonly ObservableAsPropertyHelper<IReactiveDerivedList<Person>> _filteredPersons;

        public IReactiveDerivedList<Person> Persons => _filteredPersons.Value;
        public MainViewModel()
        {
            Filter = string.Empty;
            _persons.AddRange(new[]
            {
                new Person("Peter"),
                new Person("Jane"),
                new Person("Jon"),
                new Person("Marc"),
                new Person("Heinz")
            });

            var filterPersonsCommand = ReactiveCommand.CreateFromTask<string, IReactiveDerivedList<Person>>(FilterPersons);

            this.WhenAnyValue(x => x.Filter)
                // to see the problem
                .Throttle(TimeSpan.FromMilliseconds(2000), RxApp.MainThreadScheduler)
                .InvokeCommand(filterPersonsCommand);

            _filteredPersons = filterPersonsCommand.ToProperty(this, vm => vm.Persons, _persons.CreateDerivedCollection(p => p));


        }
        private async Task<IReactiveDerivedList<Person>> FilterPersons(string filter)
        {
            await Task.Delay(500); // Lets say this takes some time
            return _persons.CreateDerivedCollection(p => p, p => p.Name.Contains(filter));
        }
    }
}

The filtering itself works like a charm, also the throttling, when using the GUI.

However, I'd like to unittest the behavior of the filtering and this is my first attempt:

    [Test]
    public void FilterPersonsByName()
    {
        var sut = new MainViewModel();

        sut.Persons.Should().HaveCount(5);
        sut.Filter = "J";
        sut.Persons.Should().HaveCount(2);
    }

This test fails because the collection still has 5 people.

When I get rid of the await Task.Delay(500) in FilterPersons then the test will pass, but takes 2 seconds (from the throttle).

1) Is there a way to have the throttle be instant within the test to speed up the unittest?

2) How would I test the async behavior in my filter?

I'm using ReactiveUI 7.x

解决方案

Short answers:

  1. Yes, by making sure you're using CurrentThreadScheduler.Instance when running under test
  2. Instead of using CurrentThreadScheduler, use a TestScheduler and manually advance it

The longer answer is that you need to ensure your unit tests can control the scheduler being used by your System Under Test (SUT). By default, you'll generally want to use CurrentThreadScheduler.Instance to make things happen "instantly" without any need to advance the scheduler manually. But when you want to write tests that do validate timing, you use a TestScheduler instead.

If, as you seem to be, you're using RxApp.*Scheduler, take a look at the With extension method, which can be used like this:

(new TestScheduler()).With(sched => {
    // write test logic here, and RxApp.*Scheduler will resolve to the chosen TestScheduler
});

I tend to avoid using the RxApp ambient context altogether for the same reason I avoid all ambient contexts: they're shared state and can cause trouble as a consequence. Instead, I inject an IScheduler (or two) into my SUT as a dependency.

这篇关于测试调度程序/节流阀的推荐方法的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!

查看全文
登录 关闭
扫码关注1秒登录
发送“验证码”获取 | 15天全站免登陆