Рекомендации по тестированию защищенных методов с помощью PHPUnit

Я нашел обсуждение Вы тестируете частный метод информативным.

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

Я подумал о следующем:

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

Какая лучшая практика? Что-нибудь еще?

Кажется, что JUnit автоматически меняет защищенные методы на публичные, но я не стал на это более подробно останавливаться. PHP не позволяет этого через отражение.

Так что это дискуссионный стиль, а значит, неконструктивный. Очередной раз :)

mlvljr 21.05.2012 16:01

Вы можете назвать это нарушением правил сайта, но просто назвать это «неконструктивным» ... это оскорбительно.

Andy V 11.10.2012 02:23

Два вопроса: 1. Почему вы должны беспокоиться о тестировании функциональности, которую не предоставляет ваш класс? 2. Если вы должны его протестировать, почему он частный?

nad2000 20.03.2011 14:50

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

AntonioCS 23.04.2012 21:59

@Visser, обидно сам;)

Pacerier 21.08.2015 09:41

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

robertmain 14.03.2018 21:53

@lenswipe, тогда он должен провести модульное тестирование API дочернего класса, который будет использовать защищенные методы

Jeremy Belolo 05.04.2018 10:52

@ Джереми Белоло, и для этого он ... Что именно?

robertmain 07.04.2018 17:01

@robertmain: как он написал, возможно, не совсем ясно: API должен расширяться от базового класса, а затем использовать защищенные (иначе в любом случае они не были бы использованы - по-настоящему, сократите его). Модуль, который может предоставлять именно те общедоступные методы представления API и т. д. Это также покажет, имеет ли расширение действительно смысл. Обычно эти тесты и их заглушки / макеты должны быть частью основной библиотеки, если она привязана к защищенной видимости. Если эти модульные тесты недоступны из первых рук, как вы узнаете, что это всего лишь чушь на защищенном уровне?

hakre 16.03.2019 02:43

Кроме защищенных методов - это деталь реализации. Вы предлагаете тестировать реализацию, а не интерфейс.

robertmain 17.03.2019 06:24
Стоит ли изучать PHP в 2026-2027 годах?
Стоит ли изучать PHP в 2026-2027 годах?
Привет всем, сегодня я хочу высказать свои соображения по поводу вопроса, который я уже много раз получал в своем сообществе: "Стоит ли изучать PHP в...
Symfony Station Communiqué - 7 июля 2023 г
Symfony Station Communiqué - 7 июля 2023 г
Это коммюнике первоначально появилось на Symfony Station .
Оживление вашего приложения Laravel: Понимание режима обслуживания
Оживление вашего приложения Laravel: Понимание режима обслуживания
Здравствуйте, разработчики! В сегодняшней статье мы рассмотрим важный аспект управления приложениями, который часто упускается из виду в суете...
Установка и настройка Nginx и PHP на Ubuntu-сервере
Установка и настройка Nginx и PHP на Ubuntu-сервере
В этот раз я сделаю руководство по установке и настройке nginx и php на Ubuntu OS.
Коллекции в Laravel более простым способом
Коллекции в Laravel более простым способом
Привет, читатели, сегодня мы узнаем о коллекциях. В Laravel коллекции - это способ манипулировать массивами и играть с массивами данных. Благодаря...
Как установить PHP на Mac
Как установить PHP на Mac
PHP - это популярный язык программирования, который используется для разработки веб-приложений. Если вы используете Mac и хотите разрабатывать...
306
10
130 328
8
Перейти к ответу Данный вопрос помечен как решенный

Ответы 8

Кажется, вы уже в курсе, но я все равно повторю это заново; Плохой знак, если нужно протестировать защищенные методы. Цель модульного теста - проверить интерфейс класса, а защищенные методы - это детали реализации. Тем не менее, есть случаи, когда это имеет смысл. Если вы используете наследование, вы можете увидеть суперкласс как обеспечивающий интерфейс для подкласса. Итак, здесь вам нужно будет протестировать защищенный метод (но никогда не частный). Решение этой проблемы - создать подкласс для целей тестирования и использовать его для предоставления методов. Например.:

class Foo {
  protected function stuff() {
    // secret stuff, you want to test
  }
}

class SubFoo extends Foo {
  public function exposedStuff() {
    return $this->stuff();
  }
}

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

Вы можете просто напрямую реализовать stuff () как общедоступное и вернуть parent :: stuff (). Смотрите мой ответ. Кажется, я сегодня слишком быстро читаю вещи.

Michael Johnson 31.10.2008 21:39

Ты прав; Можно изменить защищенный метод на публичный.

troelskn 01.11.2008 20:34

Итак, код предлагает мой третий вариант: «Обратите внимание, что вы всегда можете заменить наследование композицией». идет в сторону моего первого варианта или refactoring.com/catalog/replaceInheritanceWithDelegation.htm‌ l

GrGr 03.11.2008 11:55

Да первому. Насколько мелкозернистыми должны быть ваши объекты - вопрос стиля. Обычно я создаю объекты гораздо меньшего размера, чем большинство моих коллег.

troelskn 03.11.2008 12:52

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

