Как я могу проверить, что тестируемый PHP-код вызывает правильный метод для признака?

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

Обычно я использую внедрение зависимостей (DI), но хочу улучшить работу с типажами, поскольку кажется, что они могут выполнять аналогичную роль, при этом используя композицию вместо наследования. Проблема, с которой я столкнулся, заключается в том, что я не могу найти лучший способ убедиться, что тестируемый код (CUT) работает должным образом при использовании признака.

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

Например, давайте представим, что у нас есть черта:

trait CanLog 
{
    protected function log($level, $message)
    {
        // implementation goes here
    }
}

class ClassThatWillLog
{
    use CanLog;

    public function foobar()
    {
        // do some stuff
        $this->log("INFO", "I finished successfully!");
    }
}

Я хочу утверждать, что журнал содержит какое-то специально отформатированное сообщение уровня INFO, в котором также есть надпись «Я успешно завершил». Проблема в том, что мне нужно по сути превратить это в интеграционный тест (и проверить место регистрации фактической реализации CanLog), потому что в настоящее время я не могу заменить метод, с помощью которого регистрируется особенность CanLog... Если это имеет смысл.

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

class FakeLogger implements LoggerInterface 
{
    private array $log = [];

    protected function log($level, $message)
    {
        $this->log[] = "$level: $message\n";
    }

    public function getLogMessages(): array
    {
        return $this->log;
    }
}

class ClassThatWillLog
{
    private LoggerInterface $logger;

    public function __construct(LoggerInterface $logger)
    {
        $this->logger = $logger;
    }

    public function foobar()
    {
        // do some stuff
        $this->logger->log("INFO", "I finished successfully!");
    }
}

Я мог бы создать в CanLog то же самое, что и в FakeLogger, но это кажется неправильным и добавляет код, который совершенно не нужен для производства. Я просто не уверен, как воспроизвести эквивалент DI, используя вместо этого черту.

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

u_mulder 11.07.2024 22:07

@u_mulder Моя мысль заключалась в том, что мне не следует тестировать функциональность признака CanLog, который должен быть независимо протестирован в отдельном тесте функций. Мне просто нужно убедиться, что метод вызывается правильно, потому что тогда я могу предположить, что он работает правильно, поскольку признак уже должен передавать зеленый цвет в своих собственных тестах.

jrgilman 11.07.2024 22:26

Вам не следует проверять, использовал ли программист композицию или наследование, вам следует проверять, была ли создана запись в журнале. Если вы полностью удалите этот признак и замените вызов $this->log() локально определенным жестко закодированным блоком кода, который генерирует запись журнала, тест все равно должен пройти.

Alex Howansky 11.07.2024 22:35

Перечитывая вопрос, да и некоторые другие мои тесты, думаю, я неправильно описал свою проблему - совсем запутался. Вместо того, чтобы утверждать, что вызов был выполнен, как ожидалось (а это не то, чего я хочу, поскольку макеты делают это, и это приводит к нестабильным тестам), мне нужен способ иметь поддельный журнал, который я могу впоследствии проверить, содержит ли он сообщение, которое я ожидаю. Как ты и сказал. Проблема в том, что я не хочу проверять реальный журнал, который может находиться в AWS Cloudwatch, базе данных или каком-либо другом месте, что могло бы сделать это интеграционным тестом.

jrgilman 11.07.2024 22:50

Вы уже описали этот метод — создайте тестовый дубль, реализующий тот же интерфейс. Проблема здесь в том, что вы отказались от механизма (т. е. DI), который идеально подходит для этой цели. Если бы у вас был LoggerInterface, реализованный с помощью конкреций AwsLogger и TestLogger, то вы могли бы просто внедрить первый в продукте, а второй — в тестировании. Или просто используйте Monolog, у которого уже есть хороший TestHandler, который делает именно то, что вы описываете.

Alex Howansky 11.07.2024 23:06

@AlexHowansky Я только что отредактировал вопрос, и понял, что написал вопрос совершенно неправильно. Спасибо, что помогли мне осознать это. Я согласен, что DI идеально подходит для этого. Думаю, тогда у меня должен возникнуть вопрос: когда мне следует использовать черту вместо DI? Кажется, он находится в этом странном месте между DI и наследованием.

jrgilman 11.07.2024 23:10

Я использую черты в случаях, когда у меня есть простые, общие, общедоступные функции, которые никогда не будут охватываться шаблоном стратегии. Я думаю, что хорошей лакмусовой бумажкой является любое место, где вы испытываете желание скопировать/вставить. Например, они особенно хороши для общих полей в определениях моделей ORM, которые используют докблоки/атрибуты, как в Laravel или Symfony. У меня есть проект, в котором почти каждая модель имеет поля lastUpdatedBy и lastUpdatedAt, поэтому я просто помещаю эти свойства и их мутаторы в признак, и тогда мне не нужно дублировать их в каждой модели.

Alex Howansky 11.07.2024 23:26
Стоит ли изучать PHP в 2023-2024 годах?
Стоит ли изучать PHP в 2023-2024 годах?
Привет всем, сегодня я хочу высказать свои соображения по поводу вопроса, который я уже много раз получал в своем сообществе: "Стоит ли изучать PHP в...
Symfony Station Communiqué - 7 июля 2023 г
Symfony Station Communiqué - 7 июля 2023 г
Это коммюнике первоначально появилось на Symfony Station .
Оживление вашего приложения Laravel: Понимание режима обслуживания
Оживление вашего приложения Laravel: Понимание режима обслуживания
Здравствуйте, разработчики! В сегодняшней статье мы рассмотрим важный аспект управления приложениями, который часто упускается из виду в суете...
Установка и настройка Nginx и PHP на Ubuntu-сервере
Установка и настройка Nginx и PHP на Ubuntu-сервере
В этот раз я сделаю руководство по установке и настройке nginx и php на Ubuntu OS.
Коллекции в Laravel более простым способом
Коллекции в Laravel более простым способом
Привет, читатели, сегодня мы узнаем о коллекциях. В Laravel коллекции - это способ манипулировать массивами и играть с массивами данных. Благодаря...
Как установить PHP на Mac
Как установить PHP на Mac
PHP - это популярный язык программирования, который используется для разработки веб-приложений. Если вы используете Mac и хотите разрабатывать...
1
7
57
1
Перейти к ответу Данный вопрос помечен как решенный

Ответы 1

Ответ принят как подходящий

Особенности — это «копирование и вставка с помощью компилятора» — после компиляции класса (да, в PHP есть этап компиляции, пусть никто не говорит вам иначе), код ClassThatWillLog содержит метод log непосредственно в теле. Тот факт, что это произошло от черты, совершенно незаметен.

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

Это делает черты плохим выбором для такого рода зависимости; использовать интерфейс и инъекцию проще всего.

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

Другие вопросы по теме