multithreading java - Come dovrei eseguire il test del codice thread?




pdf python (21)

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.

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.


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


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à.


(se possibile) non usare discussioni, usare attori / oggetti attivi. Facile da testare


Ho affrontato questo problema più volte negli ultimi anni quando scrivevo il codice di gestione dei thread per diversi progetti. Sto fornendo una risposta tardiva perché la maggior parte delle altre risposte, pur fornendo alternative, in realtà non rispondono alla domanda sul test. La mia risposta è indirizzata ai casi in cui non c'è alternativa al codice multithread; Faccio riferimento ai problemi di progettazione del codice per completezza, ma discuto anche dei test unitari.

Scrittura del codice multithread testabile

La prima cosa da fare è separare il codice di gestione del thread di produzione da tutto il codice che esegue l'elaborazione dei dati effettiva. In questo modo, l'elaborazione dei dati può essere testata come codice con thread singolo e l'unica cosa che fa il codice multithread è di coordinare i thread.

La seconda cosa da ricordare è che i bug nel codice multithread sono probabilistici; i bug che si manifestano meno frequentemente sono i bug che entreranno di nascosto nella produzione, saranno difficili da riprodurre anche in produzione e causeranno quindi i maggiori problemi. Per questo motivo, l'approccio standard di codifica di scrivere il codice rapidamente e quindi eseguirne il debug fino a quando non funziona è una cattiva idea per il codice multithread; ciò si tradurrà in codice in cui sono corretti i bug facili e i bug pericolosi sono ancora lì.

Invece, quando si scrive codice multithread, è necessario scrivere il codice con l'attitudine che si intende evitare di scrivere gli errori in primo luogo. Se hai rimosso correttamente il codice di elaborazione dei dati, il codice di gestione dei thread dovrebbe essere abbastanza piccolo (preferibilmente poche righe, nel peggiore qualche dozzina di righe) che hai la possibilità di scriverlo senza scrivere un bug e sicuramente senza scrivere molti bug , se capisci le discussioni, prenditi il ​​tuo tempo e stai attento.

Scrittura di unit test per codice multithread

Una volta scritto il codice multithread nel modo più accurato possibile, vale ancora la pena scrivere test per quel codice. Lo scopo principale dei test non è tanto quello di testare i bug con condizioni di gara altamente dipendenti dalla tempistica - è impossibile testare per tali condizioni di gara in modo ripetitivo - ma piuttosto testare che la strategia di blocco per prevenire tali bug consente a più thread di interagire come previsto .

Per verificare correttamente il corretto comportamento di blocco, un test deve iniziare più thread. Per rendere ripetibile il test, vogliamo che le interazioni tra i thread avvengano in un ordine prevedibile. Non vogliamo sincronizzare esternamente i thread nel test, poiché ciò maschererà i bug che potrebbero verificarsi nella produzione in cui i thread non sono sincronizzati esternamente. Questo lascia l'uso dei ritardi temporali per la sincronizzazione dei thread, che è la tecnica che ho usato con successo ogni volta che ho dovuto scrivere test di codice multithread.

Se i ritardi sono troppo brevi, il test diventa fragile, poiché differenze di temporizzazione minori, ad esempio tra le diverse macchine su cui possono essere eseguiti i test, possono causare l'interruzione dei tempi e il fallimento del test. Quello che ho fatto in genere è iniziare con ritardi che causano errori di test, aumentare i ritardi in modo che il test passi in modo affidabile sulla mia macchina di sviluppo, e quindi raddoppiare i ritardi oltre a ciò, quindi il test ha buone possibilità di trasmettere su altre macchine. Ciò significa che il test richiederà una quantità di tempo macroscopica, sebbene, secondo la mia esperienza, un'attenta progettazione del test possa limitare il tempo a non più di una dozzina di secondi. Dal momento che non dovresti avere molti posti che richiedono il codice di coordinamento del thread nella tua applicazione, dovrebbe essere accettabile per la tua suite di test.

