TDD и конфликт приоритетов инкапсуляции - PullRequest
4 голосов
/ 11 апреля 2010

Я только начал практиковать TDD в своих проектах. Сейчас я занимаюсь разработкой проекта с использованием php / zend / mysql и phpunit / dbunit для тестирования. Я просто немного отвлечен идеей инкапсуляции и подходом, основанным на тестировании. Моя идея инкапсуляции заключается в том, чтобы скрыть доступ к нескольким объектным функциям. Чтобы сделать это более понятным, частные и защищенные функции нельзя напрямую тестировать (если только вы не создадите публичную функцию для ее вызова).

Итак, я в конечном итоге преобразовал некоторые закрытые и защищенные функции в публичные функции, чтобы иметь возможность протестировать их. Я действительно нарушаю принципы инкапсуляции, чтобы уступить место тестированию микрофункций. Это правильный способ сделать это?

Ответы [ 3 ]

8 голосов
/ 11 апреля 2010

Существует довольно стандартный ответ на этот вопрос в кругах TDD. Если в классе есть функциональность, которую вы оба хотите скрыть и непосредственно протестировать, вы должны прорастить класс с этой функциональностью. Это отличный пример того, как TDD улучшает ваш дизайн.

В исходном классе эта посторонняя функциональность исчезла, обернута внутри порожденного класса, поэтому дизайн исходного класса проще и лучше соответствует Принципу единой ответственности . В порожденном классе извлеченная функциональность - это raison d'etre , поэтому она подходит для публичного использования и поэтому может тестироваться без модификаций только для тестирования.

7 голосов
/ 12 апреля 2010

Что касается прекрасного ответа Карла Манастера, есть некоторые недостатки, которые вы должны хотя бы рассмотреть, прежде чем встать на путь, предложенный Карлом.

Наиболее значимым из них является следующее: мы используем инкапсуляцию, чтобы минимизировать количество потенциальных зависимостей, которые несут наибольшую вероятность распространения изменений. В вашем случае вы инкапсулировали закрытые методы внутри вашего класса: они не доступны другим классам и, следовательно, от них нет потенциальных зависимостей: стоимость любых изменений, которые вы вносите в них, сводится к минимуму и имеет низкую вероятность распространения на другие. классы.

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

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

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

По этим причинам я не согласен с Карлом в том, что его предложение «… отличный пример того, как TDD улучшает ваш дизайн».

Кроме того, он заявляет: «В исходном классе эта посторонняя функциональность исчезла, обернута внутри порожденного класса, поэтому дизайн исходного класса проще и лучше соответствует принципу единой ответственности».

Я бы сказал, что перемещаемая функциональность вовсе не является «посторонней». Кроме того, «проще» не является четко определенной: это, безусловно, может быть случай, когда простота класса обратно пропорциональна его размеру, но это не означает, что система простейших классов будет самой простой из возможных систем: если бы это было так, все классы содержали бы только один метод, а система имела бы огромное количество классов; Можно утверждать, что удаление этого иерархического слоя множественных методов внутри классов сделает систему намного более сложной.

Принцип единой ответственности (SRP), кроме того, общеизвестно субъективен и полностью зависит от уровня абстракции наблюдателя. Это вовсе не тот случай, когда удаление метода из класса автоматически улучшает его соответствие SRP. Класс Printer с 10 методами несет единоличную ответственность за печать на уровне абстракции класса. Одним из его методов может быть checkPrinterConnected (), а другим - checkPaper (); на уровне методов это явно отдельные обязанности, но они не предлагают автоматически разбивать класс на последующие классы.

