Я нашел обсуждение Вы тестируете частный метод информативным.
Я решил, что в некоторых классах я хочу иметь защищенные методы, но протестировать их. Некоторые из этих методов статичны и коротки. Поскольку большинство общедоступных методов используют их, я, вероятно, смогу безопасно удалить тесты позже. Но чтобы начать с подхода TDD и избежать отладки, я действительно хочу их протестировать.
Я подумал о следующем:
Какая лучшая практика? Что-нибудь еще?
Кажется, что JUnit автоматически меняет защищенные методы на публичные, но я не стал на это более подробно останавливаться. PHP не позволяет этого через отражение.
Вы можете назвать это нарушением правил сайта, но просто назвать это «неконструктивным» ... это оскорбительно.
Два вопроса: 1. Почему вы должны беспокоиться о тестировании функциональности, которую не предоставляет ваш класс? 2. Если вы должны его протестировать, почему он частный?
Возможно, он хочет проверить, правильно ли установлено частное свойство, и единственный способ тестирования с использованием только функции установщика - сделать частное свойство общедоступным и проверить данные.
@Visser, обидно сам;)
Вполне возможно, что он может захотеть не тестировать интерфейс общедоступного класса как таковой, а интерфейс, который он представляет дочерним классам (которые может имеют доступ к защищенным методам)
@lenswipe, тогда он должен провести модульное тестирование API дочернего класса, который будет использовать защищенные методы
@ Джереми Белоло, и для этого он ... Что именно?
@robertmain: как он написал, возможно, не совсем ясно: API должен расширяться от базового класса, а затем использовать защищенные (иначе в любом случае они не были бы использованы - по-настоящему, сократите его). Модуль, который может предоставлять именно те общедоступные методы представления API и т. д. Это также покажет, имеет ли расширение действительно смысл. Обычно эти тесты и их заглушки / макеты должны быть частью основной библиотеки, если она привязана к защищенной видимости. Если эти модульные тесты недоступны из первых рук, как вы узнаете, что это всего лишь чушь на защищенном уровне?
Кроме защищенных методов - это деталь реализации. Вы предлагаете тестировать реализацию, а не интерфейс.






