Come posso sostenere i test unitari su codice privato?


15

Sto cercando di sostenere i test unitari nel mio gruppo di lavoro, ma un'obiezione che ottengo spesso è che dovrebbe essere usato solo per API esportate esternamente (che è solo una parte minima e non critica del nostro sistema), e non su interno e privato codice (che ora ha solo test funzionali).

Mentre penso che il test unitario possa e debba essere applicato a tutto il codice, come posso convincere i miei colleghi?


3
Se hai metodi privati ​​che senti il ​​bisogno di testare, questo è spesso un segno che il tuo codice sta violando l'SRP e c'è un'altra classe che chiede di essere estratta e testata a sé stante.
Paddyslacker,

@Paddyslacker: sento che tutto il codice deve essere testato. Non vedo perché un'unità di codice che segue il principio della responsabilità singola non dovrebbe essere soggetta a test di unità ...
Wizard79

4
@lorenzo, ti sei perso il punto; forse non sono riuscito molto bene. Se estrai questi metodi privati ​​in un'altra classe, ora dovranno essere accessibili dalla tua classe originale. Poiché i metodi sono ora pubblici, dovranno essere testati. Non intendevo dire che non dovevano essere testati, intendevo dire che se senti la necessità di testare direttamente i metodi, è probabile che non debbano essere privati.
Paddyslacker,

@Paddyslacker: Sento la necessità di testare direttamente anche metodi privati. Perché pensi che non dovrebbero essere privati?
Wizard79,

6
Testando metodi privati ​​stai rompendo l'astrazione. Dovresti testare lo stato e / o il comportamento, non l'implementazione, nei test unitari. I tuoi esempi / scenari dovrebbero essere in grado di verificare a cosa serve il risultato del codice privato - se lo trovi difficile, come dice Paddyslacker potrebbe significare che stai violando SRP. Potrebbe anche significare, tuttavia, che non hai distillato i tuoi esempi per essere veramente rappresentativo di ciò che sta facendo il tuo codice.
FinnNk,

Risposte:


9

I tuoi colleghi potrebbero confondere i veri test unitari con i test di integrazione. Se il tuo prodotto è (o ha) un'API, i test di integrazione possono essere programmati come casi di test NUnit. Alcune persone credono erroneamente che si tratti di unit test.

