Classe beffarda del cemento - Non raccomandato


11

Ho appena letto un estratto del libro "Crescere il software orientato agli oggetti" che spiega alcuni dei motivi per cui non si consiglia di deridere la classe concreta.

Ecco un codice di esempio di un test unitario per la classe MusicCentre:

public class MusicCentreTest {
  @Test public void startsCdPlayerAtTimeRequested() {
    final MutableTime scheduledTime = new MutableTime();
    CdPlayer player = new CdPlayer() { 
      @Override 
      public void scheduleToStartAt(Time startTime) {
        scheduledTime.set(startTime);
      }
    }

    MusicCentre centre = new MusicCentre(player);
    centre.startMediaAt(LATER);

    assertEquals(LATER, scheduledTime.get());
  }
}

E la sua prima spiegazione:

Il problema con questo approccio è che lascia implicita la relazione tra gli oggetti. Spero che ormai abbiamo chiarito che l'intenzione di Test-Driven Development con Mock Objects è scoprire relazioni tra oggetti. Se eseguo la sottoclasse, non c'è nulla nel codice di dominio che renda visibile tale relazione, solo metodi su un oggetto. Questo rende più difficile vedere se il servizio che supporta questa relazione potrebbe essere rilevante altrove e dovrò ripetere l'analisi la prossima volta che lavoro con la classe.

Non riesco a capire esattamente cosa significhi quando dice:

Questo rende più difficile vedere se il servizio che supporta questa relazione potrebbe essere rilevante altrove e dovrò ripetere l'analisi la prossima volta che lavoro con la classe.

Comprendo che il servizio corrisponde al MusicCentremetodo chiamato startMediaAt.

Cosa intende per "altrove"?

L'estratto completo è qui: http://www.mockobjects.com/2007/04/test-smell-mocking-concrete-classes.html


Aggiunto un commento sul suo blog, poiché non sono riuscito a capire cosa intendesse da queste citazioni.
Oligofren,

@oligofren È davvero un grande enigma :) ...
Mik378,

Risposte:


6

L'autore di quel post sta promuovendo l'uso delle interfacce sull'uso delle classi membro.

It turns out that my MusicCentre object only uses the starting and stopping methods on the CdPlayer, the rest are used by some other part of the system. I'm over-specifying my MediaCentre by requiring it to talk to a CdPlayer, what it actually needs is a ScheduledDevice.

La relazione che è preoccupato di riscoprire in seguito è il fatto che la classe MediaCentre non ha bisogno di tutto l'oggetto CdPlayer. La sua affermazione è che usando un'interfaccia (presumibilmente limitata al solo inizio | stop) è più facile capire quale sia realmente l'interazione.

"altrove" significa semplicemente che altri oggetti possono avere relazioni similmente limitate e non è richiesto l'intero oggetto membro - un sottoinsieme della funzionalità racchiusa in un'interfaccia dovrebbe essere sufficiente.

Il reclamo inizia ad avere più senso quando esplodi tutte le potenziali funzionalità:

  • inizio
  • fermare
  • pausa
  • disco
  • ordine di gioco casuale
  • tracce campione, inizio del brano
  • tracce campione, campione casuale di canzone
  • fornire informazioni ai media
  • ...

Ora la sua affermazione di "Ho solo bisogno di iniziare e fermare" ha più senso. L'utilizzo dell'oggetto membro concreto anziché di un'interfaccia rende meno chiaro agli sviluppatori futuri ciò che è realmente richiesto. L'esecuzione di test unitari da MediaCentre su tutte le altre funzioni all'interno di CdPlayer è uno spreco di test poiché appartengono allo stato "non importa". Se la Recordfunzione non funzionava in questo caso, non ci interessa davvero poiché non è necessaria. Ma un futuro manutentore non lo saprebbe necessariamente in base al codice, come scritto.

In definitiva, la premessa dell'autore è quella di utilizzare solo ciò che è necessario e chiarire ai futuri manutentori ciò che era richiesto in precedenza. L'obiettivo è minimizzare la rilavorazione / rianalizzare il modulo di codice durante la successiva manutenzione.


Grazie per questa ottima risposta. Comunque hai detto: "L'esecuzione di test unitari su tutte le altre funzioni è uno spreco di test poiché appartengono allo stato" non importa ". Non è piuttosto: "Creare beffe per ognuna delle altre funzioni è uno spreco di prove poiché appartengono allo stato" non importa "."
Mik378

@ Mik378 - sì, è esattamente quello a cui stavo arrivando, l'ho solo espresso diversamente. E ho aggiornato la mia risposta per renderlo più chiaro.

Ma trovo che il termine "esecuzione di unit test" sia confuso. Ciò significherebbe che MusicCentre sta per testare l'unità del proprio collaboratore ... mentre in realtà MOCKS il suo collaboratore per testare l'unità dei propri servizi. A proposito, ora capisco il significato :)
Mik378

@ Mik378 - stiamo dicendo la stessa cosa, e probabilmente sto usando una terminologia poco precisa per farlo. Scuse per la confusione.

4

Questo rende più difficile vedere se il servizio che supporta questa relazione potrebbe essere rilevante altrove e dovrò ripetere l'analisi la prossima volta che lavoro con la classe.

Dopo averci pensato molto, ho una possibile interpretazione di questa citazione:

Il "servizio" citato corrisponde al "fatto della pianificazione". Ciò potrebbe essere espresso da un'interfaccia ben definita e "focalizzata su un ruolo" denominata "ScheduledDevice" o espressa implicitamente da un'implementazione del metodo concreto non dipendente da alcuna interfaccia.

Nell'esempio sopra, la pianificazione è espressa dall'intero oggetto completo denominato CDPlayer. Pertanto, porta ancora a una relazione implicita tra MusicCentree "fatto di pianificazione".

Quindi se iniziamo a iniettare classi concrete e a deriderle in oggetti di alto livello; quando vogliamo testare questi, dobbiamo analizzare ogni oggetto "concreto" iniettato per vedere se presenta una relazione specifica che DOVREBBE RIPORTARE perché sono NASCOSTI (impliciti). Al contrario, la codifica SEMPRE sull'interfaccia consente allo sviluppatore di capire direttamente quale tipo di relazione sta per essere servita dall'oggetto di alto livello e quindi rilevare caratteristiche che devono essere derise per isolare l'unità-test.


Penso che ce l'abbia adesso. Sfortunatamente, non ho ricevuto una notifica del tuo commento.
Steve Freeman,

3

Il servizio che intendevo qui era CDPlayer.scheduleToStartAt (). Ecco come chiama MediaCentre: il collaboratore che deve funzionare. MediaCentre è l'oggetto in prova.

L'idea è che se esplicito esattamente da cosa dipende MediaCentre, non una classe di implementazione, posso dare un nome a quel ruolo di dipendenza e parlarne. Tutto ciò che MediaCentre deve sapere è che parla con ScheduledDevices. Poiché il resto del sistema cambia, non avrò bisogno di modificare MediaCentre a meno che le sue funzionalità non cambino.

Questo aiuta?


(autore di questo fantastico articolo :)) quello che volevo interpretare era questa frase: "Questo rende più difficile vedere se il servizio che supporta questa relazione potrebbe essere rilevante altrove e dovrò ripetere l'analisi la prossima volta che lavoro con la classe ". Che tipo di analisi? Il fatto di rilevare quale metodo dell'oggetto dovrebbe implementare la relazione poiché questo è chiaramente nascosto?
Mik378
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.