Карл Финишеs, «В порожденном классе извлеченная функциональность является ее смыслом, поэтому она является общедоступной и поэтому тестируемой без модификаций только для тестирования». Важность функциональности (это raison-d'etre-ness не является основанием для уместности его публичности. Основой приемлемости общедоступности функциональности является минимизация интерфейса, предоставляемого клиенту, так что функциональность класса доступна для использования, в то время как независимость клиента от реализации функциональности максимальна. Конечно, если вы перемещаете только один метод в порожденный класс, он должен быть публичным. Однако, если вы перемещаете более одного метода, вы должны сделать эти методы общедоступными, которые необходимы для успешного использования клиентом класса: эти открытые методы могут быть гораздо менее важными, чем некоторые из частных методов, от которых вы хотите защитить свои. клиент. (В любом случае, я не в восторге от этой фразы «Raison-d'etre», поскольку важность метода также не вполне определена.)

Альтернативный подход к предложению Карла зависит от того, насколько велика ваша система для роста. Если его число возрастет до нескольких тысяч, то вы можете рассмотреть возможность создания сценария для копирования исходного кода в новый каталог, изменения всех вхождений с «private» на «public» в этом скопированном источнике и затем записи своего тесты против скопированного источника. Это имеет недостаток времени, необходимого для копирования кода, но дает преимущество сохранения инкапсуляции исходного кода, но делает все методы тестируемыми в скопированной версии.

Ниже приведен скрипт, который я использую для этой цели.

С уважением,

Эд Кирван

! / Bin / Баш

rm -rf code-copy

echo Создание кода-копии ...

MKDIR код-копия

cp -r ../www code-copy /

для меня в find code-copy -name "*php" -follow; делать

sed -i 's/private/public/g' $i

сделано

php run_tests.php

3 голосов
/ 13 апреля 2010

Я только что прочитал отличную статью о том, как позволить фиктивным объектам управлять вашим дизайном:

http://www.mockobjects.com/files/usingmocksandtests.pdf

Когда Карл говорит: «Вы должны вырастить класс с этой функциональностью», автор этой статьи объясняет, как ваши тесты могут помочь вам, используя фиктивные объекты, как вы можете создать свой класс, чтобы вы 1) не делали этого. нужно беспокоиться о неспособности протестировать закрытые части, и что более важно, 2) как это улучшит ваш дизайн путем (я перефразирую цитату Карлса) обнаружения соавторов и ролей с правильной ответственностью.

Автор шаг за шагом приводит вас к примеру, чтобы прояснить свою точку зрения.

Вот еще одна статья с таким же подходом:

http://www.methodsandtools.com/archive/archive.php?id=90

Цитата:

Многие, кто начинает с TDD, борются с получить контроль над зависимостями. к проверить объект, вы тренируетесь поведение, а затем проверить, является ли Объект находится в ожидаемом состоянии. Потому что ОО дизайн ориентирован на поведение, состояние объекта обычно скрытый (инкапсулированный). Быть возможность проверить, ведет ли себя объект как и ожидалось, иногда нужно получить доступ к внутреннему состоянию и ввести специальные методы, чтобы выставить это состояние, как метод получения или свойство, которое извлекает внутренний состояние.

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

Группа гибкой разработки программного обеспечения пионеры из Великобритании были также борется с этим обратно в 1999. Им пришлось добавить дополнительные методы получения, чтобы проверить состояние объектов. Их менеджер не понравился все это нарушение инкапсуляции и заявил: я не хочу добытчиков в код! (Макиннон и др., 2000 Фриман и др., 2004)

Команда пришла в голову идея сосредоточиться на взаимодействиях, а не государство. Они создали специальный объект заменить сотрудников проверяемые объекты. Эти специальные объекты содержали спецификации для ожидаемые вызовы методов. Они позвонили эти объекты издеваются над объектами, или издеваются коротко. Оригинальные идеи имеют были уточнены, в результате чего несколько фальшивые объектные рамки для всех распространенных языки программирования: Java (jMock, EasyMock, Mockito), .NET (NMock, RhinoMocks), Python (PythonMock, Mock.py, Ruby (Mocha, RSpec), C ++ (mockpp, amop). Увидеть www.mockobjects.com для более информация и ссылки.

...