c# - .NET 任务/TPL 测试和模拟? (或者用法不正确?)

标签 c# .net unit-testing mocking task-parallel-library

我有一个类,它从我的 WPF MVVM View 模型中抽象出在后台线程上执行长时间运行的方法。我还将此类接口(interface)和 IoC 注入(inject)到我的大多数 View 模型中。

public interface IAsyncActionManager : INotifyPropertyChanged
{        
    /// <summary>
    /// Sets and gets the IsBusy property.
    /// Changes to that property's value raise the PropertyChanged event. 
    /// </summary>
    bool IsBusy { get; }

    Task StartAsyncTask(Action backgroundAction);
 }

我的 View 模型以多种方式使用此类,例如:

private void LoadStuff()
{
    ActionManager.StartAsyncTask(() => { // Load stuff from database here });
}

在我的一些 XAML 中,我直接绑定(bind)到 IsBusy 属性:

<Grid Cursor="{Binding ActionManager.IsBusy, Converter={Converters:BusyMouseConverter}}">

无论如何 - 现在你已经了解了背景,我现在正在尝试做一些更奇特的事情:

private Task _saveChangesTask;
public void SaveChanges()
{
    if (_saveChangesTask != null && _saveChangesTask.Status != TaskStatus.Running)
        return;

    _saveChangesTask = ActionManager.StartAsyncTask(() =>
    { 
        // Save stuff here - slowly
    });
}

这很简单,因为我还通过一个 Command 对象将其连接起来,WPF 在其 View 中使用 CanExecute 等,但任务的“缓存”是为了使保存操作不会运行两次。

现在,解决问题,我想对这个逻辑进行单元测试 - 我该怎么做? 我尝试在测试中使用 TaskCompletionSource,但无法让我的任务进入“正在运行”状态...?

var tcs = new TaskCompletionSource<object>();
// tcs.Task.Status is now WaitingForActivation

// tcs.Task.Start(Synchronous.TaskScheduler); // Doesn't work - throws an Exception.

A.CallTo(() => mockAsyncActionManager.StartAsyncTask(A<Action>._, A<Action<Task>>._)).Returns(tcs.Task);

有人有线索吗?我可以这样做吗?

我有一个想法,我错误地使用了 TPL - 我不应该依赖任务状态 - 但不确定如何以另一种方式实现类似的事情(欢迎提出建议)。

干杯,

最佳答案

我相信这里的问题确实在于(正如你所说的)Status property 的检查。 .

TaskStatus enumeration表示 Task实例不仅具有运行/未运行的二进制状态,而且可以处于多种状态。

创建任务时,取决于 TaskScheduler ,它会将Task置于Running状态之前以下状态:

  • 已创建 - 任务已初始化,但尚未安排。
  • WaitingForActivation - 任务正在等待 .NET Framework 基础结构在内部激活和调度。
  • WaitingToRun - 任务已计划执行,但尚未开始执行。

这样,您对 RunningTaskStatus 的检查可能会失败,因为它处于上述状态之一。

我建议您只需检查任务的引用即可;如果为null,则创建一个新的Task,否则,直接返回。

这里的假设是对 SaveChanges 的调用意味着在对象上调用一次(或者在保存完成之前不执行任何操作)。

如果您要再次调用该方法(可能是因为已进行其他更改),您应该对 Task 进行延续,以设置对 Task< 的引用操作完成后, 变为 null。这样,当第二次调用 SaveChanges 时,对引用的检查就会成功。

顺便说一下,I've pointed out in the comments that you have a race condition 。如果您打算在延续中将 Task 的引用设置回 null,那么您需要以线程安全的方式执行检查和分配(因为延续将在另一个线程上运行),如下所示:

private Task _saveChangesTask;

// Used to synchronize access to _saveChangesTask
private readonly object _saveChangesTaskLock = new object();

public void SaveChanges()
{
    // Guard access to the reference.
    lock (_saveChangesTaskLock)
    {
        // Check and assign.
       if (_saveChangesTask != null) return;

        _saveChangesTask = ActionManager.StartAsyncTask(() =>
        { 
            // Save stuff here - slowly

            // Done saving stuff here - slowly
            // (BTW, is the above a reference from "True Lies"?)
            // Remove reference to task.  This is on another thread
            // so using a lock again is ok.
            // Guard access to the reference.
            lock (_saveChangesTaskLock) _saveChangesTask = null;
        });
    }
}

关于c# - .NET 任务/TPL 测试和模拟? (或者用法不正确?),我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/9486076/

相关文章:

c# - 在具有动态参数的 MvcSiteMapProvider 中为 DynamicNode 创建子节点

.net - .NET中的生产者消费者链和线程调度

javascript - 特定组件中的 'expecting to render' 测试代码覆盖率较低

python - 跨应用持久化测试数据

javascript - 使用 Jest 监视启动的模块

c# - 为什么我不能在 out 参数中传递一个未分配的对象变量然后分配它

c# - 在 DbSet<T> 上使用 LINQ 扩展方法时出现不明确的调用

c# - 内存是否在 Form.close() 上释放?

c# - 是否有支持从右到左语言的 C# Winforms 的 HtmlEditor?

c# - 在 .NET 4.5 中创建配置节处理程序错误时发生错误