Достойный образец для тестируемых асинхронных операций? - PullRequest
2 голосов
/ 08 ноября 2011

У меня были проблемы с поиском простого гибкого шаблона, позволяющего мне писать в моих моделях ViewModel код, который во время выполнения будет выполняться асинхронно, но во время тестирования - синхронно.Это то, что я придумал - у кого-нибудь есть предложения?Это хороший путь, чтобы пойти вниз?Существуют ли лучше существующие шаблоны?

Определение LongRunningCall:

public class LongRunningCall
{
    public Action ExecuteAction { get; set; }
    public Action PostExecuteAction { get; set; }

    public LongRunningCall(Action executeAction = null, Action postExecuteAction = null)
    {
        ExecuteAction = executeAction;
        PostExecuteAction = postExecuteAction;
    }

    public void Execute(Action<Exception> onError)
    {
        try
        {
            ExecuteAction();
            PostExecuteAction();
        }
        catch (Exception ex)
        {
            if (onError == null)
                throw;
            onError(ex);
        }
    }

    public void ExecuteAsync(TaskScheduler scheduler, Action<Exception> onError)
    {
        var executeTask = Task.Factory.StartNew(ExecuteAction);
        var postExecuteTask = executeTask.ContinueWith((t) =>
            {
                if (t.Exception != null)
                    throw t.Exception;
                PostExecuteAction();
            }, scheduler);
        if (onError != null)
            postExecuteTask.ContinueWith((t) => { onError(t.Exception); });
    }
}

Использование:

var continueCall = new LongRunningCall(continueCommand_Execute, continueCommand_PostExecute);
if (svc.IsAsyncRequired)
   continueCall.ExecuteAsync(TaskScheduler.FromCurrentSynchronizationContext(), continueCommand_Error);
else
   continueCall.Execute(continueCommand_Error);

единственное реальное предварительное условие - это то, что вам нужно знать во время выполнения, если вы должны использовать async / sync.Когда я запускаю свои модульные тесты, я отправляю макет, который говорит, что мой код запускается синхронно, когда приложение на самом деле выполняет IsAsyncRequired по умолчанию true;

Feedback?

Ответы [ 3 ]

2 голосов
/ 08 ноября 2011

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

public interface ITaskExecuter
{
    void ScheduleTask(
        Action executeAction,
        Action postExecuteAction,
        Action<Exception> onException);
}

Экземпляр класса, реализующего ITaskExecuter, может быть вставлен в случае необходимости. Вы можете вводить разные экземпляры для тестирования по сравнению с производственными сценариями.

Использование становится:

taskExecuter.ScheduleTask(
    continueCommand_Execute,
    continueCommand_PostExecute,
    continueCommand_Error);

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

У вас есть возможность написать тесты, которые:

  • просто проверьте правильность действий, переданных исполнителю задачи, или
  • настройка исполнителя задачи для синхронного выполнения действия и тест на желаемый результат или
  • сделать оба.
2 голосов
/ 08 ноября 2011

Я сделал что-то очень похожее на своей нынешней работе, но не могу получить код, чтобы скопировать / вставить его прямо сейчас ...

В основном я создал интерфейс IWorkerDoWork(Func<>) методом.

Затем я создал 2 производных класса, один «AsyncWorker» и один «SyncWorker».SyncWorker просто выполняет переданные в Func (синхронно), а «AsyncWorker» является оберткой вокруг BackgroundWorker, которая отправляет переданные Func в BackgroundWorker для асинхронной обработки.

Затем я изменил свою ViewModel, чтобы передать IWorker. Это перемещает разрешение зависимостей из ViewModel, так что вы можете использовать Dep.Inj.утилита (я использую Unity и Constructor инъекцию).

Поскольку я использую Unity, в конфигурации моего модульного теста я затем сопоставляю IWorker с SyncWorker, а в производственной среде я сопоставляю IWorker с AsyncWorker.

Надеюсь, что это имеет смысл ... Я знаю, было бы проще, если бы у меня был код под рукой ...

1 голос
/ 08 ноября 2011

Рассмотрите возможность изменения ExecuteAsync, чтобы он возвращал Task:

public Task ExecuteAsync(TaskScheduler scheduler, Action<Exception> onError)

Итак, в рабочем коде я бы назвал это так:

longRunningCall.ExecuteAsync(
    TaskScheduler.FromCurrentSynchronizationContext(),
    continueCommand_Error);

Но в модульных тестах я бы подождал, пока задача фактически завершится:

var task = longRunningCall.ExecuteAsync(
    TaskScheduler.FromCurrentSynchronizationContext(),
    continueCommand_Error);
task.Wait();
Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...