multithreading - threading - python thread example




Come dovrei eseguire il test del codice thread? (16)

Finora ho evitato l'incubo che sta testando il codice multi-thread poiché sembra troppo un campo minato. Vorrei chiedere come sono andate le persone a testare il codice che si basa su thread per l'esecuzione di successo, o semplicemente su come le persone hanno provato a testare quei tipi di problemi che si manifestano solo quando due thread interagiscono in un determinato modo?

Questo sembra un vero problema chiave per i programmatori oggi, sarebbe utile mettere insieme le nostre conoscenze su questo imho.


È passato un po 'di tempo quando è stata pubblicata questa domanda, ma non ha ancora ricevuto risposta ...

La risposta di kleolb02 è buona. Proverò a entrare in ulteriori dettagli.

C'è un modo, che pratico per il codice C #. Per i test unitari dovresti essere in grado di programmare test riproducibili , che è la più grande sfida nel codice multithread. Quindi la mia risposta mira a forzare il codice asincrono in un'imbracatura di test, che funziona in modo sincrono .

È un'idea del libro di Gerard Meszardos " xUnit Test Patterns " e si chiama "Humble Object" (p. 695): Devi separare il codice logico di base e tutto ciò che puzza come un codice asincrono gli uni dagli altri. Ciò risulterebbe in una classe per la logica principale, che funziona in modo sincrono .

Questo ti mette nella posizione di testare il codice logico core in modo sincrono . Hai il controllo assoluto sui tempi delle chiamate che stai facendo sulla logica di base e quindi puoi fare test riproducibili . E questo è il tuo vantaggio dal separare la logica di base e la logica asincrona.

Questa logica di base deve essere avvolta da un'altra classe, che è responsabile della ricezione delle chiamate alla logica principale in modo asincrono e delega queste chiamate alla logica principale. Il codice di produzione accederà alla logica principale solo tramite quella classe. Poiché questa classe dovrebbe delegare solo le chiamate, è una classe molto "stupida" senza molta logica. Quindi puoi mantenere i tuoi test unitari per questa classe lavoratrice asincrona come minimo.

Qualsiasi cosa al di sopra di questo (test di interazione tra classi) sono test di componenti. Anche in questo caso, dovresti essere in grado di avere il controllo assoluto sui tempi, se ti attieni al pattern "Humble Object".


È possibile utilizzare EasyMock.makeThreadSafe per rendere l'analisi dell'istanza protetta da thread


Ci sono alcuni strumenti che sono abbastanza buoni. Ecco un riassunto di alcuni di quelli Java.

Alcuni buoni strumenti di analisi statica includono FindBugs (fornisce alcuni suggerimenti utili), JLint , Java Pathfinder (JPF e JPF2) e Bogor .

MultithreadedTC è un ottimo strumento di analisi dinamica (integrato in JUnit) in cui devi impostare i tuoi casi di test.

ConTest di IBM Research è interessante. Misura il tuo codice inserendo tutti i tipi di comportamenti di modifica del thread (ad esempio, sleep e yield) per cercare di scoprire bug in modo casuale.

SPIN è uno strumento davvero interessante per modellare i componenti Java (e altri), ma è necessario disporre di un framework utile. È difficile da usare così com'è, ma estremamente potente se sai come usarlo. Parecchi strumenti usano SPIN sotto il cofano.

MultithreadedTC è probabilmente il più tradizionale, ma alcuni degli strumenti di analisi statici sopra elencati meritano sicuramente una visita.


Dai un'occhiata alla mia risposta correlata a

Progettare una classe di test per una barriera personalizzata

È prevenuto nei confronti di Java ma ha un ragionevole riepilogo delle opzioni.

In sintesi però (IMO) non è l'uso di qualche quadro di fantasia che garantirà la correttezza, ma come si procede progettando il codice multithreaded. Dividere le preoccupazioni (concorrenza e funzionalità) è un modo enorme per aumentare la fiducia. Software guidato orientato agli oggetti in crescita spiega alcune opzioni meglio di me.

