Test unitario che asserisce che il thread corrente è il thread principale

7

La domanda è che ha senso scrivere un test unitario che asserisce che il thread corrente è il thread principale? Pro / contro?

Recentemente ho visto il test unitario che asserisce il thread corrente per il callback del servizio. Non sono sicuro, è una buona idea, credo che sia più una sorta di test di integrazione. A mio parere, il test unitario dovrebbe affermare il metodo isolatamente e non dovrebbe conoscere la natura del consumatore del servizio.

Ad esempio, in iOS, l'utente di questo servizio deve essere un'interfaccia utente che per impostazione predefinita ha un vincolo per eseguire un codice nel thread principale.

    
posta Vladimir Kaltyrin 14.12.2018 - 11:58
fonte

8 risposte

9

Lascia che ti mostri i miei principi di test unitari preferiti:

A test is not a unit test if:

  1. It talks to the database
  2. It communicates across the network
  3. It touches the file system
  4. It can't run at the same time as any of your other unit tests
  5. You have to do special things to your environment (such as editing config files) to run it.

A Set of Unit Testing Rules - Michael Feathers

Se il tuo test dell'unità insiste sul fatto che è il "thread principale", è difficile non eseguire il controllo del numero 4.

Questo può sembrare duro, ma ricorda che un test unitario è solo uno dei molti diversi tipi di test.

Sei libero di violare questi principi. Ma quando lo fai, non chiamare il test un test unitario. Non metterlo con i test unitari reali e rallentarli.

    
risposta data 14.12.2018 - 18:14
fonte
4

Dovresti scrivere un test unitario che verifica se un thread è il thread principale? Dipende, ma probabilmente non è una buona idea.

Se il punto di un test unitario è inteso a verificare il corretto funzionamento di un metodo, quindi testare questo aspetto è quasi certamente non testare questo aspetto. Come hai detto tu stesso, è concettualmente più un test di integrazione, poiché è improbabile che il corretto funzionamento di un metodo cambi in base a quale thread lo sta eseguendo, e quello responsabile per determinare quale thread viene utilizzato è il chiamante e non il metodo stesso.

Tuttavia, questo è il motivo per cui dico che dipende, perché potrebbe benissimo dipendere dal metodo stesso, nel caso in cui il metodo generi nuovi thread. Anche se con ogni probabilità questo non è il tuo caso.

Il mio consiglio è di lasciare questo controllo out del test dell'unità e portarlo a un test di integrazione corretto per questo aspetto.

    
risposta data 14.12.2018 - 12:15
fonte
2

should not know about the nature of the consumer of service.

Quando un consumatore consuma un servizio, esiste un "contratto" espresso o implicito su quale funzionalità viene fornita e in che modo. Le restrizioni sul thread in cui può avvenire la richiamata fanno parte di quel contratto.

Presumo che il codice venga eseguito in un contesto in cui è presente una sorta di "motore eventi" che viene eseguito nel "thread principale" e un modo per altri thread di richiedere che il codice delle pianificazioni del motore di eventi venga eseguito nel thread principale .

Nella visione più pura dei test unitari, prendi in giro assopitamente ogni dipendenza. Nel mondo reale pochissime persone lo fanno. Il codice in prova è autorizzato a utilizzare le versioni reali di almeno le funzioni di base della piattaforma.

Quindi la vera domanda IMO è che consideri il motore degli eventi e il supporto del threading di runtime essere parte delle "funzionalità di base della piattaforma" a cui i test di unità possono dipendere o meno? Se lo fai, ha perfettamente senso verificare se avviene una chiamata sul "thread principale". Se stai prendendo in giro il motore degli eventi e il sistema di threading, dovrai progettare i tuoi mock in modo che possano determinare se il callback si sarebbe verificato nel thread principale. Se stai prendendo in giro il motore degli eventi ma non il supporto del thread di runtime, vorrai testare se la callback avviene sul thread in cui viene eseguito il tuo motore di eventi simulati.

    
risposta data 14.12.2018 - 19:13
fonte
1

