2015-03-06 2 views
1

Я только начал использовать Rhino Mocks, которые проверяют мои асинхронные методы в коде. Но в то время как обычно код работает просто отлично, но в то время как издевательские тесты с Rhino Mocks есть некоторые странные проблемы async.асинхронное тестирование задачи

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

public async Task MyTask() 
{ 
    List<Task<List<int>>> numberTasks = new List<Task<List<int>>>(); 

    foreach (var numberGenerator in numberGenerators) 
    { 
     // GetNumbers is an 'async Task<List<int>>' 
     // As you can see I don't use 'await' so it should just add it and go on. 
     numberTasks.Add(numberGenerator.GetNumbers()); 
    } 

    try 
    { 
     await Task.WhenAll(numberTasks); 
    } 
    catch (Exception e) 
    { 
     // Log exception if something happens at 'GetNumbers' (like time-out or whatever) 
    } 

    var numbers = new List<int>(); 
    numbers.AddRange(numberTasks.Where(task => task.Status == TaskStatus.RanToCompletion).SelectMany(task => task.Result)); 

    // Do something with the numbers 
} 

испытывается:

numberGenerator.Expect(x => x.GetNumbers()) 
    .WhenCalled(x => Thread.Sleep(10000)) 
    .Return(Task.FromResult(numbers)); 

Я использую два генератора на 'mocked' в приведенном выше коде. Теперь, если я запустил код с «немытыми» объектами, numberTasks.Add просто добавляет задачу и перемещается. Но когда я издеваюсь над ним с WhenCalled sleep, он ждет 10 секунд, прежде чем перейти к следующему в цикле foreach.

Как я могу изменить свой макет, что он действует как обычный async Task, который занимает 10 секунд?

Вопрос о бонусе: Может быть, можно сделать то же самое с .Throw(), поэтому я могу подождать 10 секунд, прежде чем выбрасывать исключение.

Update: Хотя я думал, что следующий будет решить эту проблему:

numberGenerator.Expect(x => x.GetNumbers()) 
    .Return(Task.Delay(10000).ContinueWith(x => numbers)); 

Это, кажется, не так. Задача начнет отсчет с момента создания этого mock. Поэтому, если я создаю этот макет, подождите 10 секунд, а затем запустите тест, это будет сделано за 0 секунд.

Так что мой вопрос остается, как я могу проверить асинхронный метод с delay перед возвратом результата.

+0

Пожалуйста, см [ «СЛЕДУЕТ вопросы включают в себя„метки“в их названиях?»] (Http: //meta.stackexchange .com/questions/19190/should-questions-include-tags-in-their-titles), где консенсус «нет, они не должны»! –

+1

На самом деле оригинальная тема была в порядке, http://meta.stackexchange.com/help/tagging «Единственный раз, когда вы должны использовать теги в своем названии, это когда они органичны для разговорного тона названия». – Tseng

+0

@ На самом деле, даже с тегами исходное название было не в порядке - ни в коем случае ... Это было просто уловка некоторых терминов и отсутствие четкого вопроса/названия вообще. См. Http://stackoverflow.com/help/how-to-ask. * Представьте, что вы разговариваете с занятым коллегой и должны подвести весь свой вопрос в одном предложении * –

ответ

2

Асинхронный метод работает синхронно до достижения await. Это означает, что если вы подождете перед возвратом задачи в издеваемую операцию, это ожидание будет синхронным. То, что вы хотите сделать, - это немедленно вернуть задачу, которая завершается через некоторое время.

Вы можете сделать это с Task.Delay:

numberGenerator.Expect(x => x.GetNumbers()). 
     Return(Task.Delay(10000).ContinueWith(t => numbers)); 

Task.ContinueWith используется для добавления фактического возвращаемого значения после задержки завершения.

Если вы хотите исключить исключение вместо возврата значения, это тоже можно сделать в продолжении.

С Task.Delay(10000).ContinueWith(t => numbers) оценивается при создании макета, каждый вызов возвращает ту же задачу (которая будет завершена через 10 секунд после создания макета).

Если вам нужно создать новую задачу каждый раз, когда вам нужно передать делегат. Return не принимает делегата, так что вы должны использовать WhenCalled:

numberGenerator.Expect(x => x.GetNumbers()). 
     WhenCalled(mi => mi.ReturnValue = Task.Delay(10000).ContinueWith(x => numbers)); 

Теперь, в то время как WhenCalled используется для установки значения, возвращаемого с помощью Return по-прежнему требуется in order to determine the type of the return result. Это не имеет значения, какое значение он получает, как возвращаемое значение по-прежнему будет установлено WhenCalled:

numberGenerator.Expect(x => x.GetNumbers()). 
    WhenCalled(mi => mi.ReturnValue = Task.Delay(10000).ContinueWith(x => numbers)). 
    Return(Task.FromResult(new List<int>())); 
+0

Одна странная вещь, если я добавлю этот код, который вы написали, и вместо добавления задач в foreach я жду их в foreach. Это все еще сделано примерно через 10 секунд. Похоже, что он просто переходит к следующему в foreach, хотя есть ожидание. – Julian

+0

@ Джулиан. Я так не думаю. Задача, которую вы создаете, перед тем как перейти к 'Return' - та же задача, которая будет завершена через 10 секунд. Когда первый элемент вернет его, он будет ждать 10 секунд, но второй будет двигаться, потому что задача уже завершена. Если вы хотите вернуть новую задачу каждый раз, когда вам понадобится 'Return', который принимает делегата или что-то подобное. – i3arnon

+0

после тестирования alot проблема не кажется исправленной, потому что 'Task.Delay' запускается в момент создания' mock'. – Julian

1

Лично я не работал с rhino mock, поэтому не знаю, поддерживает ли он асинхронные вызовы методов.

Но Thread.Sleep(10000) - это синхронный код. Если вы хотите ждать асинхронно, вы должны использовать Task.Delay(10000).

Или альтернативно использовать Task.Run(() => Thread.Sleep(10000)), хотя, Task.Delay(10000) является предпочтительным способом и наилучшей практикой.

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