Estoy escribiendo pruebas de unidad para la capa "pegamento" de mi aplicación, y tengo dificultades para crear pruebas deterministas para métodos asíncronos que permiten al usuario cancelar la operación prematuramente.cancelación de orquestación en prueba de unidad de larga duración
Específicamente, en algunos métodos asíncronos tenemos un código que reacciona a la cancelación de la llamada y asegura que el objeto está en el estado correcto antes de completarlo. Me gustaría asegurarme de que estas rutas de código estén cubiertas por pruebas.
Algunos seudo código C# que ejemplifica un método asíncrono típico en este escenario es el siguiente:
public void FooAsync(CancellationToken token, Action<FooCompletedEventArgs> callback)
{
if (token.IsCancellationRequested) DoSomeCleanup0();
// Call the four helper methods, checking for cancellations in between each
Exception encounteredException;
try
{
MyDependency.DoExpensiveStuff1();
if (token.IsCancellationRequested) DoSomeCleanup1();
MyDependency.DoExpensiveStuff2();
if (token.IsCancellationRequested) DoSomeCleanup2();
MyDependency.DoExpensiveStuff3();
if (token.IsCancellationRequested) DoSomeCleanup3();
MyDependency.DoExpensiveStuff4();
if (token.IsCancellationRequested) DoSomeCleanup4();
}
catch (Exception e)
{
encounteredException = e;
}
if (!token.IsCancellationRequested)
{
var args = new FooCompletedEventArgs(a bunch of params);
callback(args);
}
}
La solución que he encontrado hasta el momento implica burlarse de los MyDependency
operaciones subyacentes que se envuelven por la capa de pegamento y obligando a cada uno a dormir durante un período de tiempo arbitrario. Luego invoco el método async y digo a mi prueba unitaria que duerma durante varios milisegundos antes de cancelar la solicitud asincrónica.
Algo como esto (usando burla de Rhino como ejemplo):
[TestMethod]
public void FooAsyncTest_CancelAfter2()
{
// arrange
var myDependency = MockRepository.GenerateStub<IMyDependency>();
// Set these stubs up to take a little bit of time each so we can orcestrate the cancels
myDependency.Stub(x => x.DoExpensiveStuff1()).WhenCalled(x => Thread.Sleep(100));
myDependency.Stub(x => x.DoExpensiveStuff2()).WhenCalled(x => Thread.Sleep(100));
myDependency.Stub(x => x.DoExpensiveStuff3()).WhenCalled(x => Thread.Sleep(100));
myDependency.Stub(x => x.DoExpensiveStuff4()).WhenCalled(x => Thread.Sleep(100));
// act
var target = new FooClass(myDependency);
CancellationTokenSource cts = new CancellationTokenSource();
bool wasCancelled = false;
target.FooAsync(
cts.Token,
args =>
{
wasCancelled = args.IsCancelled;
// Some other code to manipulate FooCompletedEventArgs
});
// sleep long enough for two operations to complete, then cancel
Thread.Sleep(250);
cts.Cancel();
// Some code to ensure the async call completes goes here
//assert
Assert.IsTrue(wasCancelled);
// Other assertions to validate state of target go here
}
Aparte del hecho de que el uso de Thread.Sleep en una prueba de unidad me hace mareado, el mayor problema es que a veces las pruebas de este tipo fracasan en nuestro servidor de compilación si sucede que está bajo una carga significativa. La llamada asincrónica llega demasiado lejos y la cancelación llega demasiado tarde.
¿Alguien puede proporcionar una manera más confiable de probar la unidad lógica de cancelación para operaciones de larga ejecución como esta? Cualquier idea sería apreciada.
Esto se ve extremadamente prometedor. Lo probaré esta tarde y le informaré. –
Funcionó muy bien. ¡Gracias por tu ayuda! –