Содержание
Причины нестабильности тестов PHPUnit (Flaky tests)
Иногда PHPUnit тесты выполняются не с первого раза. Работают через раз. Или, например, каждый 10-й запуск фейлится. Является называется Flaky tests.
Запуск одного Flaky test
Вот однострочник, который запускает один тест InjectOfficeTest::testOfficeNameRouteToOffice
в бесконечном режиме до тех пор, пока он не упадет. Также выводит количество раз запуска и потраченное время.
# Предварительно настроить alias, если его нет: # alias atf='php artisan test --filter' start=$(date +%s); count=0; while atf InjectOfficeTest::testOfficeNameRouteToOffice; do ((count++)); done; echo "Executed $count times in $(( $(date +%s) - start )) seconds"
Кейсы, которые у меня были
1. Нарушение уникальности в БД.
Лечение: добавить unique()
:
$this->faker->unique()->word();
2. webp
$name = sprintf('%s.%s', $this->faker->uuid(), $this->faker->fileExtension()); $file = UploadedFile::fake()->image($name); // тут ошибка
Иногда фейлился из-за отсутствия imagewebp в образе PHP. Причем в workspace это расширение стоит.
Лечение:
$name = sprintf('%s.jpg', $this->faker->uuid());
3. tearDown
Был кейс, когда идет уборка в tearDown
. Но из-за какой-то ошибки необходимая уборка не вызывалась. Поэтому после первого раза переставало работать.
4. Storage::fake()
Storage::fake()
прибирается не в конце теста, а наоборот в начале. Это может повлиять на соседний тест или повторный запуск.
5. dataProvider
PHPUnit перед тем как реально запускать тесты, обходит все тестовые файлы, а в них - запускает все dataProvider, чтобы составить карту запуска. Это первый проход. На основе его как раз и работает --filter
.
У меня был кейс, когда dataProvider менял состояние приложения, и поэтому работало через раз.
6. register_shutdown_function, destructor() контроллера
register_shutdown_function()
запускается, когда свою работу завершает не только сам тест, но и PHPUnit.
Деструктор контроллера также запускается слишком поздно.
Если в них разместить теструемую логику, тест вообще никогда не пройдет.
Решение:
public function __construct() { CacheClearJob::dispatch()->afterResponse(); }
7. Небольшая разница при сравнении времени
Возникает при сравнении дат $this->assertEquals()
, часто равна 1 секунде. Объясняется, что время движется.
Решение - зафиксировать время:
Carbon::setTestNow(now()->timezone('Europe/Moscow')->floorSecond()); Carbon::setTestNow(now());
Пишите еще кейсы, с которыми сталкивались.