Devo testare le mie sottoclassi o la mia classe genitore astratta?

10

Ho un'implementazione scheletrica, come nell'articolo 18 da Java efficace (discussione estesa qui ). È una classe astratta che fornisce 2 metodi pubblici methodA () e methodB () che chiamano i metodi delle sottoclassi per "riempire gli spazi vuoti" che non posso definire in modo astratto.

L'ho sviluppato per primo creando una classe concreta e test di unità di scrittura per esso. Quando è arrivata la seconda classe, sono stato in grado di estrarre comportamenti comuni, implementare le "lacune mancanti" nella seconda classe ed era pronto (ovviamente, i test unitari sono stati creati per la seconda sottoclasse).

Il tempo è passato e ora ho 4 sottoclassi, ognuna delle quali implementa 3 metodi protetti corti che sono molto specifici per la loro implementazione concreta, mentre l'implementazione scheletrica fa tutto il lavoro generico.

Il mio problema è che quando creo una nuova implementazione, scrivo di nuovo i test:

  • La sottoclasse chiama un determinato metodo richiesto?
  • Un determinato metodo ha una data annotazione?
  • Ottengo i risultati attesi dal metodo A?
  • Ottengo i risultati attesi dal metodo B?

Mentre vedi i vantaggi con questo approccio:

  • Documenta i requisiti della sottoclasse attraverso i test
  • Potrebbe fallire in fretta in caso di refactoring problematico
  • Verifica la sottoclasse nel suo complesso e tramite la loro API pubblica ("funziona methodA ()?" e non "funziona questo metodo protetto?")

Il problema che ho è che i test per una nuova sottoclasse sono fondamentalmente un gioco da ragazzi:  - I test sono tutti uguali in tutte le sottoclassi, cambiano solo il tipo di ritorno dei metodi (l'implementazione dello scheletro è generica) e le proprietà che controllo nella parte asserita del test.  - Di solito le sottoclassi non hanno test specifici per loro

Mi piace il modo in cui i test si focalizzano sul risultato della sottoclasse e lo proteggono dal refactoring, ma il fatto che l'implementazione del test sia diventato solo "lavoro manuale" mi fa pensare che sto facendo qualcosa di sbagliato.

Questo problema è comune durante il test della gerarchia di classi? Come posso evitarlo?

ps: ho pensato di testare la classe skeleton, ma sembra anche strano creare una simulazione di una classe astratta per poterlo testare. E penso che qualsiasi refactoring sulla classe astratta che modifica un comportamento che non è previsto dalla sottoclasse non verrà notato come veloce. Il mio coraggio mi dice che testare la sottoclasse "nel suo insieme" è l'approccio preferito qui, ma per favore sentiti libero di dirmi che ho torto:)

ps2: ho cercato su Google e ho trovato molte domande sull'argomento. Uno di questi è questo che ha un'ottima risposta di Nigel Thorne, il mio caso sarebbe stato il suo "numero 1." Sarebbe grandioso, ma a questo punto non posso refactoring, quindi dovrei convivere con questo problema. Se potessi refactoring, avrei ciascuna sottoclasse come strategia. Sarebbe OK, ma testerei comunque la "classe principale" e testerò le strategie, ma non noterei alcun refactoring che interrompa l'integrazione tra la classe principale e le strategie.

ps3: ho trovato alcune risposte che dicono che "è accettabile" testare la classe astratta. Sono d'accordo che questo è accettabile, ma mi piacerebbe sapere qual è l'approccio preferito in questo caso (sto ancora iniziando con i test unitari)

    
posta JSBach 28.10.2015 - 12:27
fonte

2 risposte

4

Non riesco a vedere come scriveresti i test per una classe base abstract ; non puoi istanziarlo, quindi non puoi avere un'istanza di esso per test. Potresti creare una sottoclasse di cemento "fittizia", solo per gli scopi del test, non so quanta utilità sarebbe. YMMV.

Ogni volta che crei una nuova implementazione (classe), dovresti scrivere dei test che esercitano questa nuova implementazione. Poiché parti della funzionalità (le "lacune") sono implementate all'interno della sottoclasse each , ciò è assolutamente necessario; l'output di ciascuno dei metodi ereditati può (e probabilmente dovrebbe ) essere diverso per ogni nuova implementazione.

    
risposta data 28.10.2015 - 13:02
fonte
3

Generalmente si crea una classe base per forzare un'interfaccia. Vuoi testare quell'interfaccia, non i dettagli di seguito. Pertanto, dovresti creare test che istanziano ogni classe individualmente, ma testano solo attraverso i metodi della classe base.

I vantaggi di questo metodo è che, poiché hai testato l'interfaccia, ora puoi eseguire il refactoring sotto l'interfaccia. Potresti scoprire che il codice di una classe figlia dovrebbe essere nel genitore o viceversa. Ora i tuoi test dimostreranno che non hai violato il comportamento quando sposti quel codice.

In generale, dovresti testare il codice su come deve essere usato. Ciò significa evitare di testare metodi privati e spesso significa che la tua unità in prova potrebbe essere un po 'più grande di quanto pensavi in origine - forse un gruppo di classi anziché una singola classe. Il tuo obiettivo dovrebbe essere quello di isolare le modifiche in modo che raramente devi modificare un test quando refactoring in un determinato ambito.

    
risposta data 28.10.2015 - 16:21
fonte

Leggi altre domande sui tag