Планировщики в тестировании ReactiveUI - PullRequest
0 голосов
/ 08 января 2019

Итак, когда я разрабатываю новую функцию для своей системы, я тоже пытаюсь сделать TDD - код слишком большой, чтобы делать это для старых функций прямо сейчас, к сожалению.

Однако я обнаружил, что иногда я сталкиваюсь с кирпичной стеной во время испытаний - особенно при использовании Delay и Throttle.

Я много читал, и я думаю, что знаю намного больше, чем неделю назад, но я хотел применить все это на практике. Я написал несколько экспериментов:

    using System;
    using System.Collections.Generic;
    using System.Linq;
    using System.Reactive;
    using System.Reactive.Concurrency;
    using System.Reactive.Linq;
    using System.Reactive.Threading.Tasks;
    using System.Text;
    using System.Threading.Tasks;
    using Microsoft.Reactive.Testing;
    using NUnit.Framework;
    using NUnit.Framework.Internal.Commands;
    using ReactiveUI;
    using ReactiveUI.Testing;

    namespace UtilsTests
    {
        [TestFixture]
        public class SchedulersTests
        {
            private int SecondsN = 1;

            [Test]
            public async Task NoScheduler()
            {
                var t = Observable.Return(Unit.Default).Delay(TimeSpan.FromSeconds(SecondsN), RxApp.MainThreadScheduler)
                    .ObserveOn(RxApp.MainThreadScheduler)
                    .ToTask();
                await t;
            }

            [Test]
            public Task ImmediateSchedulerExperiment()
            {
                return Scheduler.Immediate.With(async s =>
                {
                    var t = Observable.Return(Unit.Default).Delay(TimeSpan.FromSeconds(SecondsN), RxApp.MainThreadScheduler).ToTask();
                    await t;
                });
            }

            [Test]
            public Task ImmediateSchedulerExperiment2()
            {
                return Scheduler.Immediate.With(async s =>
                {
                    var t = Observable.Return(Unit.Default).Delay(TimeSpan.FromSeconds(SecondsN), s).FirstAsync().ToTask();
                    await t;
                });
            }

            [Test]
            public void ImmediateSchedulerExperiment3()
            {
                Scheduler.Immediate.With(s =>
                {
                    var t = false;
                    Observable.Return(Unit.Default).Delay(TimeSpan.FromSeconds(SecondsN), s)
                        .Subscribe(_ =>
                        {
                            t = true;
                        });

                    Assert.IsTrue(t);
                });
            }


            [Test]
            public void TestSchedulerExperiment_SchedulersNotSpecified()
            {
                new TestScheduler().With(s =>
                {
                    var t = false;
                    Observable.Return(Unit.Default).Delay(TimeSpan.FromSeconds(SecondsN), s)
                        .Subscribe(_ =>
                        {
                            t = true;
                        });

                    s.AdvanceByMs(SecondsN * 1000);

                    Assert.IsTrue(t);
                });
            }

            [Test]
            public void TestSchedulerExperiment_DeylaOn_RxMainThread()
            {
                new TestScheduler().With(s =>
                {
                    var t = false;
                    Observable.Return(Unit.Default).Delay(TimeSpan.FromSeconds(SecondsN), RxApp.MainThreadScheduler)
                        .Subscribe(_ =>
                        {
                            t = true;
                        });

                    s.AdvanceByMs(SecondsN * 1000);

                    Assert.IsTrue(t);
                });
            }

            [Test]
            public void TestSchedulerExperiment_DeylaOn_RxTaskPool()
            {
                new TestScheduler().With(s =>
                {
                    var t = false;
                    Observable.Return(Unit.Default).Delay(TimeSpan.FromSeconds(SecondsN), RxApp.TaskpoolScheduler)
                        .Subscribe(_ =>
                        {
                            t = true;
                        });

                    s.AdvanceByMs(SecondsN * 1000);

                    Assert.IsTrue(t);
                });
            }

            [Test]
            public void TestSchedulerExperiment_RunOnTaskPool_ObserveOnMainThread()
            {
                new TestScheduler().With(s =>
                {
                    var t = false;
                    Observable.Return(Unit.Default)
                        .Delay(TimeSpan.FromSeconds(SecondsN), RxApp.TaskpoolScheduler)
                        .ObserveOn(RxApp.MainThreadScheduler)
                        .Subscribe(_ =>
                        {
                            t = true;
                        });

                    s.AdvanceByMs(SecondsN * 1000);

                    Assert.IsTrue(t);
                });
            }

            [Test]
            public void TestSchedulerExperiment_RunOnTaskPool_ObserveOnTaskpool()
            {
                new TestScheduler().With(s =>
                {
                    var t = false;
                    Observable.Return(Unit.Default)
                        .Delay(TimeSpan.FromSeconds(SecondsN), RxApp.TaskpoolScheduler)
                        .ObserveOn(RxApp.TaskpoolScheduler)
                        .Subscribe(_ =>
                        {
                            t = true;
                        });

                    s.AdvanceByMs(SecondsN * 1000);
                    s.AdvanceByMs(1);

                    Assert.IsTrue(t);
                });
            }

            [Test]
            public void TestSchedulerExperiment_RunOnTaskPool_ObserveOnMainThread_MainThreadIsAnotherInstance()
            {
                new TestScheduler().With(s =>
                {
                    var mainThreadScheduler = new TestScheduler();
                    RxApp.MainThreadScheduler = mainThreadScheduler;
                    var t = false;
                    Observable.Return(Unit.Default)
                        .Delay(TimeSpan.FromSeconds(SecondsN), RxApp.TaskpoolScheduler)
                        .ObserveOn(RxApp.MainThreadScheduler)
                        .Subscribe(_ =>
                        {
                            t = true;
                        });

                    s.AdvanceByMs(SecondsN * 1000);
                    mainThreadScheduler.AdvanceBy(1);
                    Assert.IsTrue(t);
                });
            }

            [Test]
            public void TestSchedulerExperiment_RunOnTest_ObserveOnTest()
            {
                new TestScheduler().With(s =>
                {
                    var t = false;
                    var obs = Observable.Return(Unit.Default)
                        .Delay(TimeSpan.FromSeconds(SecondsN), s)
                        .ObserveOn(s);
                    obs
                        .Subscribe(_ =>
                        {
                            t = true;
                        });

    //                s.AdvanceByMs(SecondsN * 1000);
    //                s.AdvanceBy(1);

                    s.AdvanceUntil(obs);

                    Assert.IsTrue(t);
                });
            }
        }


    }

