phpunit: стабы для protected методов

ivanov77

Новичок
Приветствую.
Не подскажете почему в документации сказано прямо:
Please note that final, private, protected, and static methods cannot be stubbed or mocked. They are ignored by PHPUnit’s test double functionality and retain their original behavior
Но при этом защищенные методы получается "стабировать":
Код:
class First
{
    public function one()
    {
        return 'one';
    }

    public function two()
    {
        return 'two';
    }

    public function check()
    {
        return $this->checkProtected();
    }

    protected function checkProtected()
    {
        return 'checkProtected';
    }
}
Вот, все три теста проходят:
Код:
    public function testEight()
    {
        // стабируем только метод two

        $stub = $this->getMockBuilder(First::class)
            ->setMethods(['two'])
            ->getMock();

        $this->assertEquals('one', $stub->one());
        $this->assertEquals(null, $stub->two());
        $this->assertEquals('checkProtected', $stub->check());

    }

    public function testNine()
    {
       // стабируем только метод checkProtected, возвращаемое значение по умолчанию

        $stub = $this->getMockBuilder(First::class)
            ->setMethods(['checkProtected'])
            ->getMock();

        $this->assertEquals('one', $stub->one());
        $this->assertEquals(null, $stub->check());

    }

    public function testTen()
    {
       // стабируем только метод checkProtected, возвращаемое значение настраиваем

        $stub = $this->getMockBuilder(First::class)
            ->setMethods(['checkProtected'])
            ->getMock();
        $stub->method('checkProtected')
            ->will($this->returnValue('testTen'));

        $this->assertEquals('one', $stub->one());
        $this->assertEquals('testTen', $stub->check());

    }
 

Вурдалак

Продвинутый новичок
Чисто технически, подобных ограничений быть не должно, если класс не final. Но такое поведение по возможности должно быть запрещено (protected-методы — не публичный интерфейс, а детали реализации).

Может, твоя версия PHPUnit и версия, к которой ты смотришь документацию, разнятся?
 

AnrDaemon

Продвинутый новичок
IMHO, если это unit-тестирование, тестировать protected методы вполне нормально.
 

Вурдалак

Продвинутый новичок
Есть вероятность, что это ошибка документации, попробуй issue на github создать: https://github.com/sebastianbergmann/phpunit-documentation-english/

Да, ты тестируешь public, используя protected как точку расширения. Здесь, кажется, просто сказывается моя нелюбовь к protected и наследованию классов.
Есть кейс, когда такой mock/stub точно понадобится: если бы сделали абстрактный класс, где один метод имеет какую-то логику и использует второй метод, который объявлен как абстрактный.
Просто если тебе нужна подобная точка расширения, то _всегда_ можно обойтись без наследования.
В особенности плохо выглядит кейс, когда ты перекрываешь не abstract метод. А потом ещё раз перекрываешь. А потом ты осознаёшь, что у тебя существует compile-time зависимость вместо runtime-time:
Код:
class XmlDoctrineConfigParser extends XmlConfigParser
class XmlConfigParser extends ConfigParser
vs
Код:
final class ConfigParser { ... }

new ConfigParser($doctrineRules, $xmlAdapter)
 

AnrDaemon

Продвинутый новичок
А если у меня protected метод является точкой сопряжения? Зачем мне мучаться и мокать PDO(например), если я могу перекрыть один метод в самом классе?
 

Вурдалак

Продвинутый новичок
А если у меня protected метод является точкой сопряжения? Зачем мне мучаться и мокать PDO(например), если я могу перекрыть один метод в самом классе?
Если вопрос ко мне, то я вроде достаточно ясно выразился: моя претензия к наследованию в принципе; но если мы уже в дерьме, то хуже не будет. Наследоваться от PDO — пздц. (@Фанат, ничего личного).
 

WMix

герр M:)ller
Партнер клуба
Наследоваться от PDO — пздц.
обойтись можно, но тебе ли как пользователю обертки (наследованной от pdo) не пофигу ли? обертка возвращает довольно понятные действия, некий драйвер с часто употребляемыми коммандами, нечто под именем dbal/connection, к примеру. при этом на уровне orm, это вообще часть маппера, который в свою очередь не является частью домена.
 

Вурдалак

Продвинутый новичок
обойтись можно, но тебе ли как пользователю обертки (наследованной от pdo) не пофигу ли? обертка возвращает довольно понятные действия, некий драйвер с часто употребляемыми коммандами, нечто под именем dbal/connection, к примеру. при этом на уровне orm, это вообще часть маппера, который в свою очередь не является частью домена.
Мне — не пофиг. PDO в принципе дерьмище неудобное, а тут ещё отнаследовались, поменяли поведение (это хорошо, если контракт исходного класса не нарушили), теперь при клике на тот или иной метод я уже рискую проваливаться в разные классы.

