Я перенес свои сообщения в собственный блог, потому что Medium становится все менее и менее удобным для читателей (платный доступ, невозможность выделить код и т. Д.). Чтобы прочитать эту статью в более приятном и дружественном контексте, прочтите ее в моем личном блоге и подписывайтесь на меня в Twitter, чтобы получать уведомления!

Https://titouangalopin.com/tips-for-a-reliable-and-fast-test-suite-with-symfony-and-doctrine/

На мой взгляд, одна из величайших особенностей Symfony - это внутренняя организация вокруг HTTP: его ядро ​​обрабатывает HTTP-запросы и возвращает HTTP-ответы (см. Документацию по этому поводу). Этот шаблон является одним из основных принципов фреймворка, и он дает массу преимуществ.

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

Чрезвычайно важно иметь надежные и быстрые автоматизированные тесты:

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

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

Надежность

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

Создание надежного набора тестов состоит в основном в возможности сбрасывать состояние приложения в исходное состояние после каждого теста. Обычно состояние нашего приложения находится в двух основных местах: база данных и файловая система.

Сброс базы данных после каждого теста

Во многих своих проектах я использую два пакета Doctrine, посвященных тестам базы данных:

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

Использование Flysystem для абстрагирования файловой системы

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

Мне лично нравится Flysystem: он очень хорошо продуман и прост в использовании. В нескольких строках конфигурации вы можете использовать хранилище памяти в своих тестах, которое будет работать намного быстрее и не будет сохранять состояние между тестами:

composer require league/flysystem
composer require --dev league/flysystem-memory
# config/services.yaml
services:
    League\Flysystem\AdapterInterface:
        class: League\Flysystem\Adapter\Local
        arguments: ['%kernel.project_dir%/storage']

    League\Flysystem\FilesystemInterface:
        class: League\Flysystem\Filesystem
# config/services_test.yaml
services:
    League\Flysystem\AdapterInterface:
        class: League\Flysystem\Memory\MemoryAdapter
# In a controller
public function index(FilesystemInterface $filesystem)
{
    $filesystem->put('foo.txt', 'bar');
    // ...
}

Производительность

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

Максимальное использование модульных тестов

Это довольно очевидный способ повысить производительность тестов, но о нем часто забывают: использование модульных тестов намного быстрее, чем использование функциональных тестов.

Я обычно рассматриваю функциональные тесты как интеграционные тесты: тесты, обеспечивающие хорошее согласование всех компонентов вашего приложения.

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

Когда я пишу тесты, я обычно использую один или несколько функциональных тестов для наиболее распространенных случаев выполнения и модульные тесты для остальных. Реальным примером этого является то, как я тестирую безопасность: вместо тестирования всех возможных ролей в каждом возможном случае с использованием функциональных тестов, которые были бы очень медленными, я предпочитаю проверить своего избирателя безопасности с помощью модульного теста, а затем протестировать наиболее частые случаи использования этого избирателя с помощью функционального теста. Если избиратель правильно вызывается на каждой странице (что обеспечивается функциональным тестом), и если каждый случай роли / контекста, обрабатываемый избирателем, проверяется модульным тестом, я знаю, что мои проверки безопасности безопасны, а мой набор тестов все еще быстро. Я напишу об этом статью в ближайшие недели, следите за обновлениями :)!

Использование более простого кодировщика безопасности в тестовой среде

Кодировщики безопасности описывают, как записывать и проверять пароли пользователей из базы данных. В производстве вы должны использовать bcrypt:

# config/packages/security.yaml
security:
    encoders:
        App\Entity\User: bcrypt

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

# config/packages/test/security.yaml
security:
    encoders:
        App\Entity\User:
            algorithm: md5
            encode_as_base64: false
            iterations: 0

Использование профилировщика для анализа производительности ваших тестов

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

Я регулярно использую два основных инструмента для анализа и повышения производительности моих тестов:

  • PHPUnit Pretty Printer, небольшая библиотека, предоставляющая принтер PHPUnit для отображения времени выполнения каждого теста.
  • Blackfire, замечательный инструмент для профилирования, позволяющий находить точные вызовы функций и пути кода, которые отнимают больше всего времени. Я обычно использую его в сочетании с PHPUnit Pretty Printer, в конкретном тесте, который, как я обнаружил, был медленным:
blackfire run phpunit --filter MySlowTest

Есть ли у вас дополнительные идеи / советы, которые, по вашему мнению, следует добавить в статью? Не бойтесь предлагать их в комментариях!