2015-08-10 12 views
13

Diciamo che ho una classe:TDD - Dipendenze che non può essere preso in giro

class XMLSerializer { 
    public function serialize($object) { 
     $document = new DomDocument(); 
     $root = $document->createElement('object'); 
     $document->appendChild($root); 

     foreach ($object as $key => $value) { 
      $root->appendChild($document->createElement($key, $value); 
     } 

     return $document->saveXML(); 
    } 

    public function unserialze($xml) { 
     $document = new DomDocument(); 
     $document->loadXML($xml); 

     $root = $document->getElementsByTagName('root')->item(0); 

     $object = new stdclass; 
     for ($i = 0; $i < $root->childNodes->length; $i++) { 
      $element = $root->childNodes->item($i); 
      $tagName = $element->tagName; 
      $object->$tagName = $element->nodeValue(); 
     } 

     return $object; 
    } 

} 

Come faccio a testare questo in isolamento? Durante il test di questa classe, sto anche testando la classe DomDocument

ho potuto passare l'oggetto del documento:

class XMLSerializer { 
    private $document; 

    public function __construct(\DomDocument $document) { 
     $this->document = $document; 
    } 

    public function serialize($object) { 
     $root = $this->document->createElement('object'); 
     $this->document->appendChild($root); 

     foreach ($object as $key => $value) { 
      $root->appendChild($this->document->createElement($key, $value); 
     } 

     return $this->document->saveXML(); 
    } 

    public function unserialze($xml) { 
     $this->document->loadXML($xml); 

     $root = $this->document->getElementsByTagName('root')->item(0); 

     $object = new stdclass; 
     for ($i = 0; $i < $root->childNodes->length; $i++) { 
      $element = $root->childNodes->item($i); 
      $tagName = $element->tagName; 
      $object->$tagName = $element->nodeValue(); 
     } 

     return $object; 
    } 

} 

che sembra risolvere il problema, però, ora il mio test non è in realtà facendo nulla. Ho bisogno di fare una finta DomDocument restituire il XML sto testando nel test:

$object = new stdclass; 
$object->foo = 'bar'; 

$mockDocument = $this->getMock('document') 
       ->expects($this->once()) 
       ->method('saveXML') 
       ->will(returnValue('<?xml verison="1.0"?><root><foo>bar</foo></root>')); 

$serializer = new XMLSerializer($mockDocument); 

$serializer->serialize($object); 

che ha diversi problemi:

  1. io non sono in realtà una prova del metodo a tutti, tutto quello che ho' m controllo è che il metodo restituisce il risultato di $document->saveXML()
  2. la prova è consapevole della implementazione del metodo (che utilizza DOMDocument per generare il codice XML)
  3. il test avrà esito negativo se la classe viene riscritto per utilizzare SimpleXML o di un altro libreria xml, anche se è così potrebbe produrre il risultato corretto

così posso testare questo codice in isolamento? Sembra che io non possa ... esiste un nome per questo tipo di dipendenza che non può essere deriso perché il suo comportamento è essenzialmente richiesto per il metodo che viene testato?

+0

Perché è necessario testarlo isolatamente? – kenjis

+1

Perché in un caso di utilizzo del mondo reale in cui la dipendenza non è incorporata (o DomDocument) se il test fallisce, non saprò se il problema riguarda l'implementazione del metodo che sto testando, o uno degli oggetti che costruisce . Naturalmente posso fare test separati per quelli, ma è inefficiente eseguire tutti i test ogni volta durante lo sviluppo. Come dice qui: https://msdn.microsoft.com/en-us/library/hh549175.aspx "Isolando il tuo codice per il test, sai che se il test fallisce, la causa è lì e non da qualche altra parte", rende lo sviluppo/debugging più veloce se i test sono isolati. –

risposta

11

Questa è una domanda riguardante TDD. TDD significa innanzitutto test di scrittura.

Non riesco a immaginare di iniziare con un test che prende in giro DOMElement::createElement prima di scrivere l'effettiva implementazione. È naturale iniziare con un oggetto e un xml previsto.

Inoltre, non chiamerei una dipendenza da DOMElement. È un dettaglio privato della tua implementazione. Non passerai mai un'implementazione diversa di DOMElement al costruttore di XMLSerializer, quindi non è necessario esporlo nel costruttore.

I test devono anche servire come documentazione. Il test semplice con un oggetto e xml previsto sarà leggibile. Tutti saranno in grado di leggerlo ed essere sicuri di ciò che sta facendo la tua classe. Confronta questo con il test in 50 linee con il mocking (le mock PhpUnit sono assurdamente prolisse).

MODIFICA: Ecco un buon documento su di esso http://www.jmock.org/oopsla2004.pdf. In poche parole afferma che, a meno che non si utilizzino i test per guidare il progetto (trovare le interfacce), non ha senso usare i mock.

C'è anche una buona regola

solo i tipi di Mock il proprietario

(citato nella carta) che può essere applicato al vostro esempio.

+0

Penso che questa sia la risposta più sensata. I dettagli dell'implementazione privata non devono essere presi in giro. Questo naturalmente ha il problema di non isolare per i test, ma in questo caso il problema è minimo. –

0

Permettetemi di rispondere alle tue domande/problemi che vedete nel codice e le prove:

1) Io non sono in realtà testare il metodo a tutti, tutto quello che sto controllando è che il metodo restituisce il risultato di $ documento-> saveXML()

proprio così, deridendo il DomDocument ed è metodi restituiscono in questo modo, è sufficiente verificare che il metodo sarà chiamato (nemmeno che il metodo sta tornando il risultato di saveXML(), poiché non vedo un'asserzione per il metodo serialize, ma solo una chiamata, che fa sì che l'aspettativa sia vera).

2) Il test è consapevole della implementazione del metodo (utilizza DOMDocument per generare il codice XML)