Puoi provare a convincere i tuoi colleghi con quanto segue (sono sicuro che conosci già queste cose, tutto quello che sto dicendo è che indicarle ai tuoi colleghi potrebbe essere d'aiuto):

  • Copertura di prova . Misurare la percentuale effettiva di copertura del test di tali test di integrazione. Questo è un controllo di realtà per coloro che non hanno mai eseguito la copertura dei test. Poiché è difficile esercitare tutti i percorsi logici quando l'input si trova a diversi strati di distanza, la copertura del test supera tra il 20% e il 50%. Per ottenere una maggiore copertura, i colleghi devono scrivere test unitari reali e isolati.
  • Configurazione . Distribuisci lo stesso software in prova e forse puoi dimostrare ai tuoi colleghi quanto sia difficile eseguire i loro test in un ambiente diverso. Percorsi a vari file, stringhe di connessione DB, URL di servizi remoti, ecc. - Tutto sommato.
  • Tempo di esecuzione . A meno che i test non siano veri test unitari e possano essere eseguiti in memoria, impiegheranno molto tempo per essere eseguiti.

12

I motivi per utilizzare i test unitari sul codice interno / privato sono esattamente gli stessi delle API supportate esternamente:

  • Impediscono il ripetersi di bug (test unitari fanno parte della suite di test di regressione).
  • Documentano (in un formato eseguibile!) Che il codice funziona.
  • Forniscono una definizione eseguibile di ciò che significa "il codice funziona".
  • Forniscono un mezzo automatizzato per dimostrare che il codice corrisponde effettivamente alle specifiche (come definito dal punto sopra).
  • Mostrano come l'unità / classe / modulo / funzione / metodo falliscono in presenza di input imprevisti.
  • Forniscono esempi su come utilizzare l'unità, che è un'ottima documentazione per i nuovi membri del team.

8

Se intendi privato nel modo in cui penso che lo intendi, allora no - non dovresti provare l'unità. Dovresti essere solo test unit comportamento / stato osservabile. Potrebbe mancare il punto dietro il ciclo "refactor rosso-verde" di TDD (e se non si esegue prima il test, si applica lo stesso principio). Una volta che i test sono stati scritti e superati, non si desidera che cambino durante l'esecuzione del refactoring. Se sei costretto a testare le funzionalità private dell'unità, probabilmente significa che i test delle unità attorno alla funzionalità pubblica sono difettosi. Se è difficile e complesso scrivere test attorno al codice pubblico, forse la tua classe sta facendo troppo o il tuo problema non è chiaramente definito.

Peggio ancora, nel tempo i tuoi test unitari diventeranno una palla al piede che ti rallenta senza aggiungere alcun valore (la modifica dell'implementazione, ad esempio l'ottimizzazione o la rimozione della duplicazione, non dovrebbe avere alcun effetto sui test unitari). Tuttavia, il codice interno dovrebbe essere testato in unità poiché il comportamento / stato è osservabile (solo in modo limitato).

Quando ho fatto i test unitari per la prima volta, ho tirato tutti i tipi di trucchi per testare le unità private ma ora, con alcuni anni alle spalle, lo vedo peggio di una perdita di tempo.

Ecco un esempio un po 'sciocco, ovviamente nella vita reale avresti più test di questi:

Supponiamo che tu abbia una classe che restituisce un elenco ordinato di stringhe: dovresti verificare che il risultato sia ordinato, non come ordinarlo effettivamente. È possibile avviare l'implementazione con un singolo algoritmo che ordina semplicemente l'elenco. Una volta fatto, il test non deve cambiare se si modifica l'algoritmo di ordinamento. A questo punto hai un singolo test (supponendo che l'ordinamento sia incorporato nella tua classe):

  1. Il mio risultato è ordinato?

