Вы можете тестировать быстрее

Переключение контекста между задачами

Исследования показывают: чтобы попасть в поток, требуется до 30 минут, и мы не можем просто переключиться с одной задачи на другую. Нужна полная смена режима, а постоянное переключение контекста мешает мозгу полностью заниматься текущей задачей (Источник). Профессор информатики Калифорнийского университета Глория Маркс провела исследование и выяснила, что для повторного погружения в задачу после перерыва требуется в среднем 23 минуты 15 секунд и большинство людей решит две промежуточные задачи, прежде чем вернуться к первоначальному проекту. Такое переключение ведёт к накоплению стресса, говорит Глория, и потому мало удивляет, что люди, имеющие высокий уровень невротизма, импульсивности и восприимчивости к стрессу, стремятся переключаться с задачи на задачу, как правило, больше, чем другие.

Тезис первый: стоимость переключений контекста между задачами не равна нулю.

В одном блоге (medium.com/@allo) нашёл понятное объяснение переключения контекста:

Одним из самых сильных факторов, негативно влияющих на производительность труда, является переключение контекста. Что это такое? Это когда вы занимались чем-то одним, а потом переключились на что-то другое. Мозгу нужно время “загрузить” данные новой задачи, освоиться с ними, вспомнить принципы и правила. По исследованиям, на переключение с одной задачи на другую требуется не менее 15 минут.

и аналогию с физическим трудом:

Чтобы переключиться с копания ямы на покраску забора, тоже нужно время: почистить и сложить инструменты для копания, огородить яму, чтобы в неё никто не упал, достать краску и кисти, подготовить стремянку и так далее. Поэтому в труде физическом мы вряд ли будем каждые 10 минут переключаться с ямы на забор и обратно.

А сколько времени тратится?

Чтобы не быть голословным о величине времени обратной связи я приведу конкретные примеры из проектов.

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

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

Коммерческий проект Oracle Database: пользователи Hacker News решили (перевод и обсуждение на Хабре) обсудить вопрос «Каков максимальный объем плохого — но при этом работающего — кода вам доводилось видеть?». Комментаторы рассказали немало «весёлых» историй о популярных программных проектах; но больше всего внимания привлек рассказ про код «передовой СУБД, которую используют большинство компаний, входящих в список Fortune 100». Вот, что пишет комментатор про Oracle Database: тестовая ферма состоит из 100-200 серверов, которые занимаются сборкой и тестированием БД, тестирование каждого изменения занимает от 20 до 30 часов. В силу того, что на сборку СУБД и выполнение тестов уходит не менее суток, ожидается, что каждый разработчик работает одновременно над 2-3 багами и переключается между ними, пока ждет результатов тестирования.

Разработка ядра Linux: сложно оценить сколько занимает полный цикл тестирования, потому что процесс тестирования был сильно фрагментирован и выполнялся в разных компаниях. Пару лет назад появился проект KernelCI для непрерывного тестирования изменений в основной ветке ядра.

Коммерческий проект 1C: разработчики в компании 1C ограничивают время тестирования 2 часами: “Конечная цель этой работ - обнаружение ошибки тестами (если её можно обнаружить тестами) в течение не более двух часов после коммита, чтобы найденная ошибка была исправлена до конца рабочего дня. Такое время реакции резко повышает эффективность: во-первых, самому разработчику не нужно восстанавливать контекст, с которым он работал во время привнесения ошибки, во-вторых, меньше вероятность, что ошибка заблокирует чью-нибудь ещё работу.”

Коммерческий сервис CircleCI: “Набор тестов CircleCI, вероятно, является одним из самых крупных в мире среди проектов, написанных на Clojure, на сегодняшний день. Наш серверный код - это 100% Clojure, включая набор тестов, которые в настоящее время состоят из 14 тысяч строк в 140 файлах. Без распараллеливания запуск занимает 40 минут.”

Не уверен, что приведенные проекты могут составлять репрезентативную выборку для всей индустрии, но думаю это как минимум доказывает существование проблемы в некоторой части индустрии.

Большое время на ожидание обратной связи настолько для нас привычно, что стало темой для комикса xkcd:

Почему это важно и какие последствия

Рзаработчики как правило счастливее, когда чувствуют себя продуктивными. Время сборки прерывает “поток” и заставляет чувствовать себя застрявшими с задачей, непродуктивными. Вряд ли кто-то рад этому.

