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


12

Ho un'implementazione scheletrica, come nell'articolo 18 di Effective Java (discussione estesa qui ). È una classe astratta che fornisce 2 metodi pubblici methodA () e methodB () che chiamano i metodi delle sottoclassi per "colmare le lacune" che non posso definire in modo astratto.

L'ho sviluppato prima creando una classe concreta e scrivendone dei test unitari. Quando è arrivata la seconda classe, sono stato in grado di estrarre il comportamento comune, implementare gli "spazi 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 brevi 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 determinata annotazione?
  • Ottengo i risultati previsti dal metodo A?
  • Ottengo i risultati previsti dal metodo B?

Mentre vedi i vantaggi con questo approccio:

  • Documenta i requisiti della sottoclasse attraverso i test
  • Potrebbe fallire rapidamente in caso di refactoring problematico
  • Testa la sottoclasse nel suo insieme e attraverso 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 semplicemente il tipo di ritorno dei metodi (l'implementazione dello scheletro è generica) e le proprietà I controllare la parte affermativa del test. - Di solito le sottoclassi non hanno test specifici per loro

Mi piace il modo in cui i test si concentrano sul risultato della sottoclasse stessa e la proteggono dal refactoring, ma il fatto che l'implementazione del test sia diventato solo un "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 poterla testare. E penso che qualsiasi refactoring sulla classe astratta che cambia il comportamento che non è previsto dalla sottoclasse non sarebbe notato così velocemente. Le mie viscere mi dicono che testare la sottoclasse "nel suo insieme" è l'approccio preferito qui, ma non esitate a dirmi che mi sbaglio :)

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

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


2
Scrivi una classe di test astratta e fai ereditare i tuoi test concreti, rispecchiando la struttura del codice aziendale. I test dovrebbero testare il comportamento e non l'implementazione di un'unità, ma ciò non significa che non è possibile utilizzare le proprie conoscenze privilegiate sul sistema per raggiungere l'obiettivo in modo più efficiente.
Kilian Foth,

Ho letto da qualche parte che non si dovrebbe usare l'ereditarietà nei test, sto cercando la fonte per leggerlo attentamente
JSBach,

4
@KilianFoth sei sicuro di questo consiglio? Sembra una ricetta per test unitari fragili, altamente sensibili alle modifiche di progettazione e quindi non molto mantenibili.
Konrad Morawski,

Risposte:


5

Non riesco a vedere come scriveresti i test per una classe base astratta ; non puoi istanziarlo, quindi non puoi averne un'istanza da testare. Potresti creare una sottoclasse concreta "fittizia" solo allo scopo di testarla - non sei sicuro di quanto sarebbe utile. YMMV.

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


Sì, sono diversi (i tipi e i contenuti sono diversi). Tecnicamente parlando, sarei in grado di deridere questa classe o avere una semplice implementazione solo a scopo di test con implementazioni vuote. Non mi piace questo approccio. Ma il fatto che non "abbia bisogno" di pensare di superare i test in nuove implementazioni mi fa sentire strano e mi fa anche domande su quanto sono sicuro in questo test (ovviamente, se cambio di proposito la classe di base, il i test falliscono, il che dimostra che posso fidarmi di loro)
JSBach,

4

In genere si crea una classe di base per applicare un'interfaccia. Vuoi testare quell'interfaccia, non i dettagli di seguito. Pertanto, è necessario creare test che istanziano ogni classe singolarmente ma testano solo attraverso i metodi della classe base.

I vantaggi di questo metodo sono che, poiché hai testato l'interfaccia, ora puoi eseguire il refactoring sotto l'interfaccia. Potresti scoprire che il codice in una classe figlio dovrebbe essere nel genitore o viceversa. Ora i tuoi test dimostreranno che non hai interrotto 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 l'unità in prova potrebbe essere un po 'più grande di quanto si pensasse inizialmente - forse un gruppo di classi anziché una singola classe. Il tuo obiettivo dovrebbe essere quello di isolare le modifiche in modo da dover cambiare raramente un test quando esegui il refactoring in un determinato ambito.

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.