Posso capire perché sarebbe un test interessante. Ma cosa testerebbe effettivamente? Diciamo che abbiamo la funzione

showMessage(string m)
{
    new DialogueBox(m).Show(); //will fail if not called from UI thread
}

Ora posso testarlo ed eseguirlo in un thread non dell'interfaccia utente, dimostrando che è stato generato un errore. Ma per fare in modo che il test abbia un qualche valore, la funzione deve avere un comportamento specifico che si vuole che faccia quando viene eseguito in un thread non dell'interfaccia utente.

showMessage(string m)
{
    try {
        new DialogueBox(m).Show(); //will fail if not called from UI thread
    }
    catch {
        Logger.Log(m); //alternative display method
    }
}

Ora ho qualcosa da testare, ma non è chiaro che questo tipo di alternativa sarebbe utile in una vita reale

    
risposta data 14.12.2018 - 17:21
fonte
1

Se la richiamata è documentata per non essere thread-safe da invocare su qualsiasi altro da, ad esempio, il thread dell'interfaccia utente o il thread principale, allora sembra perfettamente ragionevole in un test di unità di un modulo di codice che causa l'invocazione di questo callback per assicurarti che non stia facendo qualcosa che non sia thread-safe lavorando al di fuori del thread che il sistema operativo o il framework dell'applicazione è documentato per garantire la sicurezza.

    
risposta data 15.12.2018 - 19:40
fonte
1

Disponi
atto
Segnala

Un test unitario dovrebbe affermare che si trova su un thread specifico? No, perché non sta testando un'unità in quel caso, non è nemmeno un test di integrazione, in quanto non dice nulla sulla configurazione che si svolge nell'unità ...

Affermare quale thread è attivo il test, sarebbe come dichiarare il nome del tuo server di test, o meglio ancora il nome del metodo di test.

Ora potrebbe avere senso organizzare il test da eseguire su un thread specifico, ma solo quando si vuole essere sicuri che restituirà un risultato specifico basato sul thread.

    
risposta data 15.12.2018 - 22:27
fonte
1

Se si dispone di una funzione documentata che viene eseguita correttamente solo sul thread principale e tale funzione viene chiamata su un thread in background, non si tratta di un errore delle funzioni. Il guasto è colpa del chiamante, quindi i test di unità sono inutili.

Se il contratto viene modificato in modo che la funzione venga eseguita correttamente sul thread principale, e segnala un errore durante l'esecuzione su un thread in background, puoi scrivere un test di unità per quello, dove lo chiami una volta sul thread principale e quindi su un thread in background e controlla che si comporti come documentato. Quindi ora hai un test unitario, ma non un test unitario che verifica che venga eseguito sul thread principale.

Suggerirei vivamente che la prima riga della funzione dovrebbe affermare che venga eseguita sul thread principale.

    
risposta data 16.12.2018 - 11:36
fonte
1

Il test delle unità è utile solo se verifica la corretta implementazione di detta funzione, non l'uso corretto, perché un test unitario non può dire che una funzione non verrà chiamata da un altro thread nel resto della base di codice, proprio come non si può dire che le funzioni non vengano chiamate con parametri che violano le loro precondizioni (e tecnicamente ciò che si sta tentando di testare è fondamentalmente una violazione di una precondizione nell'uso, che è qualcosa a cui non si può testare efficacemente perché il il test non può limitare il modo in cui altri posti nella base di codici usano tali funzioni, ma puoi verificare se le violazioni delle condizioni preliminari portano ad errori / eccezioni approriate, comunque).

Questo è un caso di asserzione per me nell'implementazione delle funzioni rilevanti come alcuni altri hanno sottolineato, o anche più sofisticato è quello di assicurarsi che le funzioni siano sicure per i thread (anche se questo non è sempre pratico quando si lavora con alcuni API).

