Трусов Михаил — SuperProgrammist.Ru

Михаил Юрьевич Трусов

Более 25 лет опыта в программировании

Как быстро и просто создать mock объект для тестирования в PHP

28.01.2024

На днях на работе потребовалось получить доступ к защищенным методам объекта при использовании его в тестах. Сначала было использовано быстрое и спорное решение: сделать метод публичным. В принципе, менять доступ к методам классов, если этого требуют тесты, считается допустимой практикой (подробней). Но не покидало ощущение, что можно сделать лучше и чище.

Так и вышло. Вспомнился прием, прочитанный в книге Роя Ошероува "Искусство автономного тестирования" (подробней), который не затрагивает тестируемый код, но дает нам доступ к его защищенным членам и методам. При этом все изменения можно делать локально в текущем тестовом методе. Итак, имеем некий класс с защищенным методом:

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

Поэтому в тестовом методе мы тестируем не сам класс, а его наследника, сформированного следующим образом:

Собственно, и все. Таким способом мы можем создавать индивидуального наследника тестируемого класса для каждого теста прямо в методе самого теста, открывая только нужные нам методы или меняя их поведение, не прибегая к использованию специальных библиотек для создания тестовых двойников. Конечно, для более сложных случаев, неизбежно использование этих библиотек, но часто будет достаточно этого просто способа.

Надо обратить внимание, что определенные таким образом подклассы помещаются в глобальное пространство имен и хранятся там до окончания работы скрипта (программы). Поэтому переопределить их будет нельзя и для каждого отдельного подкласса в каждом тестовом методе нужно задавать свое собственное имя, чтобы не возникало ошибки типа "Fatal error: Cannot declare class ..., because the name is already in use".