Может в индустрии есть рекомендации насчёт времени тестирования? В отчёте “State of DevOps 2018” утверждается, что тесты должны проходить не больше, чем за 10 минут, а если сборка и тестирование занимают больше 10 минут, то вы должны повысить эффективность ваших тестов, добавить больше вычислительных ресурсов, чтобы запускать их параллельно, или выделить долгие тесты в отдельную задачу в CI пайплайне.

Можно ли избежать длительного ожидания?

Если отвечать коротко, то в большинстве случаев можно, но нет единой рекомендации для всех проектов. Далее я расскажу как сделать цикл обратной связи сделать короче в общем и частных случаях. Если вы давно занимаетесь тестированием, то многие из приёмов вы уже скорее всего знаете. Тогда просто используйте этот список ниже как чеклист и возьмите на вооружение то, что вы посчитали полезным.

Ниже я перечислю разные способы, которые позволят сделать ваши тесты более быстрыми, но сначала поделюсь небольшой историей. В одном из проектов нужно было тестировать SDK. Каждый регресионный тест для SDK использовал его же для сборки для сборки время и сборки каждого теста занимало приблизительно 10 секунд. После одного из циклов автоматического тестирования я заметил, что общее время тестирования увеличилось на ~40 минут. Это было подозрительно, потому что новые тесты не добавляли, тестовые стенды не обновлялись и не было никаких других причин, из-за которых время могло так значительно увеличиться. Время сборки каждого отдельного теста тоже увеличилась. Я решил разобраться с этим и посмотреть сборочные скрипты в SDK. В ходе ревью кода я нашел задержку в 30 секунд, которую добавляли для решения проблемы синхронизации с другими скриптами и забыли убрать при отладке. После удаления этой задержки мы ускорили тестирование на 45 минут! Но, к сожалению, такое везение случается редко и чтобы ускоирить сборку или тестирование нужно потратить время на исследование возможных способов оптимизации.

Я рассмотрю возможные варианты для более быстрого получения фидбека о внесенных изменениях в исходный код. Каждый из вариантов я попробую на реальном проекте, чтобы было видно эффект. Так как я работаю в проекте Tarantool, то все эксперименты я буду делать для этого проекта. Так как Tarantool написан на комбинации языков C и Lua, то и рассматривать я буду именно их, а в конце заметки я привел ссылки на статьи с описанием оптимизаций для других языков.

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

Совет № 1: Игнорирование нерелевантных изменений в непрерывной интеграции

Не секрет, что репозиторий состоит не только из исходного кода, но и из статических файлов, которые никак не участвуют в сборке проекта: это может быть документация, файл README и другие файлы. Запускать тесты в случае изменений только в этих файлах нет смысла. Современные системы непрерывной интеграции позволяют гибко конфигурировать для каких изменений должны запускаться тесты: указать пути и расширения для файлов, изменения в которых не должны быть триггером для запуска всего пайплайна или его части. По времени экономия не будет большой, но такое просто изменение может сэкономить ресурсы и немного уменьшить очередь изменений на тестирование.

Совет № 2: Быстрое разворачивание тестового и сборочного окружения

Для Git есть опция --depth N, которая позволяет создавать клон репозитория без выгрузки всей истории изменений. Для тестирования и сборки в CI обычно история не нужна, поэтому имеет смысл всегда использовать эту опцию при клонировании. В проектах с большим количеством изменений время клонирования с и без истории изменений может отличаться в несколько десятков раз. Например для SQLite клонирование с --depth 1 занимает 6 секунд, а без неё 2 мин 48 сек. Разница в 28 раз! Для проектов, которые используют Git-модули --depth можно использовать так:

git clone --depth 1 https://github.com/tarantool/tarantool
cd tarantool
submodule update --init --recursive --force --depth 1

Git умеет загружать новые изменения в несколько потоков: git config fetch.parallel 0 и git config submodule.fetchJobs 0. Для репозитория Tarantool git clone стал быстрее на 4 секунды.

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

Выключение триггеров при установке пакетов. В CI обычно используется базовый образ операционной системы, который перед началом сборки и тестирования проекта донастраивается: установка пакетов, изменение параметров конфигурации и т.д. При установке пакетов в Ubuntu или Debian могут выполняться различные триггеры, которые не имеют смысла при выполнении в CI. Примеры триггеров, которые занимают время, это обновление локальной базы страниц документации man-db и обновление initramfs:

Processing triggers for man-db (2.9.4-2) ...
Processing triggers for initramfs-tools (0.140) ...