Infine, tieni traccia del numero di bug catturati dal test. Se il tuo test ha una copertura del codice dell'80%, ci si può aspettare che catturi circa l'80% dei bug. Se il tuo test è ben progettato ma non trova bug, c'è una ragionevole possibilità che tu non abbia bug aggiuntivi che verranno mostrati solo in produzione. Se il test cattura uno o due bug, potresti comunque essere fortunato. Oltre a questo, potresti prendere in considerazione un'attenta revisione o anche una completa riscrittura del tuo codice di gestione thread, poiché è probabile che il codice contenga ancora bug nascosti che saranno molto difficili da trovare fino a quando il codice sarà in produzione, e molto difficile da risolvere quindi.


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.


Per il codice J2E, ho usato SilkPerformer, LoadRunner e JMeter per il test di concorrenza dei thread. Fanno tutti la stessa cosa Fondamentalmente, ti forniscono un'interfaccia relativamente semplice per l'amministrazione della loro versione del server proxy, necessaria per analizzare il flusso di dati TCP / IP e simulare più utenti che fanno richieste simultanee al tuo server delle applicazioni. Il server proxy può darti la possibilità di fare cose come analizzare le richieste fatte, presentando l'intera pagina e l'URL inviati al server, così come la risposta dal server, dopo aver elaborato la richiesta.

È possibile trovare alcuni bug in modalità http non sicura, in cui è possibile almeno analizzare i dati del modulo che vengono inviati e modificarlo sistematicamente per ciascun utente. Ma i veri test sono quando si esegue in https (Secured Socket Layers). Quindi, devi anche fare i conti con la modifica sistematica della sessione e dei dati dei cookie, che possono essere un po 'più complicati.

Il miglior bug che abbia mai trovato, durante il test della concorrenza, è stato quando ho scoperto che lo sviluppatore si era basato sulla raccolta dati inutili Java per chiudere la richiesta di connessione stabilita all'accesso, al server LDAP, al momento dell'accesso. alle sessioni di altri utenti e risultati molto confusi, quando si tenta di analizzare cosa è successo quando il server è stato portato in ginocchio, a malapena in grado di completare una transazione, ogni pochi secondi.

Alla fine, tu o qualcuno probabilmente dovresti crollare e analizzare il codice per errori come quello che ho appena menzionato. E una discussione aperta tra reparti, come quella che si è verificata, quando abbiamo spiegato il problema sopra descritto, è molto utile. Ma questi strumenti sono la soluzione migliore per testare il codice multi-thread. JMeter è open source. SilkPerformer e LoadRunner sono proprietari. Se vuoi davvero sapere se la tua app è sicura, è così che lo fanno i ragazzi più grandi. Ho fatto questo per aziende molto grandi professionalmente, quindi non sto indovinando. Sto parlando per esperienza personale.

Una parola di cautela: ci vuole del tempo per capire questi strumenti. Non si tratta semplicemente di installare il software e accendere la GUI, a meno che non si abbia già una certa esposizione alla programmazione multi-thread. Ho cercato di identificare le 3 categorie critiche di aree da comprendere (moduli, sessione e dati sui cookie), con la speranza che almeno iniziando a comprendere questi argomenti ti aiuterà a concentrarti sui risultati rapidi, invece di dover leggere il tutta la documentazione


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 ...)


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.


La concorrenza è una complessa interazione tra il modello di memoria, l'hardware, le cache e il nostro codice. Nel caso di Java almeno tali test sono stati in parte indirizzati principalmente da jcstress . I creatori di quella libreria sono noti per essere autori di molte funzioni di concorrenza JVM, GC e Java.

Ma anche questa libreria ha bisogno di una buona conoscenza delle specifiche del modello di memoria Java in modo da sapere esattamente cosa stiamo testando. Ma penso che l'obiettivo di questo sforzo sia mircobenchmarks. Non enormi applicazioni aziendali.


Ho fatto molto di questo, e sì, fa schifo.

