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

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

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

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

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

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

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

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 может принять строка или объект...

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 со смешанными степенями успеха. Другой альтернативы я придумал использовать шаблон Visitor:

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

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

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

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


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

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

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

попробуйте.