Если вы хотя бы раз устанавливали пакеты в Ubuntu, то замечали, что выполнение триггера для man-db может занимать несколько секунд и чтобы не тратить на это время триггер можно отключить удалением пакета apt remove man-db --purge или выключением автоматического обновления страниц документации rm -f /var/lib/man-db/auto-update. Триггер для обновления initramfs можно отключить так: sudo sed -i 's/yes/no/g' /etc/initramfs-tools/update-initramfs.conf. Полный список триггеров можно посмотреть в документации проекта Debian - https://wiki.debian.org/DpkgTriggers.

Совет № 3: Статический анализ

Статические анализаторы находят неочевидные проблемы, требуют минимум ресурсов и работают стабильнее, чем регрессионные тесты (привет, интеграционные тесты!). Их лучше запускать на самой раней стадии в CI или даже в IDE во время разработки. Некоторые статические анализаторы позволяют проверять только новые изменения, без необходимости перепроверять всю кодовую базу. Статический анализ дешевле других средств поиска проблем в коде, почему бы не использовать его?

Совет № 4: Быстрая сборка проекта

Этот раздел относится к C/C++ проектам. Для других языков может быть своя специфика.

Совет с параллельной компиляцией проекта думаю не нуждается в комментариях. Используйте параллельную сборку, её можно включить с помощью опции -j.

CMake позволяет генерировать правила для сборки не только в формате Make, но и для Ninja. Ninja может дать серьёзное ускорение:

Один из подходов в сокращении времени сборки проекта - это поиск и удаление подключённых неиспользуемых библиотечных файлов. Чем плохо включение неиспользуемых заголовочных файлов? Каждый заголовочный файл .h который присутствует в проекте немного увеличивает общее время сборки проекта, потому что компилятор будет тратить время на чтение этого файла, парсинг и пропроцессинг. Если вы удаляете этот файл, то вы сокращаете временные затраты компилятора. Особенно это проявляется с кодом написанным с использованием шаблонов (привет, плюсы). Найти неиспользуемые заголовочные файлы можно найти с помощью include-what-you-use. У проекта есть более подробное описание зачем этот инструмент нужен - https://github.com/include-what-you-use/include-what-you-use/blob/master/docs/WhyIWYU.md

Для некоторых проектов параллельная компоновка может значительно сократить время сборки. К примеру, линковщик Mold позволяет сократить время сборки в 17 раз по сравнению с GNU gold и в 3-5 раз по сравнению с lld.

Общий кеш для компиляторов позволяет значительно ускорить время сборки проекта. Есть ccache для GCC/Clang в Linux или MacOS и clcache для VisualStudio.

Использование кеша для компилятора особенно полезно когда вы часто делаете сборки с нуля или переключаетесь между разными ветками. Его также можно использовать на сборочном сервере, когда большая часть изменений и веток одинаковы, то есть часто происходит пересборка почти одинаковых версий.

В случае использования ccache достаточно выполнить две команды для начала использования кеша в CMake:

sudo apt install ccache
export PATH=/usr/lib/ccache:$PATH

Теперь при запуске CMake он будет автоматически использовать ccache вместо GCC или Clang. Только убедитесь, что директория для кеша расположена на SSD диске.

ccache может ускорить сборку в 30 (!) раз. ccache может хранить кеш не только локально, но и в популярных облачных хранилищах наподобие Google Cloud Storage или в key-value хранилищах наподобие Redis.

Распределенная компиляция проекта позволяет выполнять параллельную сборку на нескольких компьютерах и ускорить её в 2-4 раза. Из популярных инструментов это distcc, icecream (форк distcc). Я на практике распределенную компиляцию никогда не использовал, поэтому своим опытом поделиться не могу. Но знаю, что с ней есть много тонкостей и подойдёт она не всем проектам. Из ограничений: все машины должны использовать одинаковую версию компилятора.

В опубликованных материалах конференции USENIX 2019 моё внимание привлёк один доклад. Докладчик рассказывал про фреймворк, который позволяет некоторые типы задач выполнять гораздо быстрее, чем локально или в облачных виртуальных машинах. К таким задачам относятся компиляция программ, тестирование, обработка видео и др. Например локальная сборка проекта Inkscape занимает 32 мин на одном ядре, а сборка с помощью фреймворка gg занимает полторы минуты и обходится в 50 центов USD. Такое существенное ускорение получилось за счёт разделения задачи на небольшие подзадачи (в данном случае препроцессинг Си кода и компиляция отдельных файлов) и параллельного (тысячи потоков) выполнения этих подзадач на легковесных контейнерах AWS Lambda. Исходный код gg - https://github.com/stanfordsnr/gg, слайды, статья и видео доклада - “From Laptop to Lambda: Outsourcing Everyday Jobs to Thousands of Transient Functional Containers”.

