Ok - Je ne sais pas si les éléments suivants vous seront utiles, car j'ai fait certaines hypothèses dans le développement d'une solution qui peut ou non être vraie dans votre cas. Peut-être que ma "solution" est trop théorique et ne fonctionne que pour des exemples artificiels - je n'ai pas fait de test au-delà des choses ci-dessous.
De plus, je verrais ce qui suit plus une solution de contournement qu'une vraie solution, mais étant donné le manque de réponses, je pense que cela pourrait être mieux que rien (j'ai continué à regarder votre question en attendant une solution, mais sans en voir une se publier, j'ai commencé à jouer autour de la question).
Mais assez dit: disons que nous avons un service de données simple qui peut être utilisé pour récupérer un entier:
public interface IDataService
{
Task<int> LoadMagicInteger();
}
Une implémentation simple utilise du code asynchrone:
public sealed class CustomDataService
: IDataService
{
public async Task<int> LoadMagicInteger()
{
Console.WriteLine("LoadMagicInteger - 1");
await Task.Delay(100);
Console.WriteLine("LoadMagicInteger - 2");
var result = 42;
Console.WriteLine("LoadMagicInteger - 3");
await Task.Delay(100);
Console.WriteLine("LoadMagicInteger - 4");
return result;
}
}
Maintenant, un problème se pose, si nous utilisons le code "incorrectement" comme illustré par cette classe. Foo
accède de manière incorrecte Task.Result
au lieu de await
générer le résultat comme le Bar
fait:
public sealed class ClassToTest
{
private readonly IDataService _dataService;
public ClassToTest(IDataService dataService)
{
this._dataService = dataService;
}
public async Task<int> Foo()
{
var result = this._dataService.LoadMagicInteger().Result;
return result;
}
public async Task<int> Bar()
{
var result = await this._dataService.LoadMagicInteger();
return result;
}
}
Ce dont nous (vous) avons maintenant besoin, c'est d'un moyen d'écrire un test qui réussit lors de l'appel Bar
mais échoue lors de l'appel Foo
(du moins si j'ai bien compris la question ;-)).
Je vais laisser le code parler; voici ce que j'ai trouvé (en utilisant des tests Visual Studio, mais cela devrait aussi fonctionner en utilisant NUnit):
DataServiceMock
utilise TaskCompletionSource<T>
. Cela nous permet de définir le résultat à un point défini dans le test, ce qui conduit au test suivant. Notez que nous utilisons un délégué pour renvoyer le TaskCompletionSource dans le test. Vous pouvez également mettre cela dans la méthode Initialize des propriétés test et use.
TaskCompletionSource<int> tcs = null;
this._dataService.LoadMagicIntegerMock = t => tcs = t;
Task<int> task = null;
TaskTestHelper.AssertDoesNotBlock(() => task = this._instance.Foo());
tcs.TrySetResult(42);
var result = task.Result;
Assert.AreEqual(42, result);
this._end = true;
Ce qui se passe ici, c'est que nous vérifions d'abord que nous pouvons laisser la méthode sans bloquer (cela ne fonctionnerait pas si quelqu'un y accédait Task.Result
- dans ce cas, nous courrions dans un délai d'attente car le résultat de la tâche n'est rendu disponible qu'après le retour de la méthode ).
Ensuite, nous définissons le résultat (maintenant la méthode peut s'exécuter) et nous vérifions le résultat (à l'intérieur d'un test unitaire, nous pouvons accéder à Task.Result car nous voulons réellement que le blocage se produise).
Classe de test complète - BarTest
réussit et FooTest
échoue comme souhaité.
[TestClass]
public class UnitTest1
{
private DataServiceMock _dataService;
private ClassToTest _instance;
private bool _end;
[TestInitialize]
public void Initialize()
{
this._dataService = new DataServiceMock();
this._instance = new ClassToTest(this._dataService);
this._end = false;
}
[TestCleanup]
public void Cleanup()
{
Assert.IsTrue(this._end);
}
[TestMethod]
public void FooTest()
{
TaskCompletionSource<int> tcs = null;
this._dataService.LoadMagicIntegerMock = t => tcs = t;
Task<int> task = null;
TaskTestHelper.AssertDoesNotBlock(() => task = this._instance.Foo());
tcs.TrySetResult(42);
var result = task.Result;
Assert.AreEqual(42, result);
this._end = true;
}
[TestMethod]
public void BarTest()
{
TaskCompletionSource<int> tcs = null;
this._dataService.LoadMagicIntegerMock = t => tcs = t;
Task<int> task = null;
TaskTestHelper.AssertDoesNotBlock(() => task = this._instance.Bar());
tcs.TrySetResult(42);
var result = task.Result;
Assert.AreEqual(42, result);
this._end = true;
}
}
Et une petite classe d'aide pour tester les blocages / timeouts:
public static class TaskTestHelper
{
public static void AssertDoesNotBlock(Action action, int timeout = 1000)
{
var timeoutTask = Task.Delay(timeout);
var task = Task.Factory.StartNew(action);
Task.WaitAny(timeoutTask, task);
Assert.IsTrue(task.IsCompleted);
}
}
async
articles de ce type ?