Come evitare la necessità di testare unitamente metodi privati


15

So che non dovresti testare metodi privati, e se sembra che tu debba farlo, potrebbe esserci una classe lì dentro in attesa di venire fuori.

Ma non voglio avere un gazillion di classi solo per poter testare le loro interfacce pubbliche e trovo che per molte classi se provo solo i metodi pubblici finisco per prendere in giro molte dipendenze e i test unitari sono enorme e difficile da seguire.

Preferisco di gran lunga deridere i metodi privati ​​quando testano quelli pubblici e deridere le dipendenze esterne quando testano quelli privati.

Sono pazzo?


Un test unitario approfondito dovrebbe implicitamente coprire tutti i membri privati ​​di una determinata classe, perché mentre non puoi chiamarli direttamente, il loro comportamento avrà comunque un effetto sull'output. Se non lo fanno, allora perché sono lì in primo luogo? Ricorda nei test unitari ciò che ti interessa è il risultato, non come il risultato è stato raggiunto.
GordonM,

Risposte:


24

Hai parzialmente ragione: non dovresti testare direttamente i metodi privati. I metodi privati ​​su una classe dovrebbero essere invocati da uno o più metodi pubblici (forse indirettamente - un metodo privato chiamato da un metodo pubblico può invocare altri metodi privati). Pertanto, quando testerai i tuoi metodi pubblici, testerai anche i tuoi metodi privati. Se si dispone di metodi privati ​​che non sono stati testati, i casi di test sono insufficienti oppure i metodi privati ​​non sono utilizzati e possono essere rimossi.

Se stai adottando un approccio di test white-box, dovresti considerare i dettagli di implementazione dei tuoi metodi privati ​​quando costruisci unit test intorno ai tuoi metodi pubblici. Se stai adottando un approccio black-box, non dovresti testare i dettagli dell'implementazione nei metodi pubblici o privati, ma contro il comportamento previsto.

Personalmente, preferisco un approccio white box ai test unitari. Sono in grado di creare test per mettere i metodi e le classi sotto test in diversi stati che causano comportamenti interessanti nei miei metodi pubblici e privati ​​e quindi affermare che i risultati sono quelli che mi aspetto.

Quindi - non deridere i tuoi metodi privati. Usali per capire cosa devi testare al fine di fornire una buona copertura delle funzionalità fornite. Ciò è particolarmente vero a livello di test unitario.


1
"non deridere i tuoi metodi privati" sì, capisco i test, quando li prendi in giro potresti aver attraversato la sottile linea sottile per impazzire
Ewan,

Sì, ma come ho detto, il mio problema con i test in white box è che di solito deridere tutte le dipendenze per i metodi pubblici e privati ​​rende metodi di test davvero grandi. Qualche idea su come affrontarlo?
Fran Sevillano,

8
@FranSevillano Se dovessi stubare o deridere così tanto, guarderei il tuo design generale. Qualcosa si sente fuori.
Thomas Owens

Uno strumento di copertura del codice aiuta in questo.
Andy,

5
@FranSevillano: Non ci sono molte buone ragioni per una classe che fa una cosa per avere tonnellate di dipendenze. Se hai tonnellate di dipendenze, probabilmente hai una classe di Dio.
Mooing Duck il

4

Penso che sia una pessima idea.

Il problema con la creazione di unit test di membri privati ​​è che si adatta male al ciclo di vita del prodotto.

Il motivo per cui hai SCELTO di rendere privati ​​questi metodi è che non sono fondamentali per ciò che le tue classi stanno cercando di fare - solo aiutanti nel modo in cui attualmente implementi quella funzionalità. Come refactoring, quei dettagli privati ​​sono probabilmente candidati a cambiare e causeranno attrito quindi con refactoring.

Inoltre, una differenza fondamentale tra membri pubblici e privati ​​è che dovresti pensare attentamente alla tua API pubblica, documentarla bene e verificarla bene (affermazioni, ecc.). Ma con un'API privata, sarebbe inutile pensare con attenzione (uno sforzo sprecato poiché il suo utilizzo è così localizzato). Inserire metodi privati ​​in unit test equivale a creare dipendenze esterne da tali metodi. Significa che l'API deve essere stabile e ben documentata (dal momento che qualcuno deve capire perché quei test unitari hanno fallito se / quando lo fanno).

Ti suggerisco:

  • RIPETI se tali metodi devono essere privati
  • Scrivi test di una volta (solo per assicurarti che sia corretto ora, rendilo pubblico temporaneamente in modo da poter testare e quindi eliminare il test)
  • Costruito test condizionali nell'implementazione usando #ifdef e asserzioni (i test vengono eseguiti solo nelle build di debug).

Apprezzo la tua inclinazione a testare questa funzionalità e che è difficile farlo testare attraverso la tua attuale API pubblica. Personalmente, apprezzo la modularità più della copertura del test.


