С самого начала работы над одним из web-проектов мы стремились к высокому уровню покрытия кода тестами, и на начальном этапе разработки я не задумывался об оптимизациях скорости их выполнения. Как результат, с ростом проекта, всё большим покрытием его тестами и ростом команды время выполнения тестов выросло с нескольких секунд до десятков минут. А наличие быстрых тестов может быть также важно как и производительность всего приложения. Как я с этим боролся и что получилось в итоге? Приложение разрабатывается на PHP 7.2 и Symfony 4.2. Для написания тестов пользуемся PHPUnit. Большая часть тестов представляет собой интеграционные тесты для REST API. Запускаем тесты: На все тесты ушло около 20 минут. Это приводило к следующим проблемам:  Начинаем искать основные узкие места и экспериментировать! Эта библиотека позволяет изолировать работу с базой данных для каждого теста, в результате чего нам нужно подготовить тестовые данные и схему БД только один раз перед запуском тестов. С помощью специального PHPUnit Listener перед запуском каждого теста открывается новая транзакция и откатывается после завершения теста. Также библиотека предоставляет средство кэширования метаданных и запросов для всех EntityManager. Пример конфигурации: Мы уже использовали эту библиотеку, но если у вас её нет, то обязательно попробуйте. У фреймворка Symfony прекрасная документация, в которой в частности есть раздел про производительность. Советую ознакомиться с ним и проверить вашу конфигурацию. Если вы уже используете PHP 7.4 и последние версии Symfony, то обязательно попробуйте предварительную загрузку классов. У нас уже были выставлены все рекомендуемые параметры OPcache и PHP, поэтому возникла идея обновиться до PHP версии 7.4 и Symfony 4.4, проверив будет ли прирост производительности в тестах, тем более это не должно было занять много времени. Заодно обновим PHPUnit до текущей последней версии — 9.1.5. Перед обновлением ознакомимся с документацией по переходу на новые минорные версии Symfony: Обновляем зависимости, исправляем проблемные места и начинаем настраивать OPcache и предварительную загрузку классов. Вносим изменения в php.ini для dev, test и prod окружений: И services.yaml: Запускаем наши тесты и... Ускорились на 3 минуты, но нам этого мало. Для сборки и запуска приложения используется Docker с multistage сборкой. Для локальной отладки кода в dev сборку подключается PHP-расширение XDebug, которое также влияет на скорость выполнения кода. Во время прогона всех тестов отладка кода нам не нужна, поэтому для этого случая отключаем расширение с помощью небольшого скрипта (спасибо stackoverlow): Проверяем: Отлично! Выиграли дополнительные 3 минуты. Я уже упоминал, что большая часть тестов — интеграционные, которые начинаются с создания клиента: Посмотрим конфигурацию Symfony: Encoder описывает каким образом будут проверяться и храниться пользовательские пароли.
А теперь получим полную актуальную конфигурацию с учётом стандартных параметров: Для тестов нам абсолютно не нужна такая криптоустойчивость, попробуем использовать md5 без каких-либо итераций: Проверяем: Hoooraaay! Давайте посмотрим сможем ли мы побежать еще быстрее... В Doctrine ORM есть возможность логировать каждый выполняемый SQL запрос: Явно отключаем логирование для тестовой среды и изменяем уровень для остальных логов с debug на critical: Как результат, получаем ускорение в 20-30 секунд:  Все тесты запускаются последовательно один за другим и я решил посмотреть возможность запускать тесты параллельно. Для PHPUnit есть библиотека ParaTest, которая как раз делает то, что мне нужно. Устанавливаем через composer, дополнительной конфигурации не требуется, поэтому запускаем и смотрим: К сожалению, большого прироста скорости здесь не получили. Возможно, это связано с самими тестами и, если у вас много unit-тестов, то результаты будут лучше. Используя все эти оптимизации, играясь с настройками и библиотеками я достиг оптимального времени выполнения всех тестов, снизив его с 20 до 2 минут. Если у вас есть советы как ещё можно ускориться, буду рад их услышать и опробовать. Разработчик инфраструктуры