Alcuni suggerimenti:

  • GroboUtils per l'esecuzione di più thread di test
  • alphaWorks ConTest alle classi dello strumento per causare variazioni tra le iterazioni
  • Crea un campo da throwable e controllalo in tearDown (vedi elenco 1). Se rilevi una brutta eccezione in un'altra discussione, assegnala a gettare.
  • Ho creato la classe utils nel Listato 2 e l'ho trovata inestimabile, in particolare waitForVerify e waitForCondition, che aumenterà notevolmente le prestazioni dei test.
  • Fai buon uso di AtomicBoolean nei tuoi test. È thread-safe e spesso è necessario un tipo di riferimento finale per archiviare valori da classi di callback e simili. Guarda l'esempio nel Listato 3.
  • Assicurati di dare sempre un timeout al test (ad esempio, @Test(timeout=60*1000) ), poiché i test di concorrenza possono a volte bloccarsi per sempre quando sono interrotti

Elenco 1:

@After
public void tearDown() {
    if ( throwable != null )
        throw throwable;
}

Elenco 2:

import static org.junit.Assert.fail;
import java.io.File;
import java.lang.reflect.InvocationHandler;
import java.lang.reflect.Proxy;
import java.util.Random;
import org.apache.commons.collections.Closure;
import org.apache.commons.collections.Predicate;
import org.apache.commons.lang.time.StopWatch;
import org.easymock.EasyMock;
import org.easymock.classextension.internal.ClassExtensionHelper;
import static org.easymock.classextension.EasyMock.*;

import ca.digitalrapids.io.DRFileUtils;

/**
 * Various utilities for testing
 */
public abstract class DRTestUtils
{
    static private Random random = new Random();

/** Calls {@link #waitForCondition(Integer, Integer, Predicate, String)} with
 * default max wait and check period values.
 */
static public void waitForCondition(Predicate predicate, String errorMessage) 
    throws Throwable
{
    waitForCondition(null, null, predicate, errorMessage);
}

/** Blocks until a condition is true, throwing an {@link AssertionError} if
 * it does not become true during a given max time.
 * @param maxWait_ms max time to wait for true condition. Optional; defaults
 * to 30 * 1000 ms (30 seconds).
 * @param checkPeriod_ms period at which to try the condition. Optional; defaults
 * to 100 ms.
 * @param predicate the condition
 * @param errorMessage message use in the {@link AssertionError}
 * @throws Throwable on {@link AssertionError} or any other exception/error
 */
static public void waitForCondition(Integer maxWait_ms, Integer checkPeriod_ms, 
    Predicate predicate, String errorMessage) throws Throwable 
{
    waitForCondition(maxWait_ms, checkPeriod_ms, predicate, new Closure() {
        public void execute(Object errorMessage)
        {
            fail((String)errorMessage);
        }
    }, errorMessage);
}

/** Blocks until a condition is true, running a closure if
 * it does not become true during a given max time.
 * @param maxWait_ms max time to wait for true condition. Optional; defaults
 * to 30 * 1000 ms (30 seconds).
 * @param checkPeriod_ms period at which to try the condition. Optional; defaults
 * to 100 ms.
 * @param predicate the condition
 * @param closure closure to run
 * @param argument argument for closure
 * @throws Throwable on {@link AssertionError} or any other exception/error
 */
static public void waitForCondition(Integer maxWait_ms, Integer checkPeriod_ms, 
    Predicate predicate, Closure closure, Object argument) throws Throwable 
{
    if ( maxWait_ms == null )
        maxWait_ms = 30 * 1000;
    if ( checkPeriod_ms == null )
        checkPeriod_ms = 100;
    StopWatch stopWatch = new StopWatch();
    stopWatch.start();
    while ( !predicate.evaluate(null) ) {
        Thread.sleep(checkPeriod_ms);
        if ( stopWatch.getTime() > maxWait_ms ) {
            closure.execute(argument);
        }
    }
}

/** Calls {@link #waitForVerify(Integer, Object)} with <code>null</code>
 * for {@code maxWait_ms}
 */
static public void waitForVerify(Object easyMockProxy)
    throws Throwable
{
    waitForVerify(null, easyMockProxy);
}

/** Repeatedly calls {@link EasyMock#verify(Object[])} until it succeeds, or a
 * max wait time has elapsed.
 * @param maxWait_ms Max wait time. <code>null</code> defaults to 30s.
 * @param easyMockProxy Proxy to call verify on
 * @throws Throwable
 */
static public void waitForVerify(Integer maxWait_ms, Object easyMockProxy)
    throws Throwable
{
    if ( maxWait_ms == null )
        maxWait_ms = 30 * 1000;
    StopWatch stopWatch = new StopWatch();
    stopWatch.start();
    for(;;) {
        try
        {
            verify(easyMockProxy);
            break;
        }
        catch (AssertionError e)
        {
            if ( stopWatch.getTime() > maxWait_ms )
                throw e;
            Thread.sleep(100);
        }
    }
}

/** Returns a path to a directory in the temp dir with the name of the given
 * class. This is useful for temporary test files.
 * @param aClass test class for which to create dir
 * @return the path
 */
static public String getTestDirPathForTestClass(Object object) 
{

    String filename = object instanceof Class ? 
        ((Class)object).getName() :
        object.getClass().getName();
    return DRFileUtils.getTempDir() + File.separator + 
        filename;
}

static public byte[] createRandomByteArray(int bytesLength)
{
    byte[] sourceBytes = new byte[bytesLength];
    random.nextBytes(sourceBytes);
    return sourceBytes;
}

/** Returns <code>true</code> if the given object is an EasyMock mock object 
 */
static public boolean isEasyMockMock(Object object) {
    try {
        InvocationHandler invocationHandler = Proxy
                .getInvocationHandler(object);
        return invocationHandler.getClass().getName().contains("easymock");
    } catch (IllegalArgumentException e) {
        return false;
    }
}
}