6
Non sono d'accordo. IMO, questo è corretto al 100% per i test di integrazione. Ma con i test unitari le cose sono diverse; L'obiettivo del test unitario è individuare dove si trova un bug, abbastanza stretto da poterlo risolvere rapidamente. Mi trovo spesso in questa situazione: ho pochissimi metodi pubblici perché questo è davvero il valore fondamentale delle mie lezioni (come hai detto che uno dovrebbe fare). Tuttavia, evito anche di scrivere metodi a 400 righe, né pubblici né privati. Quindi i miei pochi metodi pubblici possono raggiungere il loro obiettivo solo con l'aiuto di decine di metodi privati. Questo è troppo codice per "risolverlo rapidamente". Devo avviare il debugger ecc.
Marstato il

6
@marstato: suggerimento: inizia prima a scrivere i test e cambia idea sugli unittest: non trovano bug, ma verificano che il codice funzioni come previsto dallo sviluppatore.
Timothy Truckle,

@marstato Grazie! Sì. I test passano sempre la prima volta, quando fai il check-in (o non lo avresti fatto!). Sono utili, mentre evolvi il codice, ti danno un avvertimento quando rompi qualcosa e se hai buoni test di regressione, ti danno COMFORT / GUIDANCE su dove cercare i problemi (non nelle cose che sono stabili e testato per la regressione).
Lewis Pringle,

@marstato "L'obiettivo del test unitario è individuare dove si trova un bug" - Questo è esattamente il malinteso che porta alla domanda del PO. L'obiettivo del test unitario è verificare il comportamento previsto (e preferibilmente documentato) di un'API.
StackOverthrow

4
@marstato Il nome "test di integrazione" deriva dal test che più componenti lavorano insieme (ovvero si integrano correttamente). Il test unitario sta testando un singolo componente che fa ciò che dovrebbe fare da solo, il che significa sostanzialmente che la sua API pubblica funziona come documentata / richiesta. Nessuno di questi termini indica se si includano test della logica di implementazione interna come parte del garantire che l'integrazione funzioni o che la singola unità funzioni.
Ben

3

I test unitari verificano il comportamento osservabile del pubblico , non il codice, dove "pubblico" significa: valori di ritorno e comunicazione con dipendenze.

Una "unità" è qualsiasi codice, che risolve lo stesso problema (o più precisamente: ha lo stesso motivo per cambiare). Potrebbe essere un metodo singolo o un gruppo di classi.

Il motivo principale per cui non vuoi testare private methodsè: sono dettagli di implementazione e potresti volerli cambiare durante il refactoring (migliora il tuo codice applicando i principi OO senza cambiare la funzionalità). Questo è esattamente quando non vuoi che i tuoi unittest cambino in modo che possano garantire che il comportamento del tuo CuT non sia cambiato durante il refactoring.

Ma non voglio avere un gazillion di classi solo per poter testare le loro interfacce pubbliche e trovo che per molte classi se provo solo i metodi pubblici finisco per prendere in giro molte dipendenze e i test unitari sono enorme e difficile da seguire.

Di solito provo il contrario: più piccole sono le classi (minore è la responsabilità che hanno) meno dipendenze hanno, e più facili sono gli impegni, sia per scrivere che per leggere.

Idealmente, applichi lo stesso livello di pattern di astrazione alle tue classi. Questo significa che le tue classi forniscono una logica di business (preferibilmente come "funzioni pure" che funzionano solo sui loro parametri senza mantenere un proprio stato) (x) o chiamano metodi su altri oggetti, non entrambi contemporaneamente.

In questo modo unittesting del comportamento aziendale è un gioco da ragazzi e gli oggetti "delega" di solito sono troppo semplici per fallire (nessuna ramificazione, nessun cambiamento di stato) in modo che non sia necessario unittesting e il loro test può essere lasciato all'integrazione o ai test del modulo


1

Il test unitario ha un certo valore quando sei l'unico programmatore che lavora sul codice, specialmente se l'applicazione è molto grande o molto complessa. Il punto in cui il test unitario diventa essenziale è quando si ha un numero maggiore di programmatori che lavorano sulla stessa base di codice. Il concetto di unit test è stato introdotto per affrontare alcune delle difficoltà di lavorare in questi team più grandi.

Il motivo per cui i test unitari aiutano i team più grandi riguarda i contratti. Se il mio codice sta chiamando al codice scritto da qualcun altro, sto facendo ipotesi su cosa farà il codice dell'altra persona in varie situazioni. A condizione che tali presupposti siano ancora veri, il mio codice funzionerà comunque, ma come faccio a sapere quali presupposti sono validi e come posso sapere quando tali presupposti sono cambiati?

È qui che entrano in gioco i test unitari. L'autore di una classe crea test unitari per documentare il comportamento previsto della loro classe. Il test unitario definisce tutti i modi validi di utilizzare la classe e l'esecuzione del test unitario convalida che questi casi d'uso funzionino come previsto. Un altro programmatore che desidera utilizzare la tua classe può leggere i test unitari per comprendere il comportamento che possono aspettarsi per la tua classe e utilizzarlo come base per le loro ipotesi su come funziona la tua classe.

