Запускаем тесты параллельно и стабильно

Предположим, есть ситуация.

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

Что делать?

Изолируйте тест-кейсы друг от друга

Звучит очевидно, но если все тест кейсы будут изолированными друг от друна, падений типа «тест “Б” падает, только если до этого запускался тест “А”» не будет.

Как это сделать? Избавиться от ситуаций, когда данные созданные в одном тест-кейсе используются в другом тест-кейсе. Например.

  • Тест-кейс А тестирует приложение, заполняющее кеш. Тест-кейс Б использует закешированные ранее данные и падает.
  • Тест-кейс А тестирует приложение, пишущее на диск в используемую тест-кейсом Б директорию. Б видит неожиданные данные и падает.
  • Тест-кейс А тестирует приложение, выполняющее TRUNCATE в таблице user. Тест-кейс Б ожидает что в user залиты фикстуры. Б не видит нужных данных и падает.
  • Тест-кейс А тестирует приложение, отправляющее HTTP-запрос к облачному сервису. Тест-кейс Б отправляет запрос на удаление данных из облака. Тест-кейс В отправляет такой же запрос как и А. Последовательность А -> Б -> В проходит, а А -> В падает с конфликтом.
  • Тест-кейс А тестирует приложение и проходит только благодаря сайд-эффектам: например, потому что MySQL отдает приложению данные в каком-то определенном порядке без явного ORDER BY. Тест-кейс Б изменяет поведение сайд-эффекта (например, отправляет запрос к этому MySQL, после чего результат для А начинаеи возвращаться в другом порядке). Последовательность А -> Б проходит всегда, а Б -> А падает.

Рецепт успеха

  • Никогда не используйте данные сгенерированные из тест-кейса А в тест-кейсе Б. Делайте честный setup и teardown.
  • Изолируйте кеши приложения между тест-кейсами: как встроенные в само приложение, так и внешние.
  • Исправляйте код приложения, завязанный на сайд-эффекты и undefined behaviour. Когда это невозможно (например, вы выводите в json неупорядоченные списки), используйте в тестах так же неупорядоченные сравнения.

Изолируйте тест-процессы друг от друга

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

Например.

  • Тест-кейс А тестирует форму логина, а Тест-кейс Б форму удаления пользователя. При последовательном запуске кейсы А и Б приходят. При параллельном запуске – падают.
  • Тест-кейс А записывает в /tmp на диск файл users.txt. Тест-кейс Б пишет туда-же. При последовательном запуске тесты проходят. При параллельном запуске тест-кейсы падают.
  • Тест-кейс А пишет событие в Кафку и читает из нее, тест-кейс Б тестирует чтение пустой очереди. При последовательном запуске тесты проходят. При параллельном запуске тест-кейсы падают.

Самое неприятное всех ситуаций , что будут ли А и Б запущенны одновременно зависит от стечения множества событий, например, такое может случиться раз в 10000 запусков всех тестов проекта. Но в этот редкий момент будет падение.

Рецепт успеха

  • Изолируйте базы данных (а точнее, всё глобальное состояние) каждого тестирующего процесса: MySQL, Postgres, Redis и Memcache, Rabbitmq, Kafka, даже tmp-директория на диске – для каждого процесса с тестами эти сервисы должны быть свои.
  • Пишите в отчет по тестам не только название кейса, OK/Failed, трейсбек, но и PID процесса, который запускал тест-кейс. В тот момент, когда тест по какой-то причине упадет, вы сможете воспроизвести падение запустив только те тесты, которые запускал этот процесс, обязательно в том же порядке. Это помогает расследовать ситуации, когда тест А как-то влияет на тест Б, но эта связь не явная, и Б падает только после прохождения А.

Leave a Reply

Your email address will not be published.