Сначала я думал, что Scheduler.Immediate сделает свое дело, выполняя вещи с задержкой прямо на месте, и мальчик, это неправильно. Я нашел эту статью, которая довольно хорошо объясняла вещи. Я нашел также этот пост, объясняющий, какой оператор использует какой планировщик.

Теперь я знаю, что при игре со временем я должен использовать TestScheduler. В противном случае не меняйте расписание.

Теперь я знаю, что вы НЕ делаете ничего асинхронного в contructor, вместо этого вы создаете команду, скажем, скажем Init, которая делает это при активации, и вы можете ожидать ее в тесте (например, создание отложенной коллекции на основе конструктора). аргумент, разрешающий плавную анимацию пользовательского интерфейса, когда представление завершено)

НО, когда я запускаю эти тесты сверху, я получаю это:

Test results

Есть несколько вещей, которые я не понимаю.

1) Почему при Scheduler.Immediate тесты занимают вдвое больше времени? Я думаю, я понимаю, почему Take(1) не имеет значения, но все же ...

2) Как использовать TestSchduler, как определить, на какой шаг шаг вперед?

Я заметил, что в тесте TestSchedulerExperiment_RunOnTest_ObserveOnTest я должен сделать дополнительные AdvanceBy(1), потому что это также наблюдатель. Поэтому, когда цепь длиннее, у нее больше наблюдателей, их действительно сложно сосчитать.

Это обычная практика scheduler.AdvanceBy(10000000000000);?

Я пытался создать расширение AdvanceUntil, но я знаю, что оно отстой по многим причинам (например, наблюдаемые в холодном режиме).

 public static void AdvanceUntil<TIgnore>(this TestScheduler s, IObservable<TIgnore> obs, double? advanceByMs = null)
        {
            var done = false;
            obs.Subscribe(_ => done = true, (ex) => done = true, () => done = true);

            while(!done)
                s.AdvanceByMs(advanceByMs ?? 100);
        }

Или, может быть, есть метод "сброса", которого я не знаю?

Кроме того, я научился ждать вещи внутри TestScheduler.With:

    [Test]
    public Task TestSchedulerExperiment_await()
    {
        return new TestScheduler().With(async s =>
        {
            var v = false;

            var t = Observable.Return(true).Delay(TimeSpan.FromSeconds(SecondsN), s)
                .Take(1) // without hits the test never ends
                .ToTask();
            s.AdvanceByMs(SecondsN * 1000);
            v = await t;

            Assert.IsTrue(v);
        });

но мне все еще нужно знать время.

А почему там должно быть Take(1)?

1 Ответ

0 голосов
/ 10 января 2019

scheduler.Start () выполняет все, что было запланировано, поэтому вам не нужен этот метод расширения.

Я рекомендую не смешивать async / await с Rx большую часть времени, особенно для функциональности, основанной на времени, что в основном является всеми вашими тестами из-за оператора задержки. В противном случае вы могли бы ждать минуты для завершения одного теста. Так что async / await не имеет смысла ни в одном из них.

Например, в сценарии, подобном вашему TestSchedulerExperiment, ожидающему теста, планировщик тестов вместе с подпиской - это все, что вам нужно. Этот тест просто стал бы:

// Passing test    
[Test]
public void TestSchedulerExperiment()
{
    new TestScheduler().With(s =>
    {
        var v = false;

        Observable
            .Return(true)
            .Delay(TimeSpan.FromSeconds(1), s)
            .Subscribe(_ => v = true);

        s.Start();
        Console.WriteLine("Scheduler clock value: {0}", s.Clock);

        Assert.True(v);
    });
}

Почему с Scheduler.Immediate тесты занимают в два раза больше времени?

Если вы действительно хотите вникнуть и посмотреть, что происходит под капотом, я настоятельно рекомендую это расширение шпиона Джеймса и добавление меток времени.

var t = Observable
    .Return(Unit.Default).Spy("Return")
    .Delay(TimeSpan.FromSeconds(2), RxApp.MainThreadScheduler).Spy("Delay")
    .ToTask();
await t;

// Partial output
Return: OnNext(()) on Thread: 1, 23:22:41.2631845
Delay: OnNext(()) on Thread: 1, 23:22:43.2891836
Return: OnCompleted() on Thread: 1, 23:22:43.2921808
Delay: OnCompleted() on Thread: 1, 23:22:45.2958130

Return использует ImmediateScheduler и, как вы, возможно, знаете, RxApp.MainThreadScheduler = ImmediateScheduler в модуле выполнения модульных тестов. Поскольку этот планировщик является синхронным, Уведомления о возврате и задержке должны ждать друг друга. Return не может запустить OnCompleted, пока Delay не сработает OnNext, а затем уведомление Delay OnCompleted задержится еще на 2 секунды.

Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...