可测试异步操作的体面模式?

时间:2011-11-07 21:03:41

标签: c# unit-testing design-patterns asynchronous

我很难找到一个简单灵活的模式,允许我在我的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);

唯一真正的先决条件是你需要在运行时知道你是否应该使用异步/同步。当我运行单元测试时,我发送一个模拟器,告诉我的代码同步运行,当应用程序实际运行时IsAsyncRequired默认为true;

反馈

3 个答案:

答案 0 :(得分:2)

我更愿意在一个单独的类中同步或异步执行代码的决定,这个类可以在这样的接口后面抽象:

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

可以在需要时注入实现ITaskExecuter的类的实例。 您可以为测试与生产方案注入不同的实例。

用法变为:

taskExecuter.ScheduleTask(
    continueCommand_Execute,
    continueCommand_PostExecute,
    continueCommand_Error);

在调用类中没有用于测试与生产的单独代码路径。

您可以选择编写以下测试:

  • 只需检查传递给任务执行者的正确操作,或
  • 配置任务执行器以同步执行操作 测试所需的结果,或
  • 同时做到这两点。

答案 1 :(得分:2)

我在当前的工作中做了一些非常简单的事情,但是现在无法复制/粘贴代码......

基本上我所做的是使用IWorker方法创建DoWork(Func<>)界面。

然后我创建了2个派生类,一个是“AsyncWorker”,另一个是“SyncWorker”。 SyncWorker只执行传入的Func(同步),而'AsyncWorker'是BackgroundWorker的包装器,它将传递的Func发送到BackgroundWorker,以便异步处理。

然后,我将ViewModel更改为传入IWorker。这会将依赖项解析移出ViewModel,因此您可以使用Dep。注射液。实用程序(我使用Unity和Constructor注入)。

由于我使用Unity,因此在我的单元测试配置中,我将IWorker映射到SyncWorker,在制作中我将IWorker映射到AsyncWorker

希望这是有道理的...我知道如果我手边有代码会更容易......

答案 2 :(得分:1)

考虑更改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();