koen 11.12.2009 22:56

На самом деле я сделал то же самое, что и вы, и решил праздно посмотреть, что делают другие. Даже не подумал о методе, который предложил MJ ala: реализация публичной функции protectedFoo () {parent :: _ protectedFoo ()}.

Aries VII 24.01.2011 14:11

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

B T 13.02.2011 13:53

В PHP 7+ вы также можете использовать анонимные классы, например $foo = new class extends Foo {// declare a public version of stuff()}.

Alessandro Benoit 12.04.2018 18:17

Это похоже на нежелательный совет и не отвечает на вопрос.

PHPst 31.05.2018 01:15

«Цель модульного теста - проверить интерфейс класса ...» Откуда вы пришли к такому выводу? На мой взгляд, unit test - это тестирование небольшой единицы вашего кода. Скорее всего, это функция. Будь то private, protected или public, это на самом деле деталь реализации. Независимо от того, является ли ваше устройство частью интерфейса public или нет, если оно протестировано, оно защищено от нежелательных изменений, которые могут нарушить его логику. Вам когда-нибудь приходилось исправлять ошибку в методе private? Если да, поможет ли модульный тест избежать этой ошибки? Не могу поверить, что люди обсуждают это.

NeverEndingQueue 15.02.2020 01:15

Я думаю, что Трэлскн близок. Я бы сделал это вместо этого:

class ClassToTest
{
   protected function testThisMethod()
   {
     // Implement stuff here
   }
}

Затем реализуйте что-то вроде этого:

class TestClassToTest extends ClassToTest
{
  public function testThisMethod()
  {
    return parent::testThisMethod();
  }
}

Затем вы запускаете свои тесты с TestClassToTest.

Должна быть предусмотрена возможность автоматического создания таких классов расширения путем анализа кода. Я не удивлюсь, если PHPUnit уже предлагает такой механизм (хотя я не проверял).

Хех ... кажется, я говорю, используйте свой третий вариант :)

Michael Johnson 31.10.2008 21:37

Да, это как раз мой третий вариант. Я почти уверен, что PHPUnit не предлагает такого механизма.

GrGr 03.11.2008 11:46

Это не сработает, вы не можете заменить защищенную функцию общедоступной функцией с тем же именем.

Koen. 15.07.2014 16:37

Возможно, я ошибаюсь, но я не думаю, что этот подход может сработать. PHPUnit (насколько я когда-либо его использовал) требует, чтобы ваш тестовый класс расширил другой класс, который обеспечивает фактическую функциональность тестирования. Если нет способа обойтись, я не уверен, что могу понять, как можно использовать этот ответ. phpunit.de/manual/current/en/…

Cypher 08.10.2015 00:42

К вашему сведению, этот онл работает для методов защищенный, а не для частных

Sliq 09.10.2019 13:56

Я предлагаю следующий обходной путь для обходного пути / идеи "Хенрика Пола" :)

Вы знаете имена частных методов вашего класса. Например, они похожи на _add (), _edit (), _delete () и т. д.

Следовательно, когда вы хотите протестировать его с точки зрения модульного тестирования, просто вызовите частные методы, добавив префикс и / или суффикс некоторого слова общий (например, _addPhpunit), чтобы при вызове метода __call () (поскольку метод _addPhpunit () не exist) класса владельца, вы просто помещаете необходимый код в метод __call () для удаления префиксов / суффиксов слова / слов (Phpunit), а затем для вызова этого выведенного частного метода оттуда. Это еще одно хорошее применение магических методов.

Попробуйте сами.

затрудняет поиск ссылок на вызовы частных методов, не так ли?

Toskan 13.11.2020 02:13

Вы действительно можете использовать __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.

Adam Franco 14.09.2010 22:25

Функция call_user_method_array() устарела с PHP 4.1.0 ... используйте вместо нее call_user_func_array(array($this, $method), $args). Обратите внимание, что если вы используете PHP 5.3.2+, вы можете использовать Reflection to получить доступ к защищенным / частным методам и атрибутам

nuqqsa 25.05.2011 15:58

@nuqqsa - Спасибо, обновил свой ответ. С тех пор я написал общий пакет Accessible, который использует отражение, чтобы позволить тестам получать доступ к частным / защищенным свойствам и методам классов и объектов.

David Harkness 25.05.2011 22:24

Этот код не работает для меня в PHP 5.2.7 - метод __call не вызывается для методов, определенных базовым классом. Я не могу найти это задокументированным, но я предполагаю, что это поведение было изменено в PHP 5.3 (где я подтвердил, что это работает).

Russell Davis 23.06.2011 05:22

@Russell - __call() вызывается только в том случае, если вызывающий не имеет доступа к методу. Поскольку класс и его подклассы имеют доступ к защищенным методам, их вызовы не будут проходить через __call(). Можете ли вы опубликовать свой код, который не работает в 5.2.7, в новом вопросе? Я использовал приведенное выше в 5.2 и перешел к использованию отражения только в версии 5.3.2.

David Harkness 23.06.2011 06:28

@David, я использовал точный код выше (правда, мне пришлось исправить опечатку: protected getMessage() -> protected function getMessage()). Это приводит к Fatal error: Call to protected method Example::getMessage() from context ''

