2015-08-10 7 views
13

Lassen Sie sagen, kann ich eine Klasse:TDD - Abhängigkeiten, die die nicht spotten

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; 
    } 

} 

Wie teste ich diese in Isolation? Wenn diese Klasse zu testen, teste ich auch die DomDocument Klasse

ich in dem Dokument-Objekt übergeben konnte:

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; 
    } 

} 

, das das Problem zu lösen scheint jedoch, jetzt ist mein Test nicht wirklich etwas zu tun. Ich brauche, um ein Mock DomDocument die XML zu machen kehre ich im Test testing bin:

$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); 

Welche mehrere Probleme:

  1. Ich bin nicht wirklich die Prüfung der Methode überhaupt, alles, was ich‘ m-Prüfung ist, dass die Methode gibt das Ergebnis von $document->saveXML()
  2. der Test ist mir der Durchführung des Verfahrens (es DOMDocument verwendet das xML zu erzeugen)
  3. der Test schlägt fehl, wenn die Klasse neu geschrieben wird simplexml oder andere zu bedienen XML-Bibliothek, obwohl es könnte das korrekte Ergebnis produzieren

also kann ich diesen Code isoliert testen? Es sieht so aus, als ob ich nicht kann. Gibt es einen Namen für diese Art von Abhängigkeit, die nicht verspottet werden kann, da ihr Verhalten im Wesentlichen für die zu testende Methode erforderlich ist?

+0

Warum müssen Sie es isoliert testen? – kenjis

+1

Da in einem realen Anwendungsfall, in dem die Abhängigkeit nicht eingebaut ist (oder DomDocument), der Test fehlschlägt, weiß ich nicht, ob das Problem bei der Implementierung der zu testenden Methode oder eines der Objekte liegt, die es erstellt . Natürlich kann ich dafür separate Tests durchführen, aber es ist ineffizient, alle Tests während der Entwicklung durchzuführen. Wie es hier heißt: https://msdn.microsoft.com/en-us/library/hh549175.aspx "Indem Sie Ihren Code zum Testen isolieren, wissen Sie, dass, wenn der Test fehlschlägt, die Ursache da ist und nicht irgendwo anders", es macht die Entwicklung/das Debuggen nur schneller, wenn Tests isoliert sind. –

Antwort

11

Dies ist eine Frage bezüglich TDD. TDD bedeutet zuerst Test schreiben.

Ich kann mir nicht vorstellen, mit einem Test zu beginnen, der DOMElement::createElement vor dem Schreiben der tatsächlichen Implementierung spottet. Es ist natürlich, dass Sie mit einem Objekt beginnen und xml erwarten.

Auch würde ich DOMElement eine Abhängigkeit nicht aufrufen. Es ist ein privates Detail Ihrer Implementierung. Sie werden nie eine andere Implementierung von DOMElement an den Konstruktor übergeben, es ist also nicht notwendig, sie im Konstruktor verfügbar zu machen.

Tests sollten auch als Dokumentation dienen. Einfacher Test mit einem Objekt und erwartetem XML wird lesbar sein. Jeder kann es lesen und sicher sein, was deine Klasse macht. Vergleichen Sie dies mit 50 Linientest mit Mocking (PhpUnit Mocks sind absurd ausführlich).

EDIT: Hier ist ein gutes Papier darüber http://www.jmock.org/oopsla2004.pdf. Zusammenfassend heißt es, dass es keinen Sinn macht, Mocks zu verwenden, es sei denn, Sie verwenden Tests, um Ihr Design zu steuern (Schnittstellen finden).

Es gibt auch eine gute Regel

Nur Mock Typen Sie arbeiten

(in dem Papier erwähnt), die Ihrem Beispiel angewendet werden kann.

+0

Ich denke, das ist die vernünftigste Antwort. Private Implementierungsdetails sollten nicht verspottet werden. Dies hat natürlich das Problem, nicht zum Testen zu isolieren, aber in diesem Fall ist das Problem minimal. –

0

Lassen Sie mich Ihre Fragen/Probleme lösen Sie den Code und die Tests sehen:

1) Ich bin eigentlich nicht die Methode zu testen überhaupt alles, was ich bin Überprüfung ist, dass die Methode liefert die Ergebnis von $ document-> saveXML()

das ist richtig, durch die DomDocument spöttisch und Methoden auf diese Weise zurückgeben, die Sie gerade prüfen, ob das Verfahren (auch nicht, dass das Verfahren das Ergebnis kehrt den saveXML aufgerufen wird(), weil ich keine Behauptung für die serialize-Methode sehe, sondern sie nur anrufe, was die Erwartung wahr werden lässt).

2) Der Test ist sich der Durchführung des Verfahrens (es verwendet DOMDocument das xml)