In questo modo le firme del metodo pubblico della classe e i test unitari formano insieme un contratto tra l'autore della classe e gli altri programmatori che fanno uso di questa classe nel loro codice.

In questo scenario cosa succede se si include il test di metodi privati? Chiaramente non ha senso.

Se sei l'unico programmatore che lavora sul tuo codice e desideri utilizzare il test unitario come metodo di debug del codice, non vedo alcun danno in questo, è solo uno strumento e puoi usarlo in qualsiasi modo per cui funzioni voi, ma non è stato questo il motivo per cui è stato introdotto il test unitario e non offre i maggiori vantaggi del test unitario.


Faccio fatica in questo perché derido le dipendenze in modo tale che se una dipendenza cambia il suo comportamento, il mio test non fallirà. Si suppone che questo errore si verifichi in un test di integrazione non nel test unitario?
Todd,

La simulazione dovrebbe comportarsi in modo identico alla reale implementazione, ma non ha dipendenze. Se l'implementazione reale cambia in modo che ora siano diverse, questo si presenterà come un fallimento del test di integrazione. Personalmente considero lo sviluppo di simulazioni e l'implementazione reale come un compito. Non costruisco beffe come parte della scrittura di unit test. In questo modo, quando cambio il comportamento delle mie classi e cambio la derisione in modo che corrisponda, l'esecuzione dei test unitari identificherà tutte le altre classi che sono state interrotte da questa modifica.
bikeman868,

1

Prima di rispondere a una domanda del genere, devi decidere cosa vuoi effettivamente ottenere.

Scrivi il codice. Speri che adempia al suo contratto (in altre parole, fa quello che dovrebbe fare. Annotare ciò che dovrebbe fare è un enorme passo in avanti per alcune persone).

Per essere ragionevolmente convinto che il codice faccia quello che dovrebbe fare, o lo fissi abbastanza a lungo, o scrivi un codice di prova che verifica abbastanza casi per convincerti "se il codice supera tutti questi test allora è corretto".

Spesso sei interessato solo all'interfaccia definita pubblicamente di alcuni codici. Se uso la libreria, non mi importa come hai fatto funziona correttamente, solo che fa il lavoro in modo corretto. Verifica che la tua biblioteca sia corretta eseguendo test unitari.

Ma stai creando la libreria. Far funzionare correttamente può essere difficile da raggiungere. Diciamo che mi interessa solo che la libreria esegua correttamente l'operazione X, quindi ho un test unitario per X. Tu, lo sviluppatore responsabile della creazione della libreria, implementa X combinando i passaggi A, B e C, che sono totalmente non banali. Per far funzionare la tua libreria, aggiungi dei test per verificare che A, B e C funzionino correttamente. Vuoi questi test. Dire "non dovresti avere unit test per metodi privati" non ha senso. Si desidera che i test per questi metodi privati. Forse qualcuno ti dice che l'unità che verifica i metodi privati ​​è sbagliata. Questo significa solo che potresti non chiamarli "test unitari" ma "test privati" o come preferisci chiamarli.

Il linguaggio Swift risolve il problema che non si desidera esporre A, B, C come metodi pubblici solo perché si desidera testarlo assegnando alle funzioni un attributo "testabile". Il compilatore consente di chiamare metodi testabili privati ​​dai test unitari, ma non dal codice non test.


0

Sì, sei pazzo .... COME UNA VOLPE!

Esistono un paio di modi per testare metodi privati, alcuni dei quali dipendono dalla lingua.

  • riflessione! le regole sono fatte per essere infrante!
  • renderli protetti, ereditare e sovrascrivere
  • amico / InternalsVisiblePer le classi

Nel complesso, tuttavia, se si desidera testare metodi privati, probabilmente si desidera spostarli su metodi pubblici su una dipendenza e testarli / iniettarli.


Non lo so, ai miei occhi mi hai spiegato che non è una buona idea ma hai continuato a rispondere alla domanda
Liath

Pensavo di avere coperto tutte le basi :(
Ewan,

3
Ho votato a favore, perché l'idea di esporre i tuoi metodi di supporto privato all'API pubblica solo per testarli è pazza, e l'ho sempre pensato.
Robert Harvey,

0

Sii pragmatico. Testare casi speciali in metodi privati ​​mediante l'impostazione dello stato dell'istanza e dei parametri su un metodo pubblico in modo che tali casi si verifichino lì, è spesso troppo complicato.

Aggiungo un internalaccessorio aggiuntivo (insieme alla bandiera InternalsVisibleTodel gruppo di test), chiaramente denominato DoSomethingForTesting(parameters), al fine di testare quei metodi "privati".

Naturalmente, l'implementazione può cambiare in qualche modo e quei test, inclusi gli accessori per i test, diventano obsoleti. È ancora meglio di casi non testati o test illeggibili.

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.