Ora supponiamo che tu voglia due algoritmi (forse uno è più efficiente in alcune circostanze ma non altri), quindi ogni algoritmo potrebbe (e in generale, dovrebbe) essere fornito da una classe diversa e la tua classe sceglierà da loro - puoi verificare che ciò avvenga per i tuoi scenari scelti usando simulazioni, ma il tuo test originale è ancora valido e poiché stiamo verificando solo comportamenti / stati osservabili, non è necessario cambiare. Si finisce con 3 test:

  1. Il mio risultato è ordinato?
  2. Dato uno scenario (diciamo che l'elenco iniziale è quasi ordinato per cominciare) viene fatta una chiamata alla classe che ordina le stringhe usando l'algoritmo X?
  3. Dato uno scenario (l'elenco iniziale è in ordine casuale) viene effettuata una chiamata alla classe che ordina le stringhe usando l'algoritmo Y?

L'alternativa sarebbe stata quella di iniziare a testare il codice privato all'interno della tua classe - non ottieni nulla da questo - i test di cui sopra mi dicono tutto ciò che devo sapere per quanto riguarda i test unitari. Aggiungendo test privati ​​ti stai costruendo una giacca dritta, quanto più lavoro sarebbe se non solo controllassi che il risultato fosse ordinato, ma anche come fosse ordinato?

I test (di questo tipo) dovrebbero cambiare solo quando cambia il comportamento, iniziare a scrivere test contro il codice privato e questo esce dalla finestra.


1
Forse c'è un malinteso sul significato di "privato". Nel nostro sistema il 99% del codice è "privato", quindi abbiamo una piccola API per l'automazione / il controllo remoto di uno dei componenti del sistema. Intendo test unitari del codice di tutti gli altri moduli.
Wizard79,

4

ecco un'altra ragione: nel caso ipotetico dovrei scegliere tra test unitari dell'API esterna rispetto alle parti private, sceglierei le parti private.

Se ogni parte privata è coperta da un test, anche l'API composta da queste parti private dovrebbe essere coperta per quasi il 100%, tranne per il livello superiore. Ma è probabile che sia uno strato sottile.

D'altra parte, quando si verifica solo l'API, può essere davvero difficile coprire completamente tutti i possibili percorsi del codice.


+1 "d'altra parte ..." Ma se non altro, aggiungi dei test in cui un guasto danneggerebbe di più.
Tony Ennis,

2

È difficile convincere la gente ad accettare i test unitari perché sembra una perdita di tempo ("potremmo codificare un altro progetto per fare soldi!") O ricorsivo ("E poi dobbiamo scrivere casi di test per i casi di test!") Sono colpevole di dire entrambi.

La prima volta che trovi un bug, devi affrontare la verità che non sei perfetto (quanto velocemente dimentichiamo i programmatori!) E vai "Hmmm".


Un altro aspetto del test unitario è che il codice deve essere scritto per essere testabile. Rendersi conto che Some Code è facilmente testabile e Some Code non lo rende un buon programmatore "Hmmm".


Hai chiesto al tuo collega perché il test unitario era utile solo per le API rivolte verso l'esterno?


Un modo per mostrare il valore del test unitario è attendere che si verifichi un brutto bug e quindi mostrare come il test unitario avrebbe potuto impedirlo. Questo non è quello di strofinarlo in faccia, questo è, nelle loro menti, spostare i test unitari da una Teoria della Torre d'Avorio a una realtà in trincea.

Un altro modo è attendere fino a quando lo stesso errore si verifica due volte . "Uhhh, beh, Boss, abbiamo aggiunto del codice per testare un valore nullo dopo il problema della scorsa settimana, ma questa volta l'utente ha inserito una cosa vuota!"


Dare l'esempio. Scrivi unit test per il TUO codice, quindi mostra il valore al tuo capo. Quindi vedi se il capo chiamerà la pizza a pranzo un giorno e farà una presentazione.


Alla fine, non posso dirti il ​​sollievo che provo quando stiamo per spingere in avanti e ottengo una barra verde dai test unitari.


2

Esistono due tipi di codice privato: il codice privato che viene chiamato dal codice pubblico (o il codice privato che viene chiamato dal codice privato che viene chiamato dal codice pubblico (o ...)) e il codice privato che lo fa non alla fine vengono chiamati dal pubblico codice.

Il primo viene già testato attraverso i test per il codice pubblico. Quest'ultimo non può essere chiamato affatto e quindi dovrebbe essere eliminato, non testato.

Nota che quando fai TDD è impossibile che esista un codice privato non testato.


Nel nostro sistema il 99% del codice è del terzo tipo : privato, non chiamato da codice pubblico ed essenziale per il sistema (solo una parte minima del nostro sistema ha un'API pubblica esterna).
Wizard79,

1
"Nota che quando fai TDD è impossibile che esista un codice privato non testato." <- elimina un caso di test, senza sapere che il test è l'unico test a coprire un determinato ramo. OK, questo è il codice più "attualmente non testato", ma è abbastanza facile vedere un banale refactoring che cambia quel codice ... solo la tua suite di test non lo copre più.
Frank Shearar,

2

Il test unitario consiste nel testare unità del tuo codice. Sta a te definire cos'è un'unità. I tuoi colleghi definiscono le unità come elementi API.

Comunque, testare l'API dovrebbe comportare anche l'esercizio del codice privato. Se definisci la copertura del codice come indicatore dell'avanzamento del test unitario, finirai per testare tutto il tuo codice. Se una parte del codice non è stata raggiunta, dai ai tuoi colleghi tre scelte:

  • definire un altro caso di prova per coprire quella parte,
  • analizzare il codice per giustificare il motivo per cui non può essere coperto nel contesto di test unitari ma dovrebbe essere coperto in altre situazioni,
  • rimuovere il codice morto che non è stato coperto né giustificato.

Nel nostro sistema l'API è solo una parte minima, che consente l'automazione / il controllo remoto per un'applicazione di terze parti. Testare solo gli account API per una copertura del codice dell'1% ...
Wizard79
Utilizzando il nostro sito, riconosci di aver letto e compreso le nostre Informativa sui cookie e Informativa sulla privacy.
Licensed under cc by-sa 3.0 with attribution required.