Anche solo una nota a margine ma "thread principale"!="Thread UI" in tutti i casi. Molte API della GUI non sono thread-safe (e creare un kit GUI thread-safe è dannatamente difficile), ma ciò non significa che devi invocarle dallo stesso thread di quello che ha il punto di ingresso per la tua applicazione. Ciò potrebbe essere utile anche nell'implementare le asserzioni all'interno delle relative funzioni dell'interfaccia utente per distinguere "thread UI" da "thread principale", come catturare l'ID del thread corrente quando viene creata una finestra da confrontare invece che dal punto di ingresso principale dell'applicazione almeno riduce la quantità di ipotesi / limitazioni d'uso che l'implementazione si applica solo a ciò che è veramente rilevante).

La sicurezza del thread era in realtà il punto di partenza "gotcha" in una mia ex squadra, e nel nostro caso specifico l'avrei etichettata come la "micro-ottimizzazione" più controproducente di tutti i tipi che hanno subito più manutenzione costi di un assemblaggio anche a mano. Abbiamo avuto una copertura del codice piuttosto completa nei nostri test unitari, insieme a test di integrazione piuttosto sofisticati, solo per incontrare deadlock e condizioni di gara nel software che ha eluso i nostri test. E questo perché gli sviluppatori hanno codificato a caso codice senza essere consapevoli di ogni singolo effetto collaterale che potrebbe verificarsi nella catena di funzioni chiamate che risulterebbero dalle loro stesse, con un'idea piuttosto ingenua di poter correggere tali errori col senno di poi semplicemente lanciando si blocca a destra e a sinistra, e forse anche a ottenere un falso senso di sicurezza dalla copertura del test *.

I was skewed in the opposite direction as an old school type that distrusted multithreading, was a real latecomer to embracing it, and thought correctness beats performance to the point of rarely ever getting use out of all these cores we have now, until I discovered things like pure functions and immutable designs and persistent data structures which finally allowed me to fully utilize that hardware without a worry in the world about race conditions and deadlocks. I must admit that all the way up until 2010 or so, I hated multithreading with a passion except for a few parallel loops here and there in areas that are trivial to reason about thread-safety, and favored much more sequential code for the design of products given my grief with multithreading in former teams.

Per me, il modo di multithreading prima e correggere bug in seguito è una strategia terribile per il multithreading al punto da farmi odiare inizialmente il multithreading; o assicurati che i tuoi progetti siano sicuri per i thread e che le loro implementazioni utilizzino solo funzioni con garanzie simili (ad es. funzioni pure), oppure eviti il multithreading. Ciò potrebbe sembrare un po 'dogmatico, ma è meglio scoprire (o peggio, non scoprire) problemi difficili da riprodurre con il senno di poi che sfuggono ai test. Non ha senso ottimizzare un motore a razzo se questo si tradurrà nel renderlo incline a esplodere improvvisamente dal nulla a metà del suo viaggio verso lo spazio.

Se si deve inevitabilmente lavorare con codice che non è thread-safe, allora non lo vedo come un problema da risolvere con test di unità / integrazione. L'ideale sarebbe limitare l'accesso . Se il codice della GUI è disgiunto dalla logica aziendale, è possibile applicare un disegno che limiti l'accesso a tali chiamate da qualsiasi cosa che non sia il thread / oggetto che lo crea *. Questa è la modalità ideale per me è quella di rendere impossibile per gli altri thread chiamare quelle funzioni piuttosto che cercare di assicurarmi che non lo facciano.

  • Yes, I realize that there's always ways around whatever design restrictions you enforce typically where the compiler can't protect you. I'm just speaking practically; if you can abstract "GUI Thread" object or whatever, then it might be the only one handed a parameter to the GUI objects/functions, and you might be able to restrict that object from having access to other threads. Of course it might be able to bypass and dig deep and work its way around such hoops to pass said GUI functions/objects to other threads to invoke, but at least there's a barrier there, and you can call anyone who does that an "idiot", and not be in the wrong, at least, for clearly bypassing and seeking loopholes for what the design obviously was attempting to restrict. :-D That's actually a very practical litmus test to me is like how confidently you can call someone an "idiot" in misusing a design, without the risk of you actually being the idiot for designing something prone to misuse.
    
risposta data 18.12.2018 - 19:12
fonte