Elenco 3:

@Test
public void testSomething() {
    final AtomicBoolean called = new AtomicBoolean(false);
    subject.setCallback(new SomeCallback() {
        public void callback(Object arg) {
            // check arg here
            called.set(true);
        }
    });
    subject.run();
    assertTrue(called.get());
}

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


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


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.


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!


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

Testare il codice MT per la correttezza è, come già detto, un bel problema. Alla fine si riduce a garantire che non ci siano corse di dati sincronizzati in modo errato nel codice. Il problema con questo è che ci sono infinite possibilità di esecuzione dei thread (intrecci) su cui non si ha molto controllo (assicuratevi di leggere this articolo, però). In scenari semplici, potrebbe essere possibile dimostrare effettivamente la correttezza con il ragionamento, ma questo di solito non è il caso. Soprattutto se si desidera evitare / minimizzare la sincronizzazione e non optare per l'opzione di sincronizzazione più ovvia / più semplice.

Un approccio che seguo è quello di scrivere un codice di prova altamente concorrente al fine di rendere potenzialmente possibili le gare di dati potenzialmente non rilevate. E poi eseguo quei test per un po 'di tempo :) Una volta mi sono imbattuto in un discorso in cui qualche scienziato informatico mostrava uno strumento che fa questo (testando a caso le specifiche e poi eseguendole selvaggiamente, contemporaneamente, controllando gli invarianti definiti essere rotto).