Das ist auch wahr, und sehr wichtig, denn wenn die interne Implementierung des Verfahrens Änderungen zu erzeugen, Der Test kann fehlschlagen, selbst wenn das richtige Ergebnis zurückgegeben wird. Der Test sollte die Methode als eine "Black Box" behandeln, die sich nur um den Rückgabewert der Methode mit einem Satz gegebener Argumente kümmert.

3) Der Test schlägt fehl, wenn die Klasse neu geschrieben wird simplexml oder eine andere XML-Bibliothek zu verwenden, auch wenn es das richtige Ergebnis produziert werden könnte

Es stimmt, siehe meinen Kommentar auf (2)

Also, was ist die Alternative dann? Angesichts Ihrer Implementierung des XMLSerializers erleichtert das/DomDocument lediglich die eigentliche Durchführung der Serialisierung. Darüber hinaus iteriert die Methode nur die Eigenschaften des Objekts. Daher sind der XMLSerializer und das DomDocument in gewisser Weise untrennbar miteinander verbunden und das könnte gut sein. Wenn ich den Test selbst betrachte, wäre mein Ansatz, ein bekanntes Objekt bereitzustellen und zu bestätigen, dass die serialize-Methode eine erwartete xml-Struktur zurückgibt (da das Objekt bekannt ist, ist auch das Ergebnis bekannt). Auf diese Weise sind Sie nicht an die tatsächliche Implementierung der Methode gebunden (es spielt also keine Rolle, ob Sie DomDocument oder etwas anderes verwenden, um die XML-Dokumenterstellung tatsächlich durchzuführen).

Jetzt, über die andere Sache, die Sie erwähnen (das DomDocument injizieren), ist es in der aktuellen Implementierung nicht von Nutzen. Warum? Wenn Sie ein anderes Tool für die Erstellung des XML-Dokuments verwenden möchten (Simplex-Datei usw., wie Sie es nennen), müssen Sie den Großteil der Methoden ändern. Eine alternative Implementierung ist die folgende:

<?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 
     } 
    } 

Der Nutzen aus der obigen Implementierung ist, dass, wenn Sie einen Serializer benötigen, können Sie die Schnittstelle typehint und jede Implementierung injizieren, so dass beim nächsten Mal erstellen Sie eine neue SimplexmlSerializer Implementierung, werden Sie Sie müssen nur die Instanziierung der Klassen durchlaufen, die einen Serialisierer als Argument benötigen (dann würde die Abhängigkeitsinjektion Sinn machen) und einfach die Implementierung ändern.

Entschuldigung für den letzten Teil und Code, kann es ein bisschen vom Zweck der TDD sein, aber es wird den Code, der den Serialisierer verwendet testbar, so ist es irgendwie relevant.

1

Wie Sie es bereits erwähnt haben, ist die Test-Isolation eine gute Technik, wenn Sie die Fehlerbehebung beschleunigen wollen. Das Schreiben dieser Tests kann jedoch erhebliche Kosten sowohl in der Entwicklung als auch in der Wartung verursachen.Am Ende des Tages, was Sie wirklich wollen, ist eine Testsuite, die nicht jedes Mal ändern muss, wenn Sie das zu testende System ändern. Mit anderen Worten, Sie schreiben einen Test gegen eine API, nicht gegen die Details der Implementierung.

Natürlich können Sie eines Tages auf einen schwer zu findenden Fehler stoßen, der eine Test-Isolation erfordert, um entdeckt zu werden, aber Sie brauchen ihn vielleicht gerade nicht. Daher würde ich vorschlagen, zuerst die Eingänge und die Ausgänge Ihres Systems zu testen (Ende-zu-Ende-Test). Wenn Sie eines Tages mehr benötigen, werden Sie immer noch feinkörnigere Tests durchführen können.

Zurück zu Ihrem Problem, was Sie wirklich testen möchten, ist die Transformationslogik, die im Serializer ausgeführt wird, egal wie es gemacht wird. Die Verspottung eines Typs, den Sie nicht besitzen, ist keine Option, da willkürliche Annahmen darüber, wie eine Klasse mit ihrer Umgebung interagiert, zu Problemen führen kann, sobald der Code implementiert ist. Wie von m1lt0n vorgeschlagen, können Sie diese Klasse innerhalb einer Schnittstelle einkapseln und für Testzwecke mocksen. Dies gibt etwas Flexibilität bezüglich der Implementierung des Serialisierers, aber die eigentliche Frage ist: brauchst du das wirklich?Was sind die Vorteile im Vergleich zu einfacheren Lösungen? Für eine erste Implementierung scheint mir, dass ein einfacher Input vs Output-Test genug sein sollte ("Keep it simple and dotly"). Wenn Sie eines Tages zwischen verschiedenen Serialisierungsstrategien wechseln müssen, ändern Sie einfach das Design und fügen Sie etwas Flexibilität hinzu.