Ho affrontato questo problema diverse volte negli ultimi anni quando ho scritto 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 sui test. La mia risposta è indirizzata ai casi in cui non esiste alternativa al codice multithread; Copro i problemi di progettazione del codice per completezza, ma discuto anche dei test unitari.
Scrittura di codice multithread verificabile
La prima cosa da fare è separare il codice di gestione del thread di produzione da tutto il codice che esegue l'elaborazione effettiva dei dati. In questo modo, l'elaborazione dei dati può essere testata come codice a thread singolo e l'unica cosa che fa il codice multithread è 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 soppiatto nella produzione, che saranno difficili da riprodurre anche in fase di produzione e causeranno quindi i maggiori problemi. Per questo motivo, l'approccio di codifica standard di scrivere rapidamente il codice e quindi eseguirne il debug fino a quando funziona non è una cattiva idea per il codice multithread; si tradurrà in codice in cui vengono corretti i bug facili e i bug pericolosi sono ancora lì.
Invece, quando si scrive codice multithread, è necessario scrivere il codice con l'atteggiamento che si eviterà di scrivere i bug 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 dei casi una dozzina di righe - che hai la possibilità di scriverlo senza scrivere un bug e certamente senza scrivere molti bug , se capisci il threading, prenditi il tuo tempo e stai attento.
Scrittura di unit test per codice multithread
Una volta che il codice multithread è stato scritto nel modo più accurato possibile, vale comunque la pena scrivere test per quel codice. Lo scopo principale dei test non è tanto quello di testare i bug delle condizioni di gara altamente dipendenti dal tempo - è impossibile testare ripetutamente tali condizioni di gara - ma piuttosto testare che la vostra strategia di blocco per prevenire tali bug consenta a più thread di interagire come previsto .
Per testare correttamente il corretto comportamento di blocco, un test deve avviare 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, perché ciò maschererà i bug che potrebbero verificarsi nella produzione in cui i thread non sono sincronizzati esternamente. Ciò 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, perché differenze di temporizzazione minori, ad esempio tra macchine diverse su cui è possibile eseguire i test, possono causare l'interruzione del tempo e il fallimento del test. Quello che ho fatto in genere è iniziare con ritardi che causano errori nei test, aumentano i ritardi in modo che il test passi in modo affidabile sulla mia macchina di sviluppo e quindi raddoppiano i ritardi oltre a ciò in modo che il test abbia buone probabilità di passare ad altre macchine. Ciò significa che il test richiederà una quantità macroscopica di tempo, anche se nella mia esperienza, un'attenta progettazione del test può limitare quel 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, questo dovrebbe essere accettabile per la tua suite di test.
Infine, tieni traccia del numero di bug rilevati dal tuo test. Se il test ha una copertura del codice dell'80%, è possibile che si verifichino circa l'80% dei bug. Se il test è ben progettato ma non rileva alcun bug, è probabile che non si disponga di altri bug che verranno visualizzati solo in produzione. Se il test rileva uno o due bug, potresti comunque essere fortunato. Oltre a ciò, e potresti voler prendere in considerazione un'attenta revisione o addirittura una completa riscrittura del codice di gestione dei thread, poiché è probabile che il codice contenga ancora bug nascosti che saranno molto difficili da trovare fino a quando il codice non sarà in produzione, e molto difficile da risolvere allora.