Подтвердить что ты не робот

Как написать модульные тесты с помощью TPL и TaskScheduler

Представьте себе такую ​​функцию:

private static ConcurrentList<object> list = new ConcurrentList<object>();
public void Add(object x)
{
   Task.Factory.StartNew(() =>
   {
      list.Add(x); 
   }
}

Мне все равно, КОГДА именно в этот список добавляется шпага, но мне нужно, чтобы он был добавлен в конце (очевидно;))

Я не вижу способ правильно убрать такие вещи, не возвращая никакого обработчика обратного вызова или sth. и, следовательно, добавление логики, которая не требуется для программы

Как вы это сделаете?

4b9b3361

Ответ 1

Один из способов сделать это - настроить ваш тип таким образом, чтобы он выполнял экземпляр TaskScheduler.

public MyCollection(TaskScheduler scheduler) {
  this.taskFactory = new TaskFactory(scheduler);
}

public void Add(object x) {
  taskFactory.StartNew(() => {
    list.Add(x);
  });
}

Теперь в вашем модульном тестировании вы можете создать тестовую версию TaskScheduler. Это абстрактный класс, который предназначен для конфигурирования. Простая функция расписания добавляет элементы в очередь, а затем добавляет функцию, чтобы вручную выполнять все элементы очереди "сейчас". Тогда ваш unit test может выглядеть следующим образом:

var scheduler = new TestableScheduler();
var collection = new MyCollection(scehduler);
collection.Add(42);
scheduler.RunAll();
Assert.IsTrue(collection.Contains(42));

Пример реализации TestableScehduler

class TestableScheduler : TaskScheduler {
  private Queue<Task> m_taskQueue = new Queue<Task>();

  protected override IEnumerable<Task> GetScheduledTasks() {
    return m_taskQueue;
  }

  protected override void QueueTask(Task task) {
    m_taskQueue.Enqueue(task);
  }

  protected override bool TryExecuteTaskInline(Task task, bool taskWasPreviouslyQueued) {
    task.RunSynchronously();
  }

  public void RunAll() {
    while (m_taskQueue.Count > 0) {
      m_taskQueue.Dequeue().RunSynchronously();
    }
  }
}

Ответ 2

Решение, которое работало для меня, заключалось в том, чтобы отправить TaskScheduler в зависимость от кода, который я хочу использовать unit test (например,

MyClass(TaskScheduler asyncScheduler, TaskScheduler guiScheduler)

Где asyncScheduler используется для планирования задач, выполняемых в рабочих потоках (блокирование вызовов), а guiScheduler используется для планирования задач, которые должны выполняться в графическом интерфейсе (неблокирующие вызовы).

В unit test я бы тогда ввел конкретные планировщики, то есть экземпляры CurrentThreadTaskScheduler. CurrentThreadTaskScheduler - это реализация планировщика, которая сразу запускает задачи, а не ставит их в очередь.

Вы можете найти реализацию в Microsoft Samples for Parallel Programming здесь.

Я скоро вставлю код:

/// <summary>Provides a task scheduler that runs tasks on the current thread.</summary>
public sealed class CurrentThreadTaskScheduler : TaskScheduler
{
    /// <summary>Runs the provided Task synchronously on the current thread.</summary>
    /// <param name="task">The task to be executed.</param>
    protected override void QueueTask(Task task)
    {
        TryExecuteTask(task);
    }

    /// <summary>Runs the provided Task synchronously on the current thread.</summary>
    /// <param name="task">The task to be executed.</param>
    /// <param name="taskWasPreviouslyQueued">Whether the Task was previously queued to the scheduler.</param>
    /// <returns>True if the Task was successfully executed; otherwise, false.</returns>
    protected override bool TryExecuteTaskInline(Task task, bool taskWasPreviouslyQueued)
    {
        return TryExecuteTask(task);
    }

    /// <summary>Gets the Tasks currently scheduled to this scheduler.</summary>
    /// <returns>An empty enumerable, as Tasks are never queued, only executed.</returns>
    protected override IEnumerable<Task> GetScheduledTasks()
    {
        return Enumerable.Empty<Task>();
    }

    /// <summary>Gets the maximum degree of parallelism for this scheduler.</summary>
    public override int MaximumConcurrencyLevel { get { return 1; } }
}

Ответ 3

Как сделать публичное свойство для списка?

public ConcurrentList<object> List { get; set; }

или, возможно, сделать это публичным полем, когда в сборке DEBUG:

#if DEBUG
public static ConcurrentList<object> list = new ConcurrentList<object>();
#else
private static ConcurrentList<object> list = new ConcurrentList<object>();
#endif

Ответ 4

Мой коллега и я создаем модульную систему тестирования, которая рассматривает тестирование TPL и Rx, и есть класс, который вы могли бы использовать для замены TaskScheduler по умолчанию в сценарии тестирования, так что вам не нужно изменять подписи методов. Сам проект еще не опубликован, но вы можете просмотреть файл здесь:

https://github.com/Testeroids/Testeroids/blob/master/solution/src/app/Testeroids/TplTestPlatformHelper.cs

Работа по настройке планировщика задач выполняется в TplContextAspectAttribute.cs.

Ответ 5

Для, по крайней мере, большинства простых случаев, мне нравится использовать "истекающее" утверждение для такого рода вещей. например:.

YourCollection sut = new YourCollection();

object newItem = new object();
sut.Add(newItem);

EventualAssert.IsTrue(() => sut.Contains(newItem), TimeSpan.FromSeconds(2));

где EventualAssert.IsTrue() выглядит примерно так:

public static void IsTrue(Func<bool> condition, TimeSpan timeout)
{
    if (!SpinWait.SpinUntil(condition, timeout))
    {
        Assert.IsTrue(condition());
    }
}

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