L'analisi statica e i metodi formali (vedi, Concurrency: State Models e Java Programs ) sono un'opzione ma ho trovato che sono di uso limitato nello sviluppo commerciale.

Non dimenticare che raramente sono supportati i test di stile carico / immersione per evidenziare i problemi.

In bocca al lupo!


Gestisco i test unitari dei componenti filettati nello stesso modo in cui gestisco qualsiasi test unitario, ovvero con l'inversione dei quadri di controllo e isolamento. Io sviluppo nell'arena .Net e fuori dagli schemi il threading (tra le altre cose) è molto difficile (direi quasi impossibile) isolare completamente.

Pertanto ho scritto wrapper che assomigliano a questo (semplificato):

public interface IThread
{
    void Start();
    ...
}

public class ThreadWrapper : IThread
{
    private readonly Thread _thread;

    public ThreadWrapper(ThreadStart threadStart)
    {
        _thread = new Thread(threadStart);
    }

    public Start()
    {
        _thread.Start();
    }
}

public interface IThreadingManager
{
    IThread CreateThread(ThreadStart threadStart);
}

public class ThreadingManager : IThreadingManager
{
    public IThread CreateThread(ThreadStart threadStart)
    {
         return new ThreadWrapper(threadStart)
    }
}

Da lì posso facilmente iniettare IThreadingManager nei miei componenti e usare il mio framework di isolamento di scelta per far sì che il thread si comporti come mi aspetto durante il test.

Ciò ha funzionato fino ad ora per me, e io uso lo stesso approccio per il pool di thread, le cose in System.Environment, Sleep ecc. Ecc.


Guarda, non c'è un modo semplice per farlo. Sto lavorando a un progetto che è intrinsecamente multithread. Gli eventi arrivano dal sistema operativo e devo elaborarli contemporaneamente.

Il modo più semplice per gestire il codice di un'applicazione complessa e multithread è questo: se è troppo complesso da testare, lo stai facendo male. Se si dispone di una singola istanza con più thread che agiscono su di essa e non è possibile verificare le situazioni in cui questi thread si succedono l'uno sull'altro, è necessario ripetere la progettazione. È sia semplice che complesso come questo.

Esistono molti modi per programmare il multithreading che evita che i thread eseguano le istanze contemporaneamente. Il più semplice è rendere immutabili tutti i tuoi oggetti. Certo, di solito non è possibile. Quindi devi identificare quei punti nella tua progettazione in cui i thread si interrompono con la stessa istanza e riducono il numero di quei luoghi. In questo modo, si isolano alcune classi in cui si verifica effettivamente il multithreading, riducendo la complessità complessiva del test del sistema.

Ma devi rendertene conto che, anche facendo questo, non è ancora possibile testare ogni situazione in cui due thread si sovrappongono. Per fare ciò, dovresti eseguire due thread contemporaneamente nello stesso test, quindi controllare esattamente quali linee stanno eseguendo in un dato momento. Il meglio che puoi fare è simulare questa situazione. Ma questo potrebbe richiedere un codice specifico per i test, e questo è al massimo un mezzo passo verso una vera soluzione.

Probabilmente il modo migliore per testare il codice per problemi di threading è attraverso l'analisi statica del codice. Se il tuo codice threaded non segue un insieme finito di pattern thread safe, allora potresti avere un problema. Credo che l'analisi del codice in VS contenga una certa conoscenza del threading, ma probabilmente non molto.

Guarda come stanno le cose al momento (e probabilmente starà per un buon momento a venire), il modo migliore per testare le applicazioni multithread è ridurre la complessità del codice filettato il più possibile. Ridurre al minimo le aree in cui i thread interagiscono, testare al meglio e utilizzare l'analisi del codice per identificare le aree di pericolo.