A proposito, penso che questo aspetto del test del codice MT non sia stato menzionato qui: identifica gli invarianti del codice che puoi verificare a caso. Sfortunatamente, trovare questi invarianti è un problema abbastanza difficile. Inoltre potrebbero non tenere tutto il tempo durante l'esecuzione, quindi devi trovare / imporre punti di esecuzione dove puoi aspettarti che siano vere. Portare l'esecuzione del codice a tale stato è anche un problema difficile (e potrebbe anche incorrere in problemi di concorrenza. Wow, è dannatamente difficile!

Alcuni link interessanti da leggere:

  • this : un framework che consente di forzare determinati intrecci di thread e quindi verificare la presenza di invarianti
  • jMock Blitzer : sincronizzazione dello stress test
  • assertConcurrent : versione JUnit della sincronizzazione degli stress test
  • Test del codice concorrente : breve panoramica dei due metodi principali di forza bruta (stress test) o deterministico (andando per gli invarianti)

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.


Pete Goodliffe ha una serie sul test unitario del codice filettato .

È difficile. Prendo la via più semplice e cerco di mantenere il codice di threading estratto dal test effettivo. Pete dice che il modo in cui lo faccio è sbagliato, ma ho avuto la separazione giusta o sono stato solo fortunato.


Gestire un lavoro lungo

Dal momento che .NET 4.5 e C # 5.0 è necessario utilizzare il modello asincrono basato su attività (TAP) insieme async - await parole chiave in tutte le aree (compresa la GUI):

TAP è il modello di progettazione asincrono consigliato per il nuovo sviluppo

anziché il modello di programmazione asincrono (APM) e il modello asincrono basato sugli eventi (EAP) (quest'ultimo include la classe BackgroundWorker ).

Quindi, la soluzione consigliata per il nuovo sviluppo è:

  1. Implementazione asincrona di un gestore di eventi (Sì, tutto qui):

    private async void Button_Clicked(object sender, EventArgs e)
    {
        var progress = new Progress<string>(s => label.Text = s);
        await Task.Factory.StartNew(() => SecondThreadConcern.LongWork(progress),
                                    TaskCreationOptions.LongRunning);
        label.Text = "completed";
    }
    
  2. Implementazione del secondo thread che notifica il thread dell'interfaccia utente:

    class SecondThreadConcern
    {
        public static void LongWork(IProgress<string> progress)
        {
            // Perform a long running work...
            for (var i = 0; i < 10; i++)
            {
                Task.Delay(500).Wait();
                progress.Report(i.ToString());
            }
        }
    }
    

Si noti quanto segue:

  1. Codice breve e pulito scritto in modo sequenziale senza callback e thread espliciti.
  2. Task invece di Thread .
  3. async keyword, che consente di utilizzare await che a sua volta impedisce al gestore di eventi di raggiungere lo stato di completamento fino al completamento dell'attività e nel frattempo non blocca il thread dell'interfaccia utente.
  4. Progresso della classe (vedi Interfaccia IProgress ) che supporta il principio di progettazione Separation of Concerns (SoC) e che non richiede esplicitamente dispatcher e invocazioni. Usa SynchronizationContext corrente dalla sua posizione di creazione (qui il thread dell'interfaccia utente).
  5. TaskCreationOptions.LongRunning che suggerisce di non accodare l'attività in ThreadPool .

Per un esempio più dettagliato vedi: The Future of C #: Le cose buone arrivano a coloro che "attendono" Joseph Albahari .

Vedi anche sul concetto del modello di threading UI .

Gestire le eccezioni

Il frammento di seguito è un esempio di come gestire le eccezioni e attivare la proprietà Enabled del pulsante per impedire più clic durante l'esecuzione in background.

private async void Button_Click(object sender, EventArgs e)
{
    button.Enabled = false;

    try
    {
        var progress = new Progress<string>(s => button.Text = s);
        await Task.Run(() => SecondThreadConcern.FailingWork(progress));
        button.Text = "Completed";
    }
    catch(Exception exception)
    {
        button.Text = "Failed: " + exception.Message;
    }

    button.Enabled = true;
}

class SecondThreadConcern
{
    public static void FailingWork(IProgress<string> progress)
    {
        progress.Report("I will fail in...");
        Task.Delay(500).Wait();

        for (var i = 0; i < 3; i++)
        {
            progress.Report((3 - i).ToString());
            Task.Delay(500).Wait();
        }

        throw new Exception("Oops...");
    }
}




multithreading unit-testing