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


Я нашел дискуссию о том, тестируете ли вы частный метод информативным.

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

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

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

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

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

8 240

8 ответов:

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

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

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

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

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

teastburn при правильном подходе. Еще проще-вызвать метод напрямую и вернуть ответ:

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

Я хотел бы предложить небольшое изменение getMethod (), определенное в uckelman это.

эта версия изменяет getMethod (), удаляя жестко закодированные значения и немного упрощая использование. Я рекомендую добавить его в ваш класс PHPUnitUtil, как в примере ниже, или в ваш класс phpunit_framework_testcase-extending (или, я полагаю, глобально в ваш файл PHPUnitUtil).

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

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 ClassToTest
{
   protected testThisMethod()
   {
     // Implement stuff here
   }
}

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

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

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

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

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

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

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

2: prime, что с требуемым методом и аргументами

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

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

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

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

вы действительно можете использовать __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 позволяет использовать отражение для изменения доступности методов напрямую, но я предполагаю, что вам придется сделать это для каждого метода в отдельности.

Я предлагаю следующий обходной путь для"Henrik Paul" 'ы обходной путь / идея:)

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

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

попробовать его.