Skip to content
...
Блог
Что быстрее

Что быстрее

С самой бета-версии Testo меня спрашивали, сравнивал ли я его по скорости с PHPUnit. Я отвечал, что нет, и обычно добавлял: скорее всего, чуть медленнее. В Testo на каждую фичу строится пайплайн с мидлварями, а за гибкость и удобство принято платить производительностью.

Руки до замеров не доходили. А тут выдался повод.

Повод

На днях Brent опубликовал твит, в котором его Tempest Testing десятикратно уделывает PHPUnit: примерно 3 секунды против 30.

Загружаем твит @brendt_gdОба прогона рядом, в одном видео
Открыть в X ↗

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

Первое, что приходит в голову — распараллелил. Но нет: в твите написано, что всё запускается в один поток.

Тогда файберы? Tempest уже асинхронный? Это можно проверить: кодовая база открыта — brendt/stitcher.io, ветка testing.

А раз есть проект, где два раннера уже стоят рядом на одинаковых тестах, самое время добавить третий и посмотреть, какое место Testo займёт между асинхронным Tempest и последовательным PHPUnit.

Стенд

PHPUnit я вернул в проект из основной ветки — в отдельную папку рядом с тестами Tempest, и сделал ещё одну папку для Testo. Тесты из PHPUnit в Testo перегнал AI-агент: для этого у Testo есть скиллы и правила Rector, которые конвертируют тесты между фреймворками.

Попутно пришлось восстанавливать окружение и отправить фикс в Tempest Testing, потому что проект тупо не запускался.

Инфраструктуру я тоже подкрутил. На MySQL тесты идут очень медленно — две минуты на Tempest, поэтому переключил всё на SQLite. А вместо Redis взял сразу Valkey.

Первые прогоны

  • PHPUnit — 5 минут 22 секунды.
  • Tempest — около 12 секунд.
  • Testo — около 17 секунд.

Отставание PHPUnit выглядело слишком большим, особенно в сравнении с Testo. Пошёл разбираться и кое-что обнаружил:

  1. В PHPUnit на каждый тест пересобирался весь Kernel фреймворка. В Tempest вместо этого сбрасывалось состояние уже собранного. Одна эта правка сократила время с 5 минут до 30 секунд.
  2. Атрибут #[Before] висел на setUp(), который и так работает как Before. Смешно, но, похоже, PHPUnit в таком случае дёргает setUp() дважды. Минус ещё четверть времени.

Файберов в Tempest я, кстати, так и не нашёл. Весь его буст объяснялся более удачной обвязкой вокруг тестов, а не асинхронностью.

Результаты

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

раннерпроцессовкруг 1круг 2среднее
tempest124.5с24.3с24.4с
tempest5 (дефолт)12.5с12.3с12.4с
testo121.1с21.2с21.1с
phpunit122.5с22.6с22.5с

Честно говоря, я удивлён, что Testo в одном потоке оказался быстрее всех. Tempest в дефолтных пяти процессах ожидаемо ускоряется почти вдвое относительно своего однопоточного прогона. Параллельный запуск появится и в Testo: он точно входит в план на 1.0.

Куда интереснее другое число. По метрикам Testo выполнение самих тестовых функций заняло меньше секунды на весь прогон. Вычитаем 1 секунду на сам тестовый фреймворк и получается, что остальные двадцать с лишним секунд — это обвязка: поднять приложение, накатить миграции, сбросить состояние между тестами...

Совет

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