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


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

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

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

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

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

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

Author: Community, 2008-10-30

8 answers

Если вы используете 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(...));
  ...
}
 368
Author: uckelman, 2010-10-24 10:49:41

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

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

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

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

 41
Author: troelskn, 2008-10-30 10:46:19

У Тистберна правильный подход. Еще проще вызвать метод напрямую и вернуть ответ:

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)
             );
 27
Author: robert.egginton, 2017-05-23 12:10:41

Я хотел бы предложить небольшое изменение для метода(), определенного в ответе Укельмана.

Эта версия изменяет GetMethod(), удаляя жестко закодированные значения и немного упрощая использование. Я рекомендую добавить его в ваш класс phpunitutil, как в примере ниже, или в ваш класс расширения PHPUnit_Framework_TestCase (или, я полагаю, глобально в ваш файл phpunitutil).

Поскольку экземпляр MyClass создается в любом случае, а класс отражения может принимать строку или объект...

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(), чтобы указать, что ожидается, но это зависит от вас.

Ура!

 17
Author: teastburn, 2017-05-23 12:02:48

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

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

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

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

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

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

 8
Author: Michael Johnson, 2008-10-31 18:36:25

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

Я использовал взлом __ call со смешанной степенью успеха. Альтернативой, которую я придумал, было использование шаблона посетителя:

1: создайте стандартный класс или пользовательский класс (для принудительного ввода типа)

2: запишите это с помощью требуемого метода и аргументов

3: убедитесь, что у вашего SUT есть метод acceptVisitor, который будет выполнять метод с аргументами, указанными в классе посещения

4: введите его в класс, который вы хотите протестировать

5: SUT вводит результат операции в посетителя

6: примените свои условия тестирования к атрибуту результата посетителя

 3
Author: sunwukung, 2010-08-26 15:59:45

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

class ExampleTest extends PHPUnit_Framework_TestCase {
    function testGetMessage() {
        $fixture = new ExampleExposed();
        self::assertEquals('hello', $fixture->getMessage());
    }
}

Я считаю, что PHP 5.3 позволяет вам использовать отражение для непосредственного изменения доступности методов, но я предполагаю, что вам придется делать это для каждого метода в отдельности.

 3
Author: David Harkness, 2011-06-23 17:14:55

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

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

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

Попробуйте это.

 2
Author: Anirudh Zala, 2009-09-09 10:32:34