2015-08-10 2 views
13

Скажем, у меня есть класс:TDD - Зависимости, которые не могут быть издевались

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

} 

Как проверить это в изоляции? При тестировании этого класса, я также тестировать класс DomDocument

Я мог бы передать в объект документа:

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

} 

который, кажется, решить эту проблему, однако, теперь мой тест ничего не делает. Мне нужно сделать макет DomDocument вернуть XML Я тестирования в тесте:

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

который имеет несколько проблем:

  1. Я на самом деле не тестирование метода вообще, все, что я» проверка м является то, что метод возвращает результат $document->saveXML()
  2. тест знает о реализации методы (он использует DOMDocument для создания XML)
  3. теста потерпит неудачу, если класс переписан с использованием SimpleXML или другим xml, хотя это может привести к правильному результату

так что я могу проверить этот код в изоляции? Похоже, я не могу .. есть ли имя для этого типа зависимости, которое нельзя высмеять, поскольку его поведение по существу требуется для тестируемого метода?

+0

Зачем вам нужно тестировать его изолированно? – kenjis

+1

Потому что в реальном случае, когда зависимость не встроена (или DomDocument), если тест завершился неудачно, я не буду знать, связана ли проблема с реализацией метода, который я тестирую, или с одним из объектов, которые он строит , Конечно, у меня могут быть отдельные тесты для них, но неэффективно каждый раз выполнять все тесты во время разработки. Как говорится здесь: https://msdn.microsoft.com/en-us/library/hh549175.aspx «Отделив ваш код для тестирования, вы знаете, что если тест не удался, причина есть и не где-то еще», он просто ускоряет разработку/отладку, если тесты изолированы. –

ответ

11

Это вопрос относительно TDD. TDD означает сначала запись теста.

Я не могу представить, начиная с теста, который издевается DOMElement::createElement перед написанием фактической реализации. Естественно, что вы начинаете с объекта и ожидаемого xml.

Кроме того, я бы не назвал DOMElement зависимым. Это частная деталь вашей реализации. Вы никогда не пройдете другую реализацию DOMElement конструктору XMLSerializer, поэтому нет необходимости выставлять его в конструкторе.

Испытания также должны служить документацией. Простой тест с объектом и ожидаемым xml будет читабельным. Каждый сможет прочитать его и убедиться, что делает ваш класс. Сравните это с 50-строчным тестом с издевательством (PhpUnit mocks нелепо многословно).

EDIT: Вот хорошая статья об этом http://www.jmock.org/oopsla2004.pdf. В двух словах говорится, что если вы не используете тесты для управления вашим дизайном (найти интерфейсы), мало смысла использовать mocks.

Существует также хорошее правило

только Мок Типы Вы Принадлежит

(упомянутый в статье), которые могут быть применены к вашему примеру.

+0

Я думаю, что это самый разумный ответ. Детали частной реализации не следует издеваться. Это, конечно, имеет проблему не изолировать для тестирования, но в этом случае проблема минимальна. –

0

Позвольте мне ответить на ваши вопросы/проблемы, которые вы видите в коде и тесты:

1) Я на самом деле не тестирование метода вообще, все, что я проверить, что метод возвращает результат $ document-> saveXML()

это право, по насмешливый DomDocument и это методы возвращают этот путь, вы просто проверить, что метод будет вызван (даже не о том, что метод возвращает результат saveXML(), так как я не вижу утверждения для метода сериализации, а просто вызываю его, что заставляет ожидание быть истинным).

2) Испытание известно о реализации методы (он использует DOMDocument для создания XML)

Это также верно и очень важно, потому что если внутренняя реализация изменений методы, тест может потерпеть неудачу, даже если он вернет правильный результат. Тест должен рассматривать метод как «черный ящик», беспокоясь только о возвращаемом значении метода с набором заданного аргумента (ов).

3) Проверка не будет выполнена, если класс переписывается использовать SimpleXML или другую библиотеку XML, хотя он может производить правильный результат

Правда, увидеть мой комментарий (2)

Итак, какова альтернатива? Учитывая вашу реализацию XMLSerializer, DomDocument просто облегчает/является помощником для фактического выполнения сериализации. Помимо этого, метод просто выполняет итерации по свойствам объекта. Таким образом, XMLSerializer и DomDocument неотделимы друг от друга, и это может быть просто отлично.

Что касается самого теста, то мой подход состоял бы в том, чтобы предоставить известный объект и утверждать, что метод serialize возвращает ожидаемую структуру xml (поскольку объект известен, результат известен также). Таким образом, вы не привязаны к фактической реализации метода (поэтому не имеет значения, используете ли вы DomDocument или что-то еще для фактического выполнения создания XML-документа).

Теперь о том, что вы упомянули (вводя DomDocument), в текущей реализации это бесполезно. Зачем? потому что если вы хотите использовать другой инструмент для создания XML-документа (simplexml и т. д., как вы упоминаете), вам нужно будет изменить основную часть методов. Альтернативная реализация заключается в следующем:

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

Выгода от такой реализации является то, что всякий раз, когда вам нужно сериалайзер вы можете typehint интерфейс и ввести любую реализацию, так что в следующий раз при создании новой реализации SimplexmlSerializer, вы будете просто нужно пройти через экземпляр классов, которые нуждаются (вот где инъекция зависимостей будет иметь смысл) сериализатор в качестве аргумента и просто изменить реализацию.

Извините за последнюю часть и код, это может быть немного нецелесообразно для TDD, но это сделает код, который использует тестируемый serializer, поэтому он является релевантным.

1

Как вы уже упоминали, тестовая изоляция является хорошей техникой, если вы хотите ускорить определение ошибки. Однако написание этих тестов может иметь значительную стоимость с точки зрения разработки, а также с точки зрения обслуживания.В конце дня то, что вы действительно хотите, это набор тестов, который не нужно менять каждый раз, когда вы модифицируете тестируемую систему. Другими словами, вы пишете тест против API, а не его детали реализации.

Конечно, в один прекрасный день вы можете столкнуться с труднодоступной ошибкой, которая потребует изоляции теста, чтобы ее можно было заметить, но вам может и не понадобиться ее прямо сейчас. Поэтому я бы предложил сначала протестировать входы и выходы вашей системы (сквозной тест). Если в один прекрасный день вам нужно больше, ну, вы все равно сможете сделать еще несколько оштукатуренных тестов.

Назад к проблеме, которую вы действительно хотите протестировать, является логикой преобразования, которая выполняется в сериализаторе, независимо от того, как это делается. Издевательство над типом, которым вы не владеете, не является вариантом, так как произвольные предположения относительно того, как класс взаимодействует со своей средой, могут привести к проблемам после развертывания кода. Как было предложено m1lt0n, вы можете инкапсулировать этот класс в интерфейсе и издеваться над ним для целей тестирования. Это дает некоторую гибкость в отношении реализации сериализатора, но реальный вопрос: вам это действительно нужно?Каковы преимущества по сравнению с более простым решением? Для первой реализации мне кажется, что достаточно простого теста ввода и вывода («Держите его простым и глупым»). Если в какой-то день вам нужно переключиться между различными стратегиями сериализации, просто измените дизайн и добавьте некоторую гибкость.

Смежные вопросы