Pull to refresh

Comments 24

Если вам нужно инициализировать для тестов что-то приватное в классе через вот такие хитрые подвыподверты, то в первую очередь стоит подумать, а хороший ли это код? Описанный способ интересный, однако думаю стоит все таки отрефакторить класс в который вы так нагло врываетесь

Здесь основной посыл статьи не в том, как тесты в итоге реализовать пришлось, а в том, что с помощью Closure::bind() действительно можно сломать то, что якобы сломать невозможно.


Если мы заменим код на такой:


$createNewInstance = function () {
    $instance =  new self();
    self::$instance = $instance;
    return $instance;
};

то мы вовсе заменим оригинальный объект новым.


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


Следующим что будет, добавление новых методов в классы? :)

Да, я понимаю. Не только такие методы есть для того чтобы влезать внутрь «мозгов». Есть спец расширения для ре-дефайна переменных и подмены статических функций и тп. Но философский вопрос: а стоит ли о таком вообще знать и на кой черт это вообще создали разработчики PHP если вроде как мы тут все в мире боремся за качество кода?

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

Не совсем понятно чем рефлексия не подошла. Если только, как указали ниже, ради производительности

Скорее всего именно в производительности дело, рефлексия — дорогая. А в универсальных решениях часто рефлексия используется для анализа, получения списка свойств, а кложуры для собственно манипуляций. В идеале вообще рефлексия в «компайл-тайме» (кодогенерация) осуществляется, а в «ран-тайме» уже заточенные под классы акцессоры.
Именно, не использовал рефлексию для улучшения производительности тестов.
А рефлексия вам инкапсуляцию не размывала?
Мне нужно было задать начальное состояние объекта, для того чтобы протестировать изменение этого состояния. Грубо говоря — протестировать сеттер для свойства класса, так как в нём есть логика валидации.
начальное состояние должно задаваться в самом классе вроде private $_var = null;
перед запуском сеттера вы можете убедиться что начальное состояние задано в дефолт через геттер.
тут вообще не нужно лезть в потроха класса, если там приватные переменные вас как вызывающего вообще не должно волновать что там внутри. сделайте black-box тестирование на этот класс и все
Ваши тесты проверяют не поведение, а внутреннее состояние экземпляра класса, которое, вобщем то, клиентский код волновать не должно.
Зачем нужны модульные тесты, которые взаимодействуют с объектом совершенно иным образом, чем это делает клиентский код?
Слово «модульные» вы произнесли первым, в статье этого нет.

"Одним прекрасным рабочим днём я писал unit-тесты ..."

Мне кажется это справедливо не только для модульных тестов. Если внешний код не может что-то сделать (неважно клиентский он или просто другой класс ваш же), то и проверять эту функциональность большого смысла нет.
Но при этом лезть в класс как это делал автор поста вполне может понадобиться — когда это легаси код рефакторить который сейчас слишком дорого, а его тестируемость хромает, то это может оказаться самым быстрым, простым и в итоге правильным способом протестировать что-либо. Например когда как в посте нужно проверить поведение в определенном состоянии.
Можно ли редактировать класс, в котором надо инициализировать приватные свойства? Если да, то можно добавить в класс функцию, которая инициализирует свойства.
Обычными сеттерами нельзя было пользоваться, так как там была прописана некая логика. Унаследовать или замокать класс тоже не получалось, потому что он объявлён финальным. И даже рефлексия не подошла.

ЧЯДНТ:


final class Foo {
  private $prop;

  public function __construct() {
    $this->prop = 1;
  }

  public function getProp() {
    return $this->prop;
  }
}

$foo = new Foo();
$fooR = new ReflectionObject($foo);
$prop = $fooR->getProperty('prop');
$prop->setAccessible(true);
$prop->setValue($foo, 2);
echo $foo->getProp();

Песочница

На сколько я помню из своих тестов, вариант с биндингом примерно в три раза быстрее рефлексии.
есть ли смысл здесь да и вообще в Singleton использовать позднее статическое связывание —
вместо self — static?
Смысла нету так как класс объявлен финальным и мы не можем его унаследовать.
Sign up to leave a comment.

Articles