2010-06-08 8 views
8

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.

risposta

5

Vorrei provare a utilizzare i mock per "simulare" il comportamento asincrono in modo sincrono. Invece di usare

myDependency.Stub(x => x.DoExpensiveStuff1()).WhenCalled(x => Thread.Sleep(100)); 

e quindi impostando il flag cancellazione entro qualunque sia il numero di millisecondi, vorrei solo impostarla come parte della richiamata:

myDependency.Stub(x => x.DoExpensiveStuff1()); 
myDependency.Stub(x => x.DoExpensiveStuff2()); 
myDependency.Stub(x => x.DoExpensiveStuff3()).WhenCalled(x => cts.Cancel()); 
myDependency.Stub(x => x.DoExpensiveStuff4()); 

dal punto di vista del codice di questo sarà come se il cancellazione avvenuta durante la chiamata.

+0

Questo sembra estremamente promettente. Darò una prova questo pomeriggio e riferirò. –

+0

Ha funzionato magnificamente. Grazie per l'aiuto! –

1

Ciascuna delle operazioni di lunga durata dovrebbe generare un evento quando iniziano a funzionare.

Aggancia questo evento al test dell'unità. Ciò fornisce risultati deterministici con il potenziale che gli eventi potrebbero essere utili in futuro.

Problemi correlati