Sto scrivendo test di unità per il livello "colla" della mia applicazione e sto riscontrando difficoltà nella creazione di test deterministici per metodi asincroni che consentono all'utente di annullare l'operazione prematuramente.Errore di orchestrazione nel test dell'unità di lunga durata
In particolare, in alcuni metodi asincroni abbiamo codice che reagisce alla cancellazione della chiamata e garantisce che l'oggetto sia nello stato appropriato prima del completamento. Vorrei assicurarmi che questi percorsi di codice siano coperti da test.
Alcuni C# pseudo codice esemplificare un metodo tipico asincrono in questo scenario è il seguente:
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 soluzione che ho elaborato finora coinvolge scherno sottostanti MyDependency
operazioni che vengono avvolti dallo strato di colla e costringendo ciascuno a dormire per un periodo di tempo arbitrario. Quindi invoco il metodo asincrono e comunico al mio unit test di dormire per un numero di millisecondi prima di annullare la richiesta asincrona.
Qualcosa di simile (usando Rhino Mocks come esempio):
[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
}
A parte il fatto che l'utilizzo di Thread.Sleep in una prova di unità mi fa nausea, il problema più grande è che a volte i test come questo falliscono sul nostro server di build se capita di essere sotto carico significativo. La chiamata asincrona diventa troppo lontana e l'annullamento arriva troppo tardi.
Qualcuno può fornire un modo più affidabile di logica di annullamento del test dell'unità per operazioni di lunga durata come questa? Qualsiasi idea sarebbe apprezzata.
Questo sembra estremamente promettente. Darò una prova questo pomeriggio e riferirò. –
Ha funzionato magnificamente. Grazie per l'aiuto! –