体面的可测试异步操作模式
本文关键字:异步操作 模式 测试 体面 | 更新日期: 2023-09-27 18:13:40
我很难找到一个简单,灵活的模式,允许我在我的ViewModels中编写代码,在运行时将异步运行,但在测试时同步运行。这是我想出来的,有人有什么建议吗?这是一条好的道路吗?是否存在更好的现有模式?
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。当我运行单元测试时,我发送一个模拟,告诉我的代码同步运行,当应用程序实际运行时,isasyncrerequired默认为true;
反馈?
我更愿意将是否同步或异步执行代码的决定封装在一个单独的类中,该类可以在接口后面抽象,例如:
public interface ITaskExecuter
{
void ScheduleTask(
Action executeAction,
Action postExecuteAction,
Action<Exception> onException);
}
可以在需要的地方注入实现ITaskExecuter
的类的实例。您可以为测试和生产场景注入不同的实例。
用法就变成:
taskExecuter.ScheduleTask(
continueCommand_Execute,
continueCommand_PostExecute,
continueCommand_Error);
在调用类中没有单独的代码路径,用于测试和生产。
您可以选择编写以下测试:
- 检查传递给任务执行器的操作是否正确,或者
- 配置任务执行器以同步执行操作测试所需的结果,或
- 。
我在目前的工作中做了一些非常类似的事情,但现在无法获得代码复制/粘贴…
基本上我所做的是创建一个IWorker
接口,用DoWork(Func<>)
方法。
然后我创建了2个派生类,一个'AsyncWorker'和一个'SyncWorker'。SyncWorker只是执行在Func
传递(同步),而'AsyncWorker'是一个围绕BackgroundWorker
的包装器,它将Func
传递给BackgroundWorker进行异步处理。
然后,我改变了我的ViewModel有一个IWorker
传入。这就把依赖解析移出了ViewModel,这样你就可以使用Dep. Inj。
由于我使用Unity,在我的单元测试配置中,我将IWorker
映射到SyncWorker
,并在生产中将IWorker
映射到AsyncWorker
。
希望这是有意义的…我知道如果我手头有代码会更容易…
考虑更改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();