Я нашел дискуссию о тестировании частного метода информативным.
Я решил, что в некоторых классах я хочу иметь защищенные методы, но проверять их. Некоторые из этих методов являются статическими и короткими. Поскольку большинство общедоступных методов используют их, я, вероятно, смогу безопасно удалить тесты позже. Но для того, чтобы начать с подхода 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(); } }
Обратите внимание, что вы всегда можете заменить наследование композицией. При тестировании кода обычно проще справляться с кодом, который использует этот шаблон, поэтому вы можете рассмотреть этот вариант.
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 класс (или, я полагаю, глобально для вашего файла 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 (), чтобы быть явным, что ожидается, но это зависит от вас.
Ура!
Я думаю, что troelskn близок. Я бы сделал это вместо этого:
class ClassToTest { protected testThisMethod() { // Implement stuff here } }
Затем выполните следующее:
class TestClassToTest extends ClassToTest { public testThisMethod() { return parent::testThisMethod(); } }
Затем вы проводите тесты с TestClassToTest.
Должно быть возможно автоматически генерировать такие классы расширений, анализируя код. Я не удивлюсь, если PHPUnit уже предлагает такой механизм (хотя я еще не проверил).
Я собираюсь бросить свою шляпу на ринг здесь:
Я использовал __call hack со смешанными степенями успеха. Альтернатива, с которой я столкнулся, заключалась в использовании шаблона посетителя:
1: сгенерировать stdClass или пользовательский класс (для принудительного ввода типа)
2: штрих, что с требуемым методом и аргументами
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 for Example.
class ExampleTest extends PHPUnit_Framework_TestCase { function testGetMessage() { $fixture = new ExampleExposed(); self::assertEquals('hello', $fixture->getMessage()); } }
Я считаю, что PHP 5.3 позволяет использовать отражение, чтобы напрямую изменить доступность методов, но я предполагаю, что вам придется делать это для каждого метода индивидуально.
Я предлагаю следующее обходное решение для обходного пути / идеи Хенрика Пола 🙂
Вы знаете имена частных методов вашего класса. Например, они похожи на _add (), _edit (), _delete () и т. Д.
Следовательно, если вы хотите протестировать его с точки зрения модульного тестирования, просто вызовите частные методы, префикс и / или суффикс некоторого общего слова (например, _addPhpunit), чтобы при вызове метода __call () (поскольку метод _addPhpunit () не существует) класса владельца, вы просто поместили необходимый код в метод __call (), чтобы удалить префикс / суффикс word / s (Phpunit), а затем вызвать оттуда выделенный метод. Это еще одно хорошее применение магических методов.
Попробуйте.