Implementazione della logica comune nella classe base

6

Sfondo

Nella documentazione di un progetto su cui sto lavorando mi sono imbattuto nella seguente frase che ha immediatamente attivato un allarme per me:

when having several concrete classes that inherits from the same base class, logic which is common for all sub classes should NOT be implemented in the base class.

Questa è una dichiarazione piuttosto audace che tipo di conflitti con tutto ciò che penso di OOP.

Il motivo dichiarato era testabilità. Ha detto che quando si scrivono i test delle unità, se una logica comune è implementata nella classe base, sarà testata più volte per ogni sottoclasse concreta e non può essere derisa.

Domanda

Dove o come si può implementare una logica comune per più sottoclassi in modo che possa essere facilmente derisa quando vengono testate?

    
posta Omribitan 25.03.2015 - 15:39
fonte

3 risposte

2

Sei incappato in uno dei problemi più grossi che tende a essere spazzato sotto il tappeto nelle discussioni sui test unitari / TDD. Il codice ben progettato da una prospettiva orientata agli oggetti è generalmente difficile da testare, il codice che è facile scrivere i test unitari di solito sta compromettendo alcuni paradigmi di progettazione orientata agli oggetti.

La maggior parte degli approcci ai test unitari tendono a guidare verso un design che rende più semplice il test delle unità, il che non è male a seconda di quali regole si piegano o si rompono. Questi disegni tendono a esporre troppo i metodi in modo che possano essere più facilmente isolati, utilizzando interfacce o classi di utilità per gestire codice comune.

L'iniezione di dipendenza è uno dei modi più popolari per gestire queste situazioni. Qui è un esempio piuttosto semplice in C #. Essenzialmente, piuttosto che ereditare direttamente da una classe base, ogni classe implementerà invece un'interfaccia che dipende da un oggetto che gestisce le operazioni comuni. In questo modo puoi semplicemente passare MyMockedCommonClass invece di MyCommonClass nei test di unità. Ciò consente anche al tuo codice di essere ancora abbastanza orientato agli oggetti.

    
risposta data 25.03.2015 - 16:35
fonte
3

Se questo comportamento comune vede un uso frequente, sarà sicuramente eseguito più volte durante i test per ciascuna sottoclasse, ma lo farà anche in fase di produzione in produzione, non c'è niente di sbagliato in esso. Tuttavia, ciò non significa che dovresti testare la logica comune più volte.

Concrete / abstract superclass

Se la superclasse non è astratta e il comportamento pubblico, dovresti avere un test per questo. Chiaro e semplice. Non è necessario ripetere il test per ogni bambino.

Ciò che ho trovato spesso è che per lo più non hai superclassi concrete ma astratte. Il comportamento comune contenuto in una classe astratta non viene testato di per sé ma come parte dei test delle classi derivate. Non lo vedo come un problema, è come quando un metodo privato viene testato indirettamente come parte di un test del metodo pubblico.

Mocking

Non dovresti aver bisogno di prendere in giro o stubare comportamenti comuni da una classe base (o da uno dei tuoi metodi privati, che è esattamente lo stesso), perché anche ciò che appartiene alla tua superclasse appartiene a te e provare a deriderti è stupido.

Se ritieni che sia necessario, è probabile che la tua classe non sia sufficientemente coesa e dovresti estrarre tale comportamento in una classe separata separata invece di utilizzare l'ereditarietà.

    
risposta data 25.03.2015 - 17:33
fonte
3

Assurdità assoluta.

Alcuni pensieri:

  1. Non dovresti prendere OOP ed i suoi principi troppo sul serio. Molte volte i paradigmi software o funzionali hanno più senso. Anche se la cosa degli oggetti è bella ... pensare in componenti concreti e tutto il resto.

  2. I test, e in particolare i test unitari sono intesi per servire il progetto, verificarne la progettazione e assicurarne la robustezza non il contrario.

  3. La composizione è quasi sempre migliore dell'ereditarietà

    a proposito, questo principio funziona egregiamente con OOP, l'enorme OO gli alberi dell'ereditarietà degli anni '90 erano davvero pessimi interpretazione di OOP. Ti suggerisco anche di cercare la Gang originale di Four book on Design Patterns

Alla tua domanda:

Se diverse classi sono essenzialmente la stessa "cosa" e hanno molte funzionalità comuni, dovresti farle ereditare dalla stessa classe (supponendo che tu stia utilizzando un linguaggio di programmazione OO ovviamente ...).

Ciò non limita in alcun modo la testabilità.

Per testare la classe genitore si crea una sottoclasse per il bene del test (mettila accanto alla classe test). questo è esattamente il modo in cui la classe è destinata a essere utilizzata, e ciò che è il design impone.

Per testare i bambini puoi fare due cose:

  1. L'approccio sbagliato. questo vale solo per le lingue dinamiche, C # e forse Java. puoi creare uno Shim da posare come supper-class e usarlo come classe base per il bambino.

  2. Accetta che la super-classe è una parte essenziale di ciò che è la sottoclasse. Per progettazione . Deve essere conforme ai comportamenti definiti nella classe cena, e in realtà dovresti testarli insieme. Un buon esempio di questi sono i modelli di progettazione della strategia e del modello. Ora puoi semplicemente testare la sottoclasse così com'è. dovresti anche astenermi dal testare troppo la classe base, perché l'hai già fatto da qualche altra parte.

risposta data 25.03.2015 - 18:50
fonte