Кажется, вы уже в курсе, но я все равно повторю это заново; Плохой знак, если нужно протестировать защищенные методы. Цель модульного теста - проверить интерфейс класса, а защищенные методы - это детали реализации. Тем не менее, есть случаи, когда это имеет смысл. Если вы используете наследование, вы можете увидеть суперкласс как обеспечивающий интерфейс для подкласса. Итак, здесь вам нужно будет протестировать защищенный метод (но никогда не частный). Решение этой проблемы - создать подкласс для целей тестирования и использовать его для предоставления методов. Например.:
class Foo {
protected function stuff() {
// secret stuff, you want to test
}
}
class SubFoo extends Foo {
public function exposedStuff() {
return $this->stuff();
}
}
Обратите внимание, что вы всегда можете заменить наследование композицией. При тестировании кода обычно намного проще иметь дело с кодом, использующим этот шаблон, поэтому вы можете рассмотреть этот вариант.
Вы можете просто напрямую реализовать stuff () как общедоступное и вернуть parent :: stuff (). Смотрите мой ответ. Кажется, я сегодня слишком быстро читаю вещи.
Ты прав; Можно изменить защищенный метод на публичный.
Итак, код предлагает мой третий вариант: «Обратите внимание, что вы всегда можете заменить наследование композицией». идет в сторону моего первого варианта или refactoring.com/catalog/replaceInheritanceWithDelegation.htm l
Да первому. Насколько мелкозернистыми должны быть ваши объекты - вопрос стиля. Обычно я создаю объекты гораздо меньшего размера, чем большинство моих коллег.
Я не согласен, что это плохой знак. Давайте сделаем разницу между TDD и модульным тестированием. Модульное тестирование должно тестировать частные методы imo, так как они являются модулями и выиграют точно так же, как общедоступные методы модульного тестирования выигрывают от модульного тестирования.
На самом деле я сделал то же самое, что и вы, и решил праздно посмотреть, что делают другие. Даже не подумал о методе, который предложил MJ ala: реализация публичной функции protectedFoo () {parent :: _ protectedFoo ()}.
Защищенные методы являются являются частью интерфейса класса, это не просто детали реализации. Вся суть защищенных членов заключается в том, что подклассы (пользователи сами по себе) могут использовать эти защищенные методы внутри исключений класса. Эти явно нуждаются в проверке.
В PHP 7+ вы также можете использовать анонимные классы, например $foo = new class extends Foo {// declare a public version of stuff()}.
Это похоже на нежелательный совет и не отвечает на вопрос.
«Цель модульного теста - проверить интерфейс класса ...» Откуда вы пришли к такому выводу? На мой взгляд, unit test - это тестирование небольшой единицы вашего кода. Скорее всего, это функция. Будь то private, protected или public, это на самом деле деталь реализации. Независимо от того, является ли ваше устройство частью интерфейса public или нет, если оно протестировано, оно защищено от нежелательных изменений, которые могут нарушить его логику. Вам когда-нибудь приходилось исправлять ошибку в методе private? Если да, поможет ли модульный тест избежать этой ошибки? Не могу поверить, что люди обсуждают это.
Я думаю, что Трэлскн близок. Я бы сделал это вместо этого:
class ClassToTest
{
protected function testThisMethod()
{
// Implement stuff here
}
}
Затем реализуйте что-то вроде этого:
class TestClassToTest extends ClassToTest
{
public function testThisMethod()
{
return parent::testThisMethod();
}
}
Затем вы запускаете свои тесты с TestClassToTest.
Должна быть предусмотрена возможность автоматического создания таких классов расширения путем анализа кода. Я не удивлюсь, если PHPUnit уже предлагает такой механизм (хотя я не проверял).
Хех ... кажется, я говорю, используйте свой третий вариант :)
Да, это как раз мой третий вариант. Я почти уверен, что PHPUnit не предлагает такого механизма.
Это не сработает, вы не можете заменить защищенную функцию общедоступной функцией с тем же именем.
Возможно, я ошибаюсь, но я не думаю, что этот подход может сработать. PHPUnit (насколько я когда-либо его использовал) требует, чтобы ваш тестовый класс расширил другой класс, который обеспечивает фактическую функциональность тестирования. Если нет способа обойтись, я не уверен, что могу понять, как можно использовать этот ответ. phpunit.de/manual/current/en/…
К вашему сведению, этот онл работает для методов защищенный, а не для частных
Я предлагаю следующий обходной путь для обходного пути / идеи "Хенрика Пола" :)
Вы знаете имена частных методов вашего класса. Например, они похожи на _add (), _edit (), _delete () и т. д.
Следовательно, когда вы хотите протестировать его с точки зрения модульного тестирования, просто вызовите частные методы, добавив префикс и / или суффикс некоторого слова общий (например, _addPhpunit), чтобы при вызове метода __call () (поскольку метод _addPhpunit () не exist) класса владельца, вы просто помещаете необходимый код в метод __call () для удаления префиксов / суффиксов слова / слов (Phpunit), а затем для вызова этого выведенного частного метода оттуда. Это еще одно хорошее применение магических методов.
Попробуйте сами.
затрудняет поиск ссылок на вызовы частных методов, не так ли?
Вы действительно можете использовать __call () в общем виде для доступа к защищенным методам. Чтобы иметь возможность протестировать этот класс
class Example {
protected function getMessage() {
return 'hello';
}
}
вы создаете подкласс в ExampleTest.php:
class ExampleExposed extends Example {
public function __call($method, array $args = array()) {
if (!method_exists($this, $method))
throw new BadMethodCallException("method '$method' does not exist");
return call_user_func_array(array($this, $method), $args);
}
}
Обратите внимание, что метод __call () никоим образом не ссылается на класс, поэтому вы можете скопировать приведенное выше для каждого класса с защищенными методами, которые вы хотите протестировать, и просто изменить объявление класса. Вы можете поместить эту функцию в общий базовый класс, но я не пробовал.
Теперь сам тестовый пример отличается только тем, где вы создаете объект для тестирования, заменяя ExampleExposed на Example.
class ExampleTest extends PHPUnit_Framework_TestCase {
function testGetMessage() {
$fixture = new ExampleExposed();
self::assertEquals('hello', $fixture->getMessage());
}
}
Я считаю, что PHP 5.3 позволяет вам использовать отражение для непосредственного изменения доступности методов, но я предполагаю, что вам придется делать это для каждого метода индивидуально.
Реализация __call () отлично работает! Я попытался проголосовать, но отменил свой голос до тех пор, пока не протестирую этот метод, и теперь мне не разрешено голосовать из-за ограничения по времени в SO.
Функция call_user_method_array() устарела с PHP 4.1.0 ... используйте вместо нее call_user_func_array(array($this, $method), $args). Обратите внимание, что если вы используете PHP 5.3.2+, вы можете использовать Reflection to получить доступ к защищенным / частным методам и атрибутам
@nuqqsa - Спасибо, обновил свой ответ. С тех пор я написал общий пакет Accessible, который использует отражение, чтобы позволить тестам получать доступ к частным / защищенным свойствам и методам классов и объектов.
Этот код не работает для меня в PHP 5.2.7 - метод __call не вызывается для методов, определенных базовым классом. Я не могу найти это задокументированным, но я предполагаю, что это поведение было изменено в PHP 5.3 (где я подтвердил, что это работает).
@Russell - __call() вызывается только в том случае, если вызывающий не имеет доступа к методу. Поскольку класс и его подклассы имеют доступ к защищенным методам, их вызовы не будут проходить через __call(). Можете ли вы опубликовать свой код, который не работает в 5.2.7, в новом вопросе? Я использовал приведенное выше в 5.2 и перешел к использованию отражения только в версии 5.3.2.
@David, я использовал точный код выше (правда, мне пришлось исправить опечатку: protected getMessage() -> protected function getMessage()). Это приводит к Fatal error: Call to protected method Example::getMessage() from context ''
Обновление: работает на PHP 5.2.17. Поведение изменилось где-то между 5.2.7 и 5.2.17.
@Russell - Спасибо, это немного сужает круг вопросов. Я не помню, какую версию 5.2.x мы использовали, когда я писал выше. И да, если мне давно не нужно было набирать $, я возвращаюсь к написанию кода Java и отбрасываю function из всех моих методов. :)
Если вы используете PHP5 (> = 5.3.2) с PHPUnit, вы можете протестировать свои частные и защищенные методы, используя отражение, чтобы сделать их общедоступными перед запуском тестов:
protected static function getMethod($name) {
$class = new ReflectionClass('MyClass');
$method = $class->getMethod($name);
$method->setAccessible(true);
return $method;
}
public function testFoo() {
$foo = self::getMethod('foo');
$obj = new MyClass();
$foo->invokeArgs($obj, array(...));
...
}
Очень красивое решение. Хотелось бы добавить, что это только php> = 5.3.2;)
Процитирую ссылку на блог sebastians: «Итак: то, что тестирование защищенных и закрытых атрибутов и методов возможно, не означает, что это« хорошо ». - Просто чтобы иметь это в виду.
Я бы это оспорил. Если вам не нужны ваши защищенные или частные методы для работы, не тестируйте их.
Чтобы уточнить, вам не нужно использовать PHPUnit, чтобы это работало. Он также будет работать с SimpleTest или чем-то еще. В ответе нет ничего, что зависело бы от PHPUnit.
Вы не должны напрямую тестировать защищенных / закрытых членов. Они принадлежат внутренней реализации класса и не должны использоваться вместе с тестом. Это делает рефакторинг невозможным, и в конечном итоге вы не тестируете то, что нужно тестировать. Их нужно протестировать косвенно, используя общедоступные методы. Если вам это сложно, вы почти уверены, что есть проблема с составом класса и вам нужно разделить его на более мелкие классы. Имейте в виду, что ваш класс должен быть черным ящиком для вашего теста - вы что-то бросаете и получаете что-то обратно, и все!
Да, я думаю, что комментарий gphilip - лучший ответ.
@gphilip Для меня метод protected также является частью общедоступного API, потому что любой сторонний класс может расширить его и использовать без какой-либо магии. Поэтому я думаю, что только методы private попадают в категорию методов, которые не подлежат непосредственной проверке. protected и public следует тестировать напрямую.
@FilipHalaxa, верно, именно поэтому защищенные члены, мягко говоря, проблематичны. Их частое использование может сигнализировать о плохом дизайне. Для обеспечения возможности тестирования, повторного использования кода и простоты понимания предпочтите композицию наследованию и по возможности избегайте уязвимых защищенных членов.
На мой взгляд, модульное тестирование должно знать, что некоторые / многие разработчики, такие как я, выбирают Test Driven Design. TDD является всеобъемлющим, должны быть охвачены защищенные методы. Кроме того, мне нужно разработать простые защищенные методы, выполняющие сложные запросы (даже после использования классов CRUD), и я хочу тестировать их отдельно от вызывающих методов.
@gphilip Еще одна проблема с этой логикой заключается в том, что модульные тесты помогают не только указать, что «что-то сломано», но и помочь определить, «что сломано». Представьте себе класс с одним общедоступным методом и десятью защищенными методами. Тестирование ключевых защищенных методов, вероятно, значительно упростит быстрое определение того, что взломано. (Хотя, согласен; такой сценарий может указывать на плохой дизайн)
PHPUnit, например, позволяет использовать несколько наборов тестов. Если ваша разработка ориентирована на тестирование, у вас может быть набор тестов для общего назначения и еще один (который может быть добавлен в .gitignore) для TDD, который может быть удален позже.
@Arcesilas ... зачем вам удалять свой набор тестов TDD? В этом нет никакого смысла.
Что ж, это полезно, когда вы начинаете работать над «унаследованной» кодовой базой с интересной структурой кхм, включающей защищенные и статические методы повсюду. Итак, первый шаг к устранению этого беспорядка - это добавить модульные тесты и иметь возможность делать это на меньших модулях, а не на одном-единственном общедоступном методе, который вызывает 20 защищенных методов (которые вызывают еще 5-10 защищенных методов внутри них. !) более работоспособен.
IMHO имеет смысл тестировать защищенные методы классов, над которыми вы сейчас работаете (для потока TDD, при рефакторинге старого кода и т. д.).
Я не слышу, чтобы люди говорили не тестировать частные / защищенные функции только потому, что. Если ваш общедоступный метод основан на 2 частных методах, которые вы просто не хотите раскрывать, не протестировали бы вы эти частные методы? Сделать все общедоступным только ради этого, все равно что изменить каждый файл на сервере 777, чтобы не получить ошибку разрешения.
@Ben Я полностью согласен. Если кто-то не хочет тестировать частную функцию, зачем вообще создавать функцию?
@FilipHalaxa Если метод является общедоступным, то он является частью доступного API. Если метод защищен, его можно расширить. Если метод - private, API - это клавиатура владельца кода. Эти методы private можно использовать в других их методах protected или public, которые они полагаются на этот private method. В любом случае, протестируйте свой код ... или, возможно, давайте переключим все методы на private, чтобы вы могли сэкономить время на тестировании, по крайней мере, согласно самому продаваемому комментарию от @gphilip.
@edorian Намного лучше, чем вообще не иметь тестов.
Я собираюсь бросить здесь свою шляпу на ринг:
Я использовал хак __call с переменным успехом. Альтернатива, которую я придумал, заключалась в использовании шаблона Visitor:
1: создать stdClass или настраиваемый класс (для обеспечения типа)
2: заполните это требуемым методом и аргументами
3: убедитесь, что в вашем SUT есть метод acceptVisitor, который будет выполнять метод с аргументами, указанными в классе посещения
4: введите его в класс, который хотите протестировать
5: SUT вводит результат операции в посетителя
6. Примените условия тестирования к атрибуту результата посетителя
Я хотел бы предложить небольшое изменение getMethod (), определенного в ответ Укельмана.
Эта версия изменяет getMethod (), удаляя жестко запрограммированные значения и немного упрощая использование. Я рекомендую добавить его в ваш класс PHPUnitUtil, как в примере ниже, или в ваш класс расширения PHPUnit_Framework_TestCase (или, я полагаю, глобально в ваш файл PHPUnitUtil).
Поскольку MyClass все равно создается, а ReflectionClass может принимать строку или объект ...
class PHPUnitUtil {
/**
* Get a private or protected method for testing/documentation purposes.
* How to use for MyClass->foo():
* $cls = new MyClass();
* $foo = PHPUnitUtil::getPrivateMethod($cls, 'foo');
* $foo->invoke($cls, $...);
* @param object $obj The instantiated instance of your class
* @param string $name The name of your private/protected method
* @return ReflectionMethod The method you asked for
*/
public static function getPrivateMethod($obj, $name) {
$class = new ReflectionClass($obj);
$method = $class->getMethod($name);
$method->setAccessible(true);
return $method;
}
// ... some other functions
}
Я также создал функцию псевдонима getProtectedMethod (), чтобы четко указать, что ожидается, но это зависит от вас.
У ожог правильный подход. Еще проще вызвать метод напрямую и вернуть ответ:
class PHPUnitUtil
{
public static function callMethod($obj, $name, array $args) {
$class = new \ReflectionClass($obj);
$method = $class->getMethod($name);
$method->setAccessible(true);
return $method->invokeArgs($obj, $args);
}
}
Вы можете просто вызвать это в своих тестах:
$returnVal = PHPUnitUtil::callMethod(
$this->object,
'_nameOfProtectedMethod',
array($arg1, $arg2)
);
Это отличный пример, спасибо. Метод должен быть публичным, а не защищенным, не так ли?
Хорошая точка зрения. На самом деле я использую этот метод в своем базовом классе, из которого я расширяю свои тестовые классы, и в этом случае это имеет смысл. Однако имя класса здесь было бы неправильным.
Я сделал точно такой же фрагмент кода на основе teastburn xD
Так что это дискуссионный стиль, а значит, неконструктивный. Очередной раз :)