Похожим с gg образом работает llama - распределяет задачи компиляции и препроцессинга между инстансами AWS Lambda. llama включает в себя llamac, который используется как замена компилятора в проекте. Технически llama работает так:

  • препроцессинг запускается локально, чтобы составить полный список заголовочных файлов, необходимых для каждого исходного файла
  • загружает каждый исходный файл в S3
  • вызывает функцию в Amazon Lambda в специальном окружении, которая:
    • загружает заголовочный файл из S3
    • вызывает компилятор внутри функции
    • загружает объектный файл в S3
  • загружает объектный файл из S3

По цифрам: llama позволяет получить ускорение в 2-3 раза по сравнению с локальной компиляцией.

sccache объединяет в себе одновременно инструмент для кеширования сборки и инструмент для распределённой сборки.

Совет № 5: Последовательное тестирование согласно тестовой пирамиде

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

Совет № 6: Параллельное выполнение тестов

I had a problem, so I decided to use threads. tNwoowp rIo bhlaevmes.

Каждый юнит-тест - независимая программа, им не нужно специальное тестовое окружение для выполнения и параллельный запуск тестов может существенно сократить общее время тестирования. Выше я рассказал, про gg и llama, которые позволяют использовать AWS Lambda для распределенной сборки. И тот и другой инструмент позволяют запускать абстрактные задачи в AWS Lambda. Авторы gg экспериментировали с библиотекой LibVPX и за счёт параллельного выполнения тестов сократили время с 1.5 часов до 4 минут.

Совет № 7: Падай сразу

Сам по себе этот совет не ускоряет выполнение тестов, он меняет подход к запуску тестов. Если хотя бы один из тестов “упал”, то тестирование уже не будет успешным. Поэтому тестирование можно остановить. С одной стороны Тна практике такая стратегия запуска тестов не пользуется популярностью, потому что разработчику интересно получить информацию обо всех тестах. А с другой стороны это прямое следование популярной методологии Agile:

“Failing fast” lies at the heart of agile. Teams want continuous integration (CI) to provide feedback on their latest updates as soon as possible. CI test results are the primary barometer that developers use to determine whether it’s safe to move on to the next development task, or if they inadvertently broke functionality that users have grown to rely on.

Подход будет работать ещё эффективнее вместе с приоритезацией тестов. В таблице можно посмотреть опции для разных тестовых фреймворков, которые поддерживают подход fail-fast.

Совет № 8: Ускорение медленных тестов

Успешный тест всегда занимает меньше времени на выполнение, чем неуспешный. Всё дело в таймаутах и задержках, которые выполняет тест. Проинспектируйте код тестов на предмет операторов, добавляющих безусловные задержки. Оператор sleep() не является методом синхронизации и может быть причиной нестабильного поведения тестов, известных как flaky тесты. В таких случаях установите значения в минимальные. В идеале везде использовать условное ожидание, которое зависит от какого-то внешнего условия. Чтобы избавляться от неоправданно больших таймаутов имеет смысл завести метрику “самый медленный упавший тест” с тем, чтобы отслеживать тесты с самыми долгими таймаутами и по возможности значения этих таймаутов уменьшать.

Полуавтоматизированные тесты - это то, чего нужно избегать. В моей практике были тесты, которые в определённые момент требовали перезагрузки устройства, на котором тест проходил. Моего терпения хватило ровно на то время, пока я делал USB-реле, которое позволило автоматизировать ручной шаг в тестировании.

Если ваши тесты много работают с базами данных, то использование отката транзакции вместо её коммита - в этом случае все изменения, вносимые тестом и тестовой средой, автоматически отменяются. Подход позволит избегать дорогостоящих операций записи на диск и, как следствие, решает проблему со временем очистки, но создает другую проблему: добавление транзакции может привести к несоответствию поведения между рабочей и тестовой средой. Так, конечно же, лучше не делать в интеграционных тестах.

Использование БД в памяти. Такой подход позволяет изолировать интеграционные тесты друг от друга с помощью замены базы данных её аналогом, находящимся полностью в памяти - например SQLite. Такие базы данных не требуют удаления тестовых данных, быстрее работают, могут создаваться перед каждым тестом без увеличения времени на подготовку тестового окружения. Несмотря на все эти преимущества, подход применим не во всех случаях, потому что функциональность БД сильно отличается от традиционных баз данных. То есть возникает проблема несоответствия между рабочей и тестовой средой.

