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

StackOverflow https://stackoverflow.com/questions/249664

  •  05-07-2019
  •  | 
  •  

Вопрос

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

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

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

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

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

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

Это было полезно?

Решение

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

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

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

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(), определенное в ответ укельмана.

Эта версия изменяет 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 ClassToTest
{
   protected testThisMethod()
   {
     // Implement stuff here
   }
}

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

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

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

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

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

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

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

2:дополните это требуемым методом и аргументами

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

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() и т.д.

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

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

Лицензировано под: CC-BY-SA с атрибуция
Не связан с StackOverflow
scroll top