Russell Davis 23.06.2011 11:05

Обновление: работает на PHP 5.2.17. Поведение изменилось где-то между 5.2.7 и 5.2.17.

Russell Davis 23.06.2011 12:15

@Russell - Спасибо, это немного сужает круг вопросов. Я не помню, какую версию 5.2.x мы использовали, когда я писал выше. И да, если мне давно не нужно было набирать $, я возвращаюсь к написанию кода Java и отбрасываю function из всех моих методов. :)

David Harkness 23.06.2011 21:17
Ответ принят как подходящий

Если вы используете 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;)

flungabunga 18.10.2010 05:57

Процитирую ссылку на блог sebastians: «Итак: то, что тестирование защищенных и закрытых атрибутов и методов возможно, не означает, что это« хорошо ». - Просто чтобы иметь это в виду.

edorian 08.06.2011 20:26

Я бы это оспорил. Если вам не нужны ваши защищенные или частные методы для работы, не тестируйте их.

uckelman 09.06.2011 12:06

Чтобы уточнить, вам не нужно использовать PHPUnit, чтобы это работало. Он также будет работать с SimpleTest или чем-то еще. В ответе нет ничего, что зависело бы от PHPUnit.

Ian Dunn 24.08.2011 08:57

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

gphilip 08.02.2012 19:48

Да, я думаю, что комментарий gphilip - лучший ответ.

Andrew 09.06.2016 15:56

@gphilip Для меня метод protected также является частью общедоступного API, потому что любой сторонний класс может расширить его и использовать без какой-либо магии. Поэтому я думаю, что только методы private попадают в категорию методов, которые не подлежат непосредственной проверке. protected и public следует тестировать напрямую.

Filip Halaxa 15.06.2017 09:01

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

gphilip 15.06.2017 16:45

На мой взгляд, модульное тестирование должно знать, что некоторые / многие разработчики, такие как я, выбирают Test Driven Design. TDD является всеобъемлющим, должны быть охвачены защищенные методы. Кроме того, мне нужно разработать простые защищенные методы, выполняющие сложные запросы (даже после использования классов CRUD), и я хочу тестировать их отдельно от вызывающих методов.

Dario Fumagalli 22.11.2017 12:15

@gphilip Еще одна проблема с этой логикой заключается в том, что модульные тесты помогают не только указать, что «что-то сломано», но и помочь определить, «что сломано». Представьте себе класс с одним общедоступным методом и десятью защищенными методами. Тестирование ключевых защищенных методов, вероятно, значительно упростит быстрое определение того, что взломано. (Хотя, согласен; такой сценарий может указывать на плохой дизайн)

rinogo 29.01.2018 21:23

PHPUnit, например, позволяет использовать несколько наборов тестов. Если ваша разработка ориентирована на тестирование, у вас может быть набор тестов для общего назначения и еще один (который может быть добавлен в .gitignore) для TDD, который может быть удален позже.

Arcesilas 13.04.2018 15:42

@Arcesilas ... зачем вам удалять свой набор тестов TDD? В этом нет никакого смысла.

robertmain 04.04.2019 18:22

Что ж, это полезно, когда вы начинаете работать над «унаследованной» кодовой базой с интересной структурой кхм, включающей защищенные и статические методы повсюду. Итак, первый шаг к устранению этого беспорядка - это добавить модульные тесты и иметь возможность делать это на меньших модулях, а не на одном-единственном общедоступном методе, который вызывает 20 защищенных методов (которые вызывают еще 5-10 защищенных методов внутри них. !) более работоспособен.

Juha Untinen 18.06.2019 10:17

IMHO имеет смысл тестировать защищенные методы классов, над которыми вы сейчас работаете (для потока TDD, при рефакторинге старого кода и т. д.).

ivanhoe 08.08.2019 22:36

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

Ben 29.09.2019 21:49

@Ben Я полностью согласен. Если кто-то не хочет тестировать частную функцию, зачем вообще создавать функцию?

NeverEndingQueue 15.02.2020 01:04

@FilipHalaxa Если метод является общедоступным, то он является частью доступного API. Если метод защищен, его можно расширить. Если метод - private, API - это клавиатура владельца кода. Эти методы private можно использовать в других их методах protected или public, которые они полагаются на этот private method. В любом случае, протестируйте свой код ... или, возможно, давайте переключим все методы на private, чтобы вы могли сэкономить время на тестировании, по крайней мере, согласно самому продаваемому комментарию от @gphilip.

NeverEndingQueue 15.02.2020 01:08

@edorian Намного лучше, чем вообще не иметь тестов.

NeverEndingQueue 15.02.2020 01:24

Я собираюсь бросить здесь свою шляпу на ринг:

Я использовал хак __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)
             );

Это отличный пример, спасибо. Метод должен быть публичным, а не защищенным, не так ли?

valk 26.03.2012 11:19

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

robert.egginton 04.04.2012 20:34

Я сделал точно такой же фрагмент кода на основе teastburn xD

Nebulosar 12.07.2019 10:51

Другие вопросы по теме