Ok - não tenho certeza se o seguinte será de alguma ajuda para você, porque fiz algumas suposições no desenvolvimento de uma solução que pode ou não ser verdadeira no seu caso. Talvez minha "solução" seja muito teórica e funcione apenas para exemplos artificiais - eu não realizei nenhum teste além dos itens abaixo.
Além disso, eu consideraria o seguinte mais uma solução alternativa do que uma solução real, mas, considerando a falta de respostas, acho que ainda pode ser melhor do que nada (continuei observando sua pergunta aguardando uma solução, mas não vendo uma sendo postada, comecei a jogar por aí com o problema).
Mas basta dizer: digamos que temos um serviço de dados simples que pode ser usado para recuperar um número inteiro:
public interface IDataService
{
Task<int> LoadMagicInteger();
}
Uma implementação simples usa código assíncrono:
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;
}
}
Agora, surge um problema, se estivermos usando o código "incorretamente", conforme ilustrado por esta classe. Foo
acessa incorretamente, em Task.Result
vez de await
ing o resultado, como Bar
:
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;
}
}
O que precisamos agora é de uma maneira de escrever um teste que seja bem-sucedido ao ligar, Bar
mas falhe ao ligar Foo
(pelo menos se eu entendi a pergunta corretamente ;-)).
Vou deixar o código falar; aqui está o que eu criei (usando testes do Visual Studio, mas deve funcionar usando o NUnit também):
DataServiceMock
utiliza TaskCompletionSource<T>
. Isso nos permite definir o resultado em um ponto definido na execução do teste que leva ao teste a seguir. Observe que estamos usando um representante para devolver o TaskCompletionSource de volta ao teste. Você também pode colocar isso no método Initialize do teste e usar propriedades.
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;
O que está acontecendo aqui é que primeiro verificamos que podemos deixar o método sem bloquear (isso não funcionaria se alguém fosse acessado Task.Result
- nesse caso, teríamos um tempo limite porque o resultado da tarefa não seria disponibilizado até que o método retornasse )
Em seguida, definimos o resultado (agora o método pode ser executado) e verificamos o resultado (em um teste de unidade, podemos acessar o Task.Result, pois na verdade queremos que o bloqueio ocorra).
Classe de teste completa - BarTest
obtém êxito e FooTest
falha conforme desejado.
[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;
}
}
E uma pequena turma auxiliar para testar deadlocks / 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
artigos desse cara ?