Как стать автором
Обновить

Комментарии 24

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

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


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


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

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


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


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

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

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

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

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

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

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

Нарушение SRP, нет?

Обычными сеттерами нельзя было пользоваться, так как там была прописана некая логика. Унаследовать или замокать класс тоже не получалось, потому что он объявлён финальным. И даже рефлексия не подошла.

ЧЯДНТ:


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?
Смысла нету так как класс объявлен финальным и мы не можем его унаследовать.
Зарегистрируйтесь на Хабре, чтобы оставить комментарий

Публикации

Истории