2015-03-06 7 views
1

Je viens de commencer à utiliser Rhino Mocks qui testent mes méthodes asynchrones dans le code. Mais alors que normalement le code fonctionne tout simplement parfait, mais tout en se moquant des tests avec Rhino Mocks, il y a quelques problèmes étranges async.test de tâche asynchrone

Disons que je veux tester le code suivant qui appelle plusieurs numberGenerators que tout se déroulera en même temps et attendre jusqu'à ce qu'ils soient tous fait:

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 
} 

est testée avec:

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

J'utilise deux des générateurs sur 'mocked' dans le code ci-dessus. Maintenant, si je cours le code avec des objets 'non-mocked' numberTasks.Add ajoute juste la tâche et continue. Mais quand je me moque du WhenCalled sleep, il attend 10 secondes avant de passer au suivant dans la boucle foreach.

Comment puis-je modifier mon simulacre pour qu'il se comporte comme un async Task normal qui prend 10 secondes pour terminer?

Question supplémentaire: Je veux pouvoir faire la même chose avec .Throw(), donc je peux attendre 10 secondes avant de lancer une exception.

Mise à jour: Bien que je pensais que ce qui suit fixerait le problème:

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

Cela ne semble pas être le cas. Le Task.Delay commence à décompter au moment où ce mock est créé. Donc, si je crée ce simulacre, attendez 10 secondes, puis exécutez le test, il sera fait dans 0 secondes.

Donc ma question demeure, comment puis-je tester une méthode asynchrone avec un delay avant de renvoyer le résultat.

+0

S'il vous plaît voir [ « des questions DEVRAIENT inclure des « tags » dans leurs titres? »] (Http: //meta.stackexchange .com/questions/19190/should-questions-include-tags-in-its-titles), où le consensus est "non, ils ne devraient pas"! –

+1

En fait, le sujet original était correct, http://meta.stackexchange.com/help/tagging "La seule fois où vous devriez utiliser des balises dans votre titre, c'est quand elles sont organiques au ton de la conversation du titre." – Tseng

+0

@Tseng en fait, même avec des tags, le titre original n'était pas correct - en aucun cas ... C'était juste une caténation de certains termes, et il manquait une question/un titre clair. S'il vous plaît voir: http://stackoverflow.com/help/how-to-ask. * Faites comme si vous parliez à un collègue occupé et que vous deviez résumer toute votre question en une phrase * –

Répondre

2

Une méthode asynchrone est exécutée de manière synchrone jusqu'à ce qu'un await soit atteint. Cela signifie que si vous attendez avant de renvoyer une tâche dans l'opération simulée, cette attente sera synchrone. Ce que vous voulez faire est de retourner une tâche immédiatement qui se termine après un certain temps.

Vous pouvez le faire avec Task.Delay:

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

Task.ContinueWith est utilisé pour ajouter la valeur de rendement réel après le retard est terminé.

Si vous souhaitez lancer une exception au lieu de renvoyer une valeur, cela peut également être fait dans la suite.

Puisque Task.Delay(10000).ContinueWith(t => numbers) est évalué lorsque vous créez le simulacre, chaque appel renverrait la même tâche (qui sera terminée 10 secondes après la création du simulacre).

Si vous devez créer une nouvelle tâche chaque fois que vous devez passer un délégué. Return n'accepte pas un délégué si vous devez utiliser WhenCalled:

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

Maintenant, alors que WhenCalled est utilisé pour définir la valeur de retour à l'aide Return est toujours nécessaire in order to determine the type of the return result. Peu importe quelle valeur il obtient que la valeur de retour sera toujours fixé par WhenCalled:

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

Une chose étrange, si j'ajoute ce code que vous avez écrit et au lieu d'ajouter des tâches dans le foreach je les attends dans le foreach. C'est encore fait dans environ 10 secondes. On dirait qu'il saute juste au suivant dans l'événement de foreachhough il y a un attente. – Julian

+0

@Julian Je ne pense pas. La tâche que vous créez avant de passer à 'Return' est la même tâche qui se terminera dans 10 secondes. Lorsque le premier élément le renvoie, il attendra 10 secondes mais le second se poursuivra car la tâche est déjà terminée. Si vous voulez renvoyer une nouvelle tâche chaque fois que vous avez besoin d'un 'Return' qui accepte un délégué ou quelque chose de similaire. – i3arnon

+0

après avoir testé beaucoup le problème ne semble pas être résolu car le 'Task.Delay' commence à s'exécuter au moment où' mock' est fait. – Julian

1

Personnellement, je n'ai pas travaillé avec rhino mock, donc je ne sais pas s'il supporte les appels de méthode asynchrones. Mais Thread.Sleep(10000) est un code synchrone. Si vous voulez attendre en mode asynchrone, vous devez utiliser Task.Delay(10000).

Ou bien d'utiliser Task.Run(() => Thread.Sleep(10000)) si, Task.Delay(10000) est la manière préférée et la meilleure pratique.