Ho anche avuto seri problemi con il test del codice multi-thread. Poi ho trovato una soluzione davvero interessante in "xUnit Test Patterns" di Gerard Meszaros. Lo schema che descrive è chiamato oggetto umile .

Fondamentalmente descrive come estrarre la logica in un componente separato, facile da testare, disaccoppiato dal suo ambiente. Dopo aver testato questa logica, puoi testare il comportamento complicato (multi-threading, esecuzione asincrona, ecc ...)


Ho avuto il compito sfortunato di testare il codice threaded e sono sicuramente i test più difficili che abbia mai scritto.

Quando ho scritto i miei test, ho usato una combinazione di delegati ed eventi. Fondamentalmente si tratta di utilizzare gli eventi PropertyNotifyChanged con WaitCallback o qualche tipo di ConditionalWaiter che esegue il polling.

Non sono sicuro se questo fosse l'approccio migliore, ma ha funzionato per me.


Ho trascorso la maggior parte della scorsa settimana in una biblioteca universitaria studiando il debug del codice concorrente. Il problema centrale è che il codice concorrente non è deterministico. In genere, il debugging accademico è caduto in uno dei tre campi qui:

  1. Evento-traccia / replay. Ciò richiede un monitoraggio degli eventi e quindi la revisione degli eventi inviati. In un framework UT, ciò comporterebbe l'invio manuale degli eventi come parte di un test e quindi l'esecuzione di revisioni post-mortem.
  2. Script. È qui che interagisci con il codice in esecuzione con una serie di trigger. "Su x> foo, baz ()". Questo potrebbe essere interpretato in un framework UT in cui si dispone di un sistema run-time che attiva un determinato test su una determinata condizione.
  3. Interactive. Questo ovviamente non funzionerà in una situazione di test automatico. ;)

Ora, come già notato dai commentatori, puoi progettare il tuo sistema concorrente in uno stato più deterministico. Tuttavia, se non lo fai correttamente, sei appena tornato a progettare di nuovo un sistema sequenziale.

Il mio suggerimento sarebbe quello di concentrarsi sull'avere un protocollo di progettazione molto rigoroso su cosa viene inserito e cosa non viene infilato. Se si vincola l'interfaccia in modo che vi siano delle dipendenze minime tra gli elementi, è molto più semplice.

Buona fortuna e continua a lavorare sul problema.


Il seguente articolo suggerisce 2 soluzioni. Avvolgere un semaforo (CountDownLatch) e aggiungere funzionalità come esternalizzare i dati dal thread interno. Un altro modo per raggiungere questo scopo è usare Thread Pool (vedi Punti di interesse).

Sprinkler - Oggetto di sincronizzazione avanzato


Mi piace scrivere due o più metodi di test da eseguire su thread paralleli e ognuno di essi effettua chiamate nell'oggetto sotto test. Ho usato le chiamate Sleep () per coordinare l'ordine delle chiamate da diversi thread, ma non è davvero affidabile. È anche molto più lento perché devi dormire abbastanza a lungo che i tempi di solito funzionano.

Ho trovato la this dallo stesso gruppo che ha scritto FindBugs. Ti consente di specificare l'ordine degli eventi senza utilizzare Sleep () ed è affidabile. Non l'ho ancora provato.

La più grande limitazione a questo approccio è che ti consente solo di testare gli scenari che sospetti causeranno problemi. Come altri hanno già detto, è davvero necessario isolare il proprio codice multithread in un piccolo numero di classi semplici per avere qualche speranza di testarle a fondo.

Una volta che hai testato attentamente gli scenari che prevedi di causare problemi, un test non scientifico che genera un po 'di richieste simultanee in classe per un po' è un buon modo per cercare guai inaspettati.

Aggiornamento: ho giocato un po 'con la libreria Java TC multithread e funziona bene. Ho anche portato alcune delle sue funzionalità a una versione .NET che chiamo TickingTest .


Per Java, consulta il capitolo 12 di JCIP . Esistono alcuni esempi concreti di scrittura di test unitistici deterministici e multi-thread per testare almeno la correttezza e gli invarianti del codice concorrente.

