Блокировка асинхронных методов с помощью TestScheduler в ReactiveUI - PullRequest
0 голосов
/ 11 декабря 2018

Я пытаюсь использовать в тесте планировщик реактивного тестирования при помощи асинхронного метода.

Тест зависает, когда ожидается асинхронный вызов.

Основная причина, по-видимому,команда, которая ожидается в асинхронном методе.

    [Fact]
    public async Task Test()
        => await new TestScheduler().With(async scheduler =>
        {
            await SomeAsyncMethod();

            // *** execution never gets here
            Debugger.Break();
        });

    private async Task SomeAsyncMethod()
    {
        var command = ReactiveCommand.CreateFromTask(async () =>
        {
            await Task.Delay(100);
        });

        // *** this hangs
        await command.Execute();
    }

Как я могу выполнить асинхронный вызов в сочетании с планировщиком теста, который не блокируется?

Я использую реактивную 9.4.1

РЕДАКТИРОВАТЬ:

Я попробовал метод WithAsync(), как предложено в ответе Funks, но поведение то же самое.

Ответы [ 3 ]

0 голосов
/ 16 декабря 2018

Вы пытались использовать ConfigureAwait (false) при вызове вложенного метода?

 [Fact]
    public async Task Test()
        => await new TestScheduler().With(async scheduler =>
        {
            // this hangs
            await SomeAsyncMethod().ConfigureAwait(false);

            // ***** execution will never get to here
            Debugger.Break();
        }
0 голосов
/ 23 декабря 2018

Пожалуйста, попробуйте использовать .ConfigureAwait(false) на всех ваших асинхронных методах.Это обеспечит вам неблокирующее поведение.

[Fact]
public async Task Test()
    => await new TestScheduler().With(async scheduler =>
    {
        await SomeAsyncMethod().ConfigureAwait(false);

        // *** execution never gets here
        Debugger.Break();
    }).ConfigureAwait(false);

private async Task SomeAsyncMethod()
{
    var command = ReactiveCommand.CreateFromTask(async () =>
    {
        await Task.Delay(100).ConfigureAwait(false);
    }).ConfigureAwait(false);

    // *** this hangs
    await command.Execute();
}

Еще один способ проверить, связана ли проблема с ConfigureAwait, - это перенести ваш проект в Asp.Net Core и протестировать его там.

Ядру Asp.net не нужно использовать ConfigureAwait для предотвращения этой проблемы блокировки.

Проверьте это для справки

0 голосов
/ 16 декабря 2018

Как я могу сделать асинхронный вызов в сочетании с планировщиком испытаний?

Короче говоря

command.Execute() - наблюдаемая холодная погода,Вам нужно подписаться на него, вместо того, чтобы использовать await.

Учитывая ваш интерес к TestScheduler, я полагаю, вы хотите проверить что-то, вовлекающее время.Однако из раздела Когда я должен заботиться о планировании раздела :

потоками, созданными с помощью «new Thread ()» или «Task.Run», нельзя управлять в блокеtest.

Итак, если вы хотите проверить, например, что ваш Task завершается в течение 100 мс, вам придется подождать, пока завершится асинхронный метод.Конечно, это , а не , для которого предназначен тип теста TestScheduler.

Несколько более длинная версия

Цель TestScheduler для проверки рабочих процессов, приводя вещи в движение и проверяя состояние в определенные моменты времени.Поскольку мы можем манипулировать временем только на TestScheduler, вы, как правило, предпочитаете не ждать завершения реального асинхронного кода, поскольку нет способа быстрой пересылки реальных вычислений или операций ввода-вывода.Помните, речь идет о проверке рабочих процессов: vm.A имеет новое значение в 20 мс, поэтому vm.B должно иметь новое значение в 120 мс, ...

Так как же вы можете проверить SUT?

1 \ Вы можете смоделировать асинхронный метод, используя scheduler.CreateColdObservable

public class ViewModelTests
{
    [Fact]
    public void Test()
    {
        string observed = "";

        new TestScheduler().With(scheduler =>
        {
            var observable = scheduler.CreateColdObservable(
                scheduler.OnNextAt(100, "Done"));

            observable.Subscribe(value => observed = value);
            Assert.Equal("", observed);

            scheduler.AdvanceByMs(99);
            Assert.Equal("", observed);

            scheduler.AdvanceByMs(1);
            Assert.Equal("Done", observed);
        });
    }
}

Здесь мы в основном заменили command.Execute() на var observable, созданный в scheduler.

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

Ссылка:

2 \ Вы можете сослаться на IScheduler явно

a) Использование планировщиков, предоставленных RxApp

public class MyViewModel : ReactiveObject
{
    public string Observed { get; set; }

    public MyViewModel()
    {
        Observed = "";

        this.MyCommand = ReactiveCommand
            .CreateFromTask(SomeAsyncMethod);
    }

    public ReactiveCommand<Unit, Unit> MyCommand { get; }

    private async Task SomeAsyncMethod()
    {
        await RxApp.TaskpoolScheduler.Sleep(TimeSpan.FromMilliseconds(100));
        Observed = "Done";
    }
}

public class ViewModelTests
{
    [Fact]
    public void Test()
    {
        new TestScheduler().With(scheduler =>
        {
            var vm = new MyViewModel();

            vm.MyCommand.Execute().Subscribe();
            Assert.Equal("", vm.Observed);

            scheduler.AdvanceByMs(99);
            Assert.Equal("", vm.Observed);

            scheduler.AdvanceByMs(1);
            Assert.Equal("Done", vm.Observed);
        });
    }
}

Примечание

  • CreateFromTaskсоздает ReactiveCommand с логикой асинхронного выполнения.Нет необходимости определять метод Test как асинхронный или ожидать TestScheduler.

  • В области действия With метода расширения RxApp.TaskpoolScheduler = RxApp.MainThreadScheduler = new TestScheduler().

б) Управление собственными планировщиками с помощью инжектора конструктора

public class MyViewModel : ReactiveObject
{
    private readonly IScheduler _taskpoolScheduler;
    public string Observed { get; set; }

    public MyViewModel(IScheduler scheduler)
    {
        _taskpoolScheduler = scheduler;
        Observed = "";

        this.MyCommand = ReactiveCommand
            .CreateFromTask(SomeAsyncMethod);
    }

    public ReactiveCommand<Unit, Unit> MyCommand { get; }

    private async Task SomeAsyncMethod()
    {
        await _taskpoolScheduler.Sleep(TimeSpan.FromMilliseconds(100));
        Observed = "Done";
    }
}

public class ViewModelTests
{
    [Fact]
    public void Test()
    {
        new TestScheduler().With(scheduler =>
        {
            var vm = new MyViewModel(scheduler); ;

            vm.MyCommand.Execute().Subscribe();
            Assert.Equal("", vm.Observed);

            scheduler.AdvanceByMs(99);
            Assert.Equal("", vm.Observed);

            scheduler.AdvanceByMs(0);
            Assert.Equal("Done", vm.Observed);
        });
    }
}

Ссылка:

Давайте сравним ряды с другой цитатой из Haacked:

К сожалению, и этоСледующий пункт важен, TestScheduler не распространяется на реальную жизнь, поэтому ваши махинации ограничены вашим асинхронным реактивным кодом.Таким образом, если вы вызовете Thread.Sleep(1000) в своем тесте, этот поток действительно будет заблокирован на секунду.Но что касается планировщика испытаний, времени не прошло.

...