Использование вызова TRUNCATE для очистки таблиц “TRUNCATE TABLE users, pickups, …” вместо последовательного вызова команд “DELETE FROM users; DELETE FROM writers; …” может работать гораздо быстрее.

Чтобы бороться с медленными тестами имеет смысл обращать внимание на самые медленные тесты из всего набора. У pytest есть опция --durations=N, которая как раз покажет такие тесты. Для pytest есть расширение, которое поможет профилировать тесты - pyest-profiling, для Ruby есть test-prof, возможно и для других фреймворков есть что-то похожее. Тайминги для отдельных тестов можно смотреть по тестовым отчётам.

Совет № 9: Ускорение тестового фреймворка

Этот совет появился не на пустом месте. Вообщем-то у кода для тестов могут быть такие же проблемы как и у кода для продукта. Тестовых фреймворк может тоже работать медленно и это нужно иметь ввиду. Случай из жизни: когда мы добавили поддержку Python 3 в тестовый фреймворк для Tarantool, то фреймворк стал медленнее на 25%. Заметили случайно и сразу же исправили.

Иногда тестовый фреймворк тоже нуждается в настройке. К примеру, популярный pytest по умолчанию выполняет поиск тестов в директориях рекурсивно. Это может быть затратно по времени, если у вас глубокая вложенность директорий, и бессмысленно, если тесты лежат только на одном уровне. К счастью такое поведение можно отключить с помощью опции norecursedirs:

[pytest]
norecursedirs = .svn _build tmp*

Совет № 10: Ускорение тестового окружения

Популярный способ ускорить работу с файловой системой - отключение вызова fsync() при операциях с файлами. Для PostgreSQL, Tarantool и других СУБД есть опции, которые отключают синхронную запись на диск и это может до 10 раз ускорить операции в СУБД, но убедитесь, что вы понимаете что делаете. Во время установки пакетов dpkg часто вызывает fsync(). Это поведение можно изменить с помощью опции force-unsafe-io: echo "force-unsafe-io" | sudo tee -a /etc/dpkg/dpkg.cfg.d/force-unsafe-io.

Использование менее требовательного к CPU алгоритма хеширования паролей. По умолчанию Django использует алгоритм хэширования затратный с точки зрения CPU. Для продакшена это важно, потому что взломщику получившему хэши будет труднее взломать пароли прямым перебором. А в тестировании алгоритм хэширования не так принципиален, поэтому можно использовать более быстрые алгоритмы (например MD5) и получить ускорение для тестов (примерно в 4 раза).

Медленное “железо” == медленные тесты. Тут нечего объяснять, поэтому позаботьтесь о том, чтобы ваши тесты запускались на производительных серверах.

Избегать влияния внешнего ПО на тесты. К примеру антивирусы могут значительно “тормозить” операции ввода-вывода. Это касается не только чтения и записи файлов тестами, но и во время поиска тестов фреймворком (aka discovery). pytest может показать сколько времени занимает поиск всех тестов с опцией --collect-only.

Совет № 11: Приоритезация тестов

Использование автоматизированного дымового или приёмочного теста. Даже если у вас мало автоматических тестов автоматизация дымового тестирования может сэкономить немало времени. Если сборка неудачная, то тестировщикам не прийдется отвлекаться от своих задач, сборка не пройдет дальше дымового.

Если новые тесты покрывают новую и часто изменяемую функциональность, то имеет смысл эти тесты запускать в первую очередь. Или приоритезировать тесты для тех частей проекта, которые меняются чаще всего. Если вы используете Git, по посмотреть самые часто изменяемые файлы можно с помощью git efforts:

 path                             commits    active days

 src/box/vinyl.c................. 1247        471
 src/box/vy_log.c................ 150         102
 src/box/vy_run.c................ 145         103
 src/box/vy_stmt.c............... 110         83
 src/box/vy_mem.c................ 92          79
 src/box/vy_scheduler.c.......... 90          63
 src/box/vy_tx.c................. 78          66
 src/box/vy_read_iterator.c...... 70          51
 src/box/vy_cache.c.............. 65          55
 src/box/vy_lsm.c................ 54          39
 src/box/vy_write_iterator.c..... 38          32
 src/box/vy_point_lookup.c....... 31          20
 src/box/vy_range.c.............. 27          27
 src/box/vy_quota.c.............. 25          13
 src/box/vy_upsert.c............. 21          18
 src/box/vy_regulator.c.......... 12          10
 src/box/vy_history.c............ 7           5
 src/box/vy_read_set.c........... 5           5