Questo è anche vero e molto importante, perché se l'implementazione interna del metodo modifiche, il test potrebbe fallire anche se restituisce il risultato corretto. Il test dovrebbe trattare il metodo come una "scatola nera", preoccupando solo del valore di ritorno del metodo con un insieme di argomenti dati.

3) Il test avrà esito negativo se la classe viene riscritto per utilizzare SimpleXML o un'altra libreria XML, anche se potrebbe essere producendo il risultato corretto

Vero, vedere il mio commento sulla (2)

Quindi, qual è l'alternativa allora? Data la tua implementazione di XMLSerializer, DomDocument semplifica/è un helper per l'effettiva esecuzione della serializzazione. Oltre a questo, il metodo esegue semplicemente un'iterazione sulle proprietà dell'oggetto. Quindi XMLSerializer e DomDocument sono inseparabili in un modo e potrebbe andare bene.

Per quanto riguarda il test, il mio approccio sarebbe quello di fornire un oggetto conosciuto e affermare che il metodo serialize restituisce una struttura xml prevista (poiché l'oggetto è noto, anche il risultato è noto). In questo modo, non sei legato all'implementazione effettiva del metodo (quindi non importa se usi DomDocument o qualcos'altro per eseguire effettivamente la creazione del documento XML).

Ora, riguardo all'altra cosa che si menziona (l'iniezione del DomDocument), non è di alcuna utilità nell'attuale implementazione. Perché? perché se si desidera utilizzare un altro strumento per la creazione del documento XML (simplexml ecc. come si menziona), è necessario modificare la maggior parte dei metodi. Un'implementazione alternativa è la seguente:

<?php 

    interface Serializer 
    { 
     public function serialize($object); 

     public function unserialize($xml); 
    } 


    class DomDocumentSerializer 
    { 
     public function serialize($object) 
     { 
    // the actual implementation, same as the sample code you provide 
     } 

     public function unserialize($xml) 
     { 
    // the actual implementation, same as the sample code you provide 
     } 
    } 

Il beneficio da quanto sopra implementazione è che ogni volta che hai bisogno di un serializzatore è possibile typehint l'interfaccia e iniettare qualsiasi implementazione, così la prossima volta che si crea una nuova implementazione SimplexmlSerializer, si basta passare attraverso l'istanziazione delle classi che hanno bisogno (è qui che l'iniezione della dipendenza avrebbe senso) un serializzatore come argomento e basta cambiare l'implementazione.

Siamo spiacenti per l'ultima parte e il codice, potrebbe essere un po 'fuori dallo scopo di TDD ma renderà il codice che utilizza il serializzatore testabile, quindi è un po' rilevante.

1

Come hai detto, l'isolamento di prova è una buona tecnica se vuoi velocizzare la risoluzione dei bug. Tuttavia, scrivere quei test può avere un costo importante in termini di sviluppo e anche in termini di manutenzione.Alla fine della giornata, quello che vuoi veramente è una suite di test che non deve cambiare ogni volta che si modifica il sistema in prova. In altre parole, si scrive un test contro un'API, non contro i suoi dettagli di implementazione.

Ovviamente, un giorno potresti incontrare un bug difficile da trovare che richiederebbe l'isolamento del test per essere individuato, ma potresti non averne bisogno al momento. Pertanto, suggerirei di testare prima gli ingressi e le uscite del sistema (test end-to-end). Se un giorno, hai bisogno di più, beh, sarai ancora in grado di effettuare alcuni test a grana più fini.

Torna al tuo problema, quello che vuoi veramente testare, è la logica di trasformazione che viene eseguita nel serializzatore, non importa come sia fatto. Prendere in giro un tipo che non possiedi non è un'opzione, poiché fare ipotesi arbitrarie su come una classe interagisce con il suo ambiente può portare a problemi una volta che il codice è stato distribuito. Come suggerito da m1lt0n, è possibile incapsulare questa classe all'interno di un'interfaccia e deriderla a scopo di test. Ciò offre una certa flessibilità per quanto riguarda l'implementazione del serializzatore, ma la vera domanda è, ne hai veramente bisogno?Quali sono i vantaggi rispetto alla soluzione più semplice? Per una prima implementazione, mi sembra che un semplice test di input vs output dovrebbe essere sufficiente ("Keep it simple and stupid"). Se un giorno devi passare da una strategia di serializzazione diversa, modifica il design e aggiungi flessibilità.

Problemi correlati