Я не понимаю, что сложного в полноценной обёртке. Которая оборачивает (композиция), а не наследует. Где ты будешь полностью контролировать поведение, выбрасываемые исключения, где будет гарантия отсутствия нарушения контракта parent class'а:
PHP:
<?php

final class SuperPdo
{
    public function __construct(PDO $pdo)
    {
    }

    // ...
}
 

WMix

герр M:)ller
Партнер клуба
чаще всего все сводится к банальному crud причем без r короткий синтакс db->insert/update/delete а остальное не трогая
эта часть очень легко меняется. но всякое повидал
что сложного в полноценной обёртке.
сложно понять надобность шуметь типа

PHP:
public function execute(){
  $this->pdo->execute()
}
только чтоб обеспечить композицию
при этом наследуя опишешь всего 3 метода
 
Последнее редактирование:

Вурдалак

Продвинутый новичок
сложно понять надобность шуметь типа
PHP:
public function execute(){
  $this->pdo->execute()
}
Подобное делегирование стоит копейки в разработке, зато ты сразу избавляешься от прямой зависимости от PDO.
Хочешь всегда выкидывать исключения? Зашей это сразу в настройки в конструкторе.
Хочешь запретить вызывать всякие setAttribute (по крайней мере напрямую) где-то далеко от этапа конфигурации? Не делай такого метода.
Хочешь сделать fluent interface, как это хотел сделать @Фанат и нарушил LSP? https://phpclub.ru/talk/threads/Разделение-dbal-на-два-класса-connection-и-statement.79554/page-4#post-717712
Код:
    function execute($data = array())
    {
        parent::execute($data);
        return $this;
    }
Делай fluent interface, но не нарушай LSP.
Хочешь избавиться от мусорных методов типа quote() — избавляйся.

Наследуюясь от чего-то, ты также наследуешь проблемы и неудачные моменты оригинального решения, ты завязываешь код своего проекта на решение, которое может местами не устраивать.

Как раз-таки «шумом» является весь тот мусор, который будет в рабочем скоупе при использовании полученного монстра.
Я не очень понимаю желание сэкономить 5 минут на обёртке.
Мне наплевать на количество строчек в полученном классе, который я буду открывать раз в N месяцев. А с API connection'а я буду сталкиваться, возможно, каждый день.
Так что у меня совсем иные приоритеты.
Возможно, я, делая композицию, гораздо больший прагматик, чем ты из себя воображаешь.
 

WMix

герр M:)ller
Партнер клуба
Может прагматик, может зануда, разговор о таких мелочах, на которые даже внимание обращать не хочется. слово пдо в проекте встречается крайне редко и как говорил внури композиции более высокого уровня типа маппер или репа. И в этом случае строя композицию такого шума уже не наблюдается, интерфейс совершенно другой. По мне, чем меньше написано, тем лучше.
 

Вурдалак

Продвинутый новичок
при этом на уровне orm, это вообще часть маппера, который в свою очередь не является частью домена.
внури композиции более высокого уровня типа маппер или репа
Когда говорят про (не)удобства конкретного инструмента, который используется на определенном уровне абстракции, отвечать: «слушай, ну ведь есть другие уровни абстракции, где этого инструмента нет» — это странно. Это не решает проблему с инструментом.
 

AnrDaemon

Продвинутый новичок
Главное, это не создаёт других проблем.
Так что всё, что вы тут обсуждаете - философия, не больше. Не надо доводить фантазии до абсурда, надо уметь вовремя остановиться.
 

Adelf

Administrator
Команда форума
@AnrDaemon, не фантазии. наследование может ударить с двух сторон. и когда нарушаешь Лисков(тут очевидно). и когда предка могут отрефакторить, не меняя контракта, а наследник уже будет с багом. Это вероятно не про пдо, но привычку не надо такую создавать. Такие баги могут быть весьма неприятными.
Шум, много кода... там делов на 2-3 минуты, а избавит от беспокойства за будущее!11
 

Вурдалак

Продвинутый новичок
Так что всё, что вы тут обсуждаете - философия, не больше.
Ворваться в середину дискуссии по принципам ОО дизайна и сказать «работает — и ладно!» — это ещё куда ни шло.
Но говорить после этого, что мы тут философией занимаемся — это троллинг высокого уровня.
 

WMix

герр M:)ller
Партнер клуба
середина дискуссии это возможность стабить там, и только там где не лежит ответственность тестируемого обьекта, и до тех пор пока используюмая обертка взаимозаменяемая (стабится) и основа поддается тестированию, то вообще проблем нет
 
Последнее редактирование:
Сверху