Потому что тестирование не должно быть одиссеей по 1000 строк на тест.

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

PHPUnit уже включает в себя то, что называется Test Doubles, но он несколько базовый, поскольку не находится в центре внимания самой тестовой среды. Тем не менее, это по-прежнему хороший инструмент, но не то, чем я часто пользуюсь.

Вместо этого я в основном использую Mockery, библиотеку для имитации объектов для тестирования. Это не теневая библиотека, вместо этого на момент написания статьи ее загрузили более 160 миллионов раз. Синтаксис очень простой и достаточно короткий, чтобы понять, что вы делаете, а также очень гибкий, когда вы ожидаете или утверждаете что-то.

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

1. Не используйте статику

Просто не надо.

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

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

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

Если у вас нет вариантов, не забывайте всегда сбрасывать исходное состояние объекта, когда это возможно.

2. Сократите свой синтаксис

Несколько лет назад в Mockery были реализованы методы expects() и allows(), которые могут удалить некоторые бесполезные строки из кода тестирования.

По сравнению с устаревшим синтаксисом новый современный синтаксис может сэкономить вам несколько нажатий клавиш и сделать его более понятным.

3. Используйте частичные макеты, когда это возможно

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

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

Это может сэкономить вам много строк, которые имеют дело с бесполезными ожиданиями только ради того, чтобы объект можно было использовать. Действительно, иногда в моих проектах больше частичных объектов, чем полностью имитируемых.

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

4. Проверка аргументов

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

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

С другой стороны забора withAnyArgs() просто проверяет, получает ли он хотя бы один аргумент, каким бы он ни был.

5. Типы аргументов

Иногда вы просто хотите, чтобы метод получил тип аргумента, независимо от содержимого. Это удобно, когда метод может получить что угодно, например непредсказуемое или случайное значение, или когда он получает тип объединения (более двух типов), и вы хотите только проверить, какой именно.

В этом помогает метод Mockery::type(). Когда фиктивный метод получает аргумент, он проверяет, совпадает ли тип этого аргумента с типом, который вы ожидаете. Он принимает примитивы, такие как array или int, для имен классов.

6. Объединение методов в одну строку

Одной из проблем, с которыми я столкнулся при имитации объекта, была цепочка методов. Объект был вызван несколькими методами, такими как:

Любой респектабельный разработчик издевался над объектом для всех методов и говорил, что он всегда должен возвращать сам себя. Это будет расти только так же, как количество методов, так что это может стать довольно грязным, когда у вас есть десятки.

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

7. Способы заказа

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

В этих случаях метод ordered() заставит ваши ожидания выполняться в том порядке, в котором они установлены.

8. Публичные объекты могут (иногда) быть вашими друзьями

Давайте составим сценарий. Метод объекта под названием «процесс» получает значение, преобразует его, а затем помещает результат в виде общедоступного свойства под названием «сумма».

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

В этих случаях вам могут помочь методы set() и andSet().

Они оба могут быть связаны с ожиданием и должным образом установить значение общедоступного свойства, к которому смогут получить доступ другие объекты. Второй позволяет изменить то, что установлено в зависимости от количества выполнений.

Я полностью ожидаю, что это будет более широко использоваться, поскольку свойства readonly начинают распространяться в современном коде, поскольку они уже поддерживаются в PHP 8.1.

9. Шпионы — ваши друзья

Вместо того, чтобы просто делать полный или частичный макет, вы можете использовать шпиона. Шпион — это способ установить, вызывались или не вызывались некоторые методы после использования объекта.

Отслеживаемый объект будет выполняться нормально, но Mockery подсчитает все вызванные методы, так что вы сможете утверждать их постфактум.

Это в основном полезно, когда объект, который вы хотите шпионить, не причиняет вреда, если он активен.

10. Насмешливые магические методы

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

Установка ожиданий для волшебного метода будет ожидать его в любом случае.

Например, когда этот объект получает setFoo('bar'), который является несуществующим методом, свойство foo устанавливается как 'bar'.

Бонус: возврат одних и тех же аргументов

Когда вызываются ожидаемые методы, у вас также есть возможность установить, что они возвращают. К счастью, Mockery позволяет дополнительно изменять то, что он возвращает, на основе аргументов, которые он получает, используя andReturnArg() и andReturnUsing().

Первый возвращает аргумент дословно на основе индекса аргумента, а второй принимает обратный вызов для дальнейшего изменения конечного результата.

Вы можете делать все, что вам нужно, чтобы вернуть что-то последовательное. Вы также можете поставить их в очередь, добавив несколько обратных вызовов, что аналогично andReturn().

У вас есть еще один совет по использованию Mockery? Вы можете проверить Документацию по Mockery для получения дополнительных советов и узнать, как установить Mockery в свой проект в официальном репозитории.