"Provare" la sicurezza del thread con i test di unità è molto più difficile. La mia convinzione è che questo è meglio servito da test di integrazione automatizzati su una varietà di piattaforme / configurazioni.


Recentemente ho scoperto (per Java) uno strumento chiamato Threadsafe. È uno strumento di analisi statica molto simile a findbugs ma in particolare per individuare problemi di multi-threading. Non è un sostituto per i test, ma posso raccomandarlo come parte della scrittura di Java multi-thread affidabile.

Cattura anche alcuni problemi potenziali molto sottili intorno a cose come la sussunzione di classe, l'accesso a oggetti non sicuri attraverso le classi concorrenti e l'individuazione dei modificatori volatili mancanti quando si utilizza il paradigma di blocco a doppio controllo.

Se scrivi Java multithreading contemplateltd.com/threadsafe


Se stai testando il nuovo thread semplice (eseguibile) .run (), puoi simulare il thread per eseguire il runnable in sequenza

Ad esempio se il codice dell'oggetto testato richiama un nuovo thread come questo

Class TestedClass {
    public void doAsychOp() {
       new Thread(new myRunnable()).start();
    }
}

Quindi può essere d'aiuto prendere in giro i nuovi Thread ed eseguire l'argomento eseguibile in modo sequenziale

@Mock
private Thread threadMock;

@Test
public void myTest() throws Exception {
    PowerMockito.mockStatic(Thread.class);
    //when new thread is created execute runnable immediately 
    PowerMockito.whenNew(Thread.class).withAnyArguments().then(new Answer<Thread>() {
        @Override
        public Thread answer(InvocationOnMock invocation) throws Throwable {
            // immediately run the runnable
            Runnable runnable = invocation.getArgumentAt(0, Runnable.class);
            if(runnable != null) {
                runnable.run();
            }
            return threadMock;//return a mock so Thread.start() will do nothing         
        }
    }); 
    TestedClass testcls = new TestedClass()
    testcls.doAsychOp(); //will invoke myRunnable.run in current thread
    //.... check expected 
}

Un altro modo per testare (un po ') il codice filettato e in generale i sistemi molto complessi è attraverso il test Fuzz . Non è grandioso e non troverà tutto, ma è probabile che sia utile e semplice da fare.

Citazione:

Il test fuzz o fuzzing è una tecnica di test del software che fornisce dati casuali ("fuzz") agli input di un programma. Se il programma fallisce (ad esempio, in caso di arresto anomalo, o in mancanza di affermazioni di codice incorporate), è possibile notare i difetti. Il grande vantaggio del test fuzz è che la progettazione del test è estremamente semplice e priva di preconcetti sul comportamento del sistema.

...

Il test fuzz viene spesso utilizzato in progetti di sviluppo di software di grandi dimensioni che impiegano test black box. Questi progetti di solito hanno un budget per sviluppare strumenti di test e il test fuzz è una delle tecniche che offre un elevato rapporto costi / benefici.

...

Tuttavia, il test fuzz non è un sostituto per test esaustivi o metodi formali: può solo fornire un campione casuale del comportamento del sistema, e in molti casi superare un test fuzz può solo dimostrare che un software gestisce le eccezioni senza crash, piuttosto che comportarsi correttamente Pertanto, il test fuzz può essere considerato solo uno strumento di individuazione dei bug piuttosto che una garanzia di qualità.


Awaitility può anche essere utile per aiutare a scrivere test unitari deterministici. Ti permette di aspettare fino a quando qualche stato nel tuo sistema è stato aggiornato. Per esempio:

await().untilCall( to(myService).myMethod(), greaterThan(3) );

o

await().atMost(5,SECONDS).until(fieldIn(myObject).ofType(int.class), equalTo(1));

Ha anche il supporto di Scala e Groovy.

await until { something() > 4 } // Scala example






unit-testing