
Нестабильный тест то проходит, то падает на одном и том же коде. Между двумя прогонами не изменилось ничего, кроме времени, порядка или чего-то за пределами теста, о чём никто не думал.
Это хуже, чем тест, который падает всегда, и это не оборот речи. Тест, который падает всегда, чинят до обеда. Тест, который падает раз в десять прогонов, приучает всю команду нажимать кнопку перезапуска, а как только эта привычка появилась, каждому настоящему падению приходится бороться за чьё-то внимание с шумом.
Во что обходится нестабильность
Она приучает людей не замечать красное. В первый раз, когда сборка падает, кто-то идёт разбираться. На двадцатую ложную тревогу реакция - перезапуск и пожатие плечами, и настоящая регрессия получит то же самое пожатие плечами.
Она прячется в толпе. Набор с дюжиной ненадёжных тестов падает достаточно часто, чтобы никто не мог отличить ненадёжное падение от настоящего, не открыв его, - а значит, никто его не открывает.
Она разом делает набор и медленнее, и менее заслуживающим доверия. Каждый перезапуск стоит минут; сомнение стоит дороже.
Откуда берётся нестабильность
Почти всё это - одно из пяти.
Ожидание не того. С большим отрывом самый частый случай в браузерных тестах. Тест спрашивает, виден ли элемент, пока тот ещё появляется, или проверяет состояние, до которого интерфейс доходит долей секунды позже. Он проходит на быстрой машине и падает на загруженном сборочном сервере, то есть ровно на той машине, где нельзя отладиться.
Зависимость от порядка. Тест, который проходит только после другого, потому что тот создал запись, выставил состояние или что-то после себя оставил. Запустите набор в другом порядке или параллельно - и он рассыпается.
Общее состояние. Строка в базе, значение в кэше, файл на диске, часы. Два теста, пользующиеся одной фикстурой, рано или поздно окажутся достаточно близко друг к другу, чтобы помешать один другому.
Время. Всё, что проверяет сегодняшнюю дату, когда-нибудь перейдёт полночь; всё, у чего есть таймаут, падает под нагрузкой; всё, что зависит от порядка двух событий без гарантии упорядоченности, - это подброшенная монета, которую вы пока не заметили.
Внешний мир. Тест, который ходит в настоящую сеть, к настоящему стороннему сервису или к настоящим часам, взял взаймы чужую доступность.
Ошибка, которая прячет большинство из них
Общее в случаях с таймингом стоит назвать отдельно, потому что это меняет то, как вы пишете проверку.
- Ждать перехода
- Проверять, что кнопка показывает надпись в состоянии покоя, пока анимация, убирающая предыдущее состояние, ещё идёт
- Ждать состояния
- Проверять, что класс "copied" исчез, и только потом - что надпись в состоянии покоя на месте
Первое - это гонка между терпением тестового фреймворка и таймером интерфейса. Она проходит на спокойной машине и падает на занятой, а сообщение об ошибке скорее сбивает с толку, чем объясняет: элемент на месте, с правильным текстом, и просто ещё не виден.
Этот пример настоящий: это тест в этой же кодовой базе, и он упал ровно один раз, на загруженном раннере, на коммите, который менял два файла с картинками.
Что с ним делать
Не чините его ожиданием подольше. Поднять глобальный таймаут - значит замедлить путь до падения всем остальным тестам и спрятать следующую гонку, а не убрать её. Это то же самое, что сделать музыку погромче.
Но и не удаляйте его, по крайней мере не первым делом. Нестабильный тест обычно указывает на что-то настоящее: на реальную гонку в продукте, на интерфейс, который сообщает о завершении раньше, чем завершил, на общий ресурс, которым пользуются двое. Починить тест иногда означает починить приложение.
Карантин, потом починка, со сроком. Уберите его из блокирующего набора, чтобы он перестал приучать людей не замечать красное, и поставьте дату. Карантин без срока - это удаление с лишними шагами и с более неспокойной совестью.
Считайте их. Команда, которая не может сказать, сколько у неё нестабильных тестов, узнает это, когда число станет большим. Если ваш раннер записывает перезапуски, следить надо именно за этим числом.
Когда нестабильность - вина продукта
Иногда тест прав, а ненадёжна программа: запрос, который время от времени приходит не по порядку, интерфейс, который говорит «сохранено» до того, как сохранение закончилось, задача, которая обычно успевает до перезагрузки страницы.
Это настоящие дефекты, и заводить их мучительно, потому что по определению они случаются не каждый раз. Воспроизводимым для другого человека их делает контекст того прогона, который упал, а не описание десяти, которые прошли.
Session Replay
Бесплатное расширение для Chrome. Один клик на странице, которая ведёт себя не так, захватывает скриншот, консоль и сетевой журнал и отдаёт вам ссылку, которую можно вставить в задачу.
Для плавающего дефекта скажите, как часто он случается и что вы делали, когда он случился: «три раза примерно из двадцати попыток, всегда сразу после сохранения» куда полезнее описания одного случая. Остальное разбирает руководство по баг-репорту.
Короткая версия
Нестабильный тест падает на неизменившемся коде, и его настоящая цена в том, что он приучает людей не замечать падения. Большинство из них - это тест, который ждёт перехода вместо состояния, зависимость от порядка или общее состояние. Чините причину, а не таймаут, отправляйте в карантин со сроком, а не бессрочно, и всерьёз допускайте, что тест прав, а ненадёжна программа.