5

Как я могу выполнить компонент тестирования, который ожидает Task.Delay, и не должен ждать его.Как я могу установить код проверки, содержащий Task.Delay?

Например,

public void Retry() 
{ 
    // doSomething(); 
    if(fail) 
     await Task.Delay(5000); 
} 

мне нужно проверить ветку отказа, но я не хочу, чтобы мой тест ждать 5 секунд.

Есть ли что-нибудь вроде rx virtual time-based scheduling в параллельной библиотеке задач?

ответ

6

Таймер - это еще одна форма внешней зависимости. Единственное различие заключается в том, что это зависит от часов вместо базы данных. И, как и любая другая внешняя зависимость, он сильно тестирует тестирование; Таким образом, ответ заключается в том, чтобы улучшить дизайн до тех пор, пока его легко проверить.

Следуя принципу впрыскивания зависимостей, вы должны передать таймер в конструктор объекта, который позволяет вам вводить тестовый заглушку вместо того, чтобы полагаться на реальный таймер.

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

1

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

Существует blog на асинхронном и TDD и еще один here, хотя эти более точки, что может пойти не так, в целом с асинхронным кодом, а не specifcally дело с Task.Delay.

4

Есть ли что-нибудь вроде rx виртуального планирования на основе времени, доступного в параллельной библиотеке задач?

Нет, не существует. Ваши варианты должны либо определить «сервис таймера», который вы можете реализовать с помощью тестового заглушки, либо с помощью Microsoft Fakes для перехвата вызова до Task.Delay. Я предпочитаю последнее, но это только вариант на VS Ultimate.

+0

Coul Вы предоставляете более подробную информацию о том, как можно использовать последний вариант? –

+0

Я еще не написал запись в блоге, но [вот какой код] (https://github.com/StephenCleary/Presentations/tree/ba85a29179091e86743ba0f45c01a1d93fb43f4b/Async%20Unit%20Testing/Demos/Timing) –

+0

как вы добавьте ссылку на mscorlib.dll, чтобы создать фальшивку? – Choco

2

1) Определите свою собственную реализацию Task.Delay.

public static class TaskEx 
{ 
    private static bool _shouldSkipDelays; 

    public static Task Delay(TimeSpan delay) 
    { 
     return _shouldSkipDelays ? Task.FromResult(0) : Task.Delay(delay); 
    } 

    public static IDisposable SkipDelays() 
    { 
     return new SkipDelaysHandle(); 
    } 

    private class SkipDelaysHandle : IDisposable 
    { 
     private readonly bool _previousState; 

     public SkipDelaysHandle() 
     { 
      _previousState = _shouldSkipDelays; 
      _shouldSkipDelays = true; 
     } 

     public void Dispose() 
     { 
      _shouldSkipDelays = _previousState; 
     } 
    } 
} 

2) Использование TaskEx.Delay вместо Task.Delay везде в вашем коде.

3) В ваших тестах используют TaskEx.SkipDelays:

[Test] 
public async Task MyTest() 
{ 
    using (TaskEx.SkipDelays()) 
    { 
     // your code that will ignore delays 
    } 
} 
+0

Я думаю, что это лучший вариант, но с возможной лучшей реализацией. Если вы используете Rx, вы также можете реализовать этот класс, используя IScheduler.Schedule (Timespan). Это позволит вам использовать планировщик тестов Rx для фактического тестирования тайм-аутов, если вам нужно. И их реализации в основном одинаковы. – Natan

0

на основе АЛЕКСЕЙ anwser создать оболочку для Task.Delay, вот как создать задачу.Задержка, которая использует IScheduler Реактивных расширений, так что вы можете использовать виртуальное время, чтобы проверить задержки:

using System; 
using System.Reactive.Linq; 
using System.Reactive.Threading.Tasks; 
using System.Threading; 
using System.Threading.Tasks; 

public static class TaskEx 
{ 
    public static Task Delay(int millisecondsDelay, CancellationToken cancellationToken = default(CancellationToken)) 
    { 
     #if TEST 
     return Observable.Timer(TimeSpan.FromMilliseconds(millisecondsDelay), AppContext.DefaultScheduler).ToTask(cancellationToken); 
     #else 
     return Task.Delay(millisecondsDelay, cancellationToken); 
     #endif 
    } 
} 

Это использует компиляцию символы, чтобы полностью избежать Rx, если вы не модульное тестирования.

AppContext - это только объект контекста, который ссылается на ваши планировщики. В ваших тестах вы можете установить AppContext.DefaultScheduler = testScheduler, и задержка будет вызвана виртуальным планировщиком времени.

Однако есть оговорка. TestScheduler является синхронным, поэтому вы не можете запустить задачу и использовать TaskEx.Delay внутри, потому что планировщик будет продвигаться до запланированной задачи.

var scheduler = new TestScheduler(); 
AppContext.DefaultScheduler = scheduler; 

Task.Run(async() => { 
    await TaskEx.Delay(100); 
    Console.Write("Done"); 
}); 

/// this won't work, Task.Delay didn't run yet. 
scheduler.AdvanceBy(1); 

Вместо этого, вы должны всегда запускать задачу с помощью Observable.Start(task, scheduler), поэтому задача выполняется в следующем порядке:

var scheduler = new TestScheduler(); 
AppContext.DefaultScheduler = scheduler; 

Observable.Start(async() => { 
    await TaskEx.Delay(100); 
    Console.Write("Done"); 
}, scheduler); 

/// this runs the code to schedule de delay 
scheduler.AdvanceBy(1); 

/// this actually runs until the delay is complete 
scheduler.AdvanceBy(TimeSpan.FromMilliseconds(100).Ticks); 

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

0

Как упоминалось Джоном Детером, это внешняя зависимость от часов компьютеров (так же, как если бы вам нужно было получить текущее время, хотя его легко назвать DateTime.UtcNow, это все еще зависимость).

Однако, это специальные зависимости, так как вы можете предоставить значение по умолчанию, которое всегда будет работать (Task.Delay или DateTime.UtcNow). Вы можете, следовательно, имеют свойство, чтобы сделать это:

private Func<int, Task> delayer = millisecondsDelay => Task.Delay(millisecondsDelay); 
    public Func<int, Task> Delayer 
    { 
     get { return delayer; } 
     set { delayer = value ?? (millisecondsDelay => Task.Delay(millisecondsDelay)) } 
    } 

Используя это, вы можете заменить вызов Task.Delay в ваших тестах.

sut.Delayer = _ => Task.CompletedTask; 

Чистый способ сделать это, конечно, объявить интерфейс и получить его через ваш конструктор.

Смежные вопросы