Совет № 12: Минимизация набора тестов

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

Тест можно назвать избыточным только по какому-то критерию: это может быть, например, покрытие кода по операторам. Мне неизвестны доступные инструменты для минимизации набора тестов.

Совет № 13: Избирательный запуск тестов

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

Разделение тестов на наборы в зависимости от времени прохождения или тестируемой функциональности. По факту вы в тестах ничего не меняете, но меняется подход к тестированию.

“80% of faulty builds discovered in 1% test time. 98% of faulty builds discovered in 2% test time.” - Dr. Elmar Juergens, из доклада “Accelerate 2018 - Test Impact Analysis: How to find new bugs 100x faster”)

it’s the speed of feedback that matters, and the easiest way to speed up feedback is to have your test suite find relevant failures as quickly as possible. The faster your feedback loop, the less need there is for context switching - and the faster you’ll be able to ship features and bug fixes.

Если есть уверенность, что изменения в новой версии касаются только определённой функциональности, то зачем запускать тесты для всего проекта? Выбор только тех тестов, которые покрывают изменения The Rise of Test Impact Analysis.

В таблице можно посмотреть опции и расширения для разных тестовых фреймворков, которые реализуют поддержку TIA.

Заключение

Выше я попробовал на примерах показать, что в любом проекте можно попробовать ускорить тестирование. Пытаться применить все советы в своём проекте было бы глупой идеей - время потратите, а выигрыш может быть незначительным. Горазде эффективнее было бы сначала найти узкие места, а потом найти способы сокращения времени именно в них. В этом могут помочь профилировщики. Расскажу о тех, про которые знаю.

Для сборочных систем Ninja есть ninjatracing. ninjatracing конвертирует логи Ninja в трейс-файл в формате, который понимает Chromium. Похожим образом работает buildbloat: на входе логи Ninja, а на выходе визуализация в формате webmaptree. Здесь есть пример визуализации времени для сборочной системы Chromium. Ни то ни другое не пробовал, поэтому не скажу какой инструмент удобнее.

Для мониторинга времени выполнения пайплайна в CI можно настроить экспорт статистики CI в Prometheus и настроить дашбоард в Grafana. Экспортер для Gitlab CI mvisonneau/gitlab-ci-pipelines-exporter собирает информацию как об отдельных джобах (время выполнения, статус и т.д.)), так и общее время выполнения пайплайна. Похожие по функциональности экспортеры для Github Actions - Spendesk/github-actions-exporter и cpanato/githubactionsexporter.

testres-db позволяет агрегировать отчёты о тестировании из CI в SQLite для анализа стабильности регресионных тестов, времени выполнения тестов и т.д.

github-actions-profile позволяет анализировать логи в Github Actions и показывает время выполнения каждого шага для выбранного воркфлоу.

Думаю после прочтения статьи вы догадались, что тема ускорения тестирования похожа на нору и всё ограничивается только тем, насколько глубоко вы можете в эту нору нырнуть :) Но потратить на это время определённо стоит.

Понравилась статья? Подписывайся на канал в Телеграме.

Полезные ссылки по теме

Статья про эффективность пайплайнов CI, их мониторинг, аналитику и т.д. Слайды евангелиста Gitlab про оптимизацию времени выполнения пайплайна. В слайдах и статье всё по делу, про общие подходы, независимые от языка програмиирования.

Python: доклад Speed up your tests with setUpTestData на DjangoCon 2021. Есть полезные советы, специфичные для разработчиков на Django. Статья с советами для питонистов для оптимизации времени выполнения тестсьюта.

PHP: статья о том, как ускорить сбор данных о покрытии для проекта на PHP.

Ruby: видео доклада с RubyConf 2015 “How to Stop Hating your Test Suite by Justin Searls”, статья “Tips to improve speed of your test suite”, статьи про профилировщик для тестов на Ruby TestProf: часть 1, часть 2, Test Faster: How We Cut Our Test Cycle Time in Half и материалы доклада Владимира Дементьева “Тесты тоже должны быть быстрыми”: видео и слайды и пара докладов: Speeding Up Your Test Suite, Fast Rails Tests Corey Haines.

JavaScript: статья как ускорить тесты в 1000 раз и Node’s require is dog slow.

Теги: softwaretestingfeed