Test Impact Analysis (TIA, анализ влияния тестирования) — это современный способ ускорить этап автоматизации тестирования, который работает путём анализа графа вызовов исходного кода. Благодаря этому можно определить, какие тесты необходимо запустить после внесения изменений в продакшен код. Microsoft проделала большую работу над этим подходом.
Одним из проклятий современной разработки программного обеспечения является наличие «слишком большого» количества тестов, чтобы выполнить их все до возврата кода на сервер. В этих случаях разработчики прибегают к дорогостоящей стратегии: они не запускают тесты на своей локальной рабочей машине. Вместо этого они полагаются на тесты, запускаемые позже на интеграционном сервере. И довольно часто даже они приходят в негодность. Это неизбежно, когда для команды разработки становится нормой практика «сдвиг вправо» (shift right).
Разумеется, всё, что вы тестируете до интеграции, должно быть немедленно протестировано после интеграции в CI-инфраструктуре. Даже в самых высокопроизводительных командах разработки могут возникать сбои, вызванные несоблюдением сроков фиксации коммитов в реальном времени. Бывает, что в таких командах кто-то хочет «сэкономить» на согласованном процессе интеграции и НЕ запускать тесты. К счастью, CI-серверы и приличная серия шагов сборки помогают быстро отследить такие моменты.
Ускорить выполнение тестов можно различными методами, включая их параллельный запуск на многих машинах и использование тестовых двойников для медленных удалённых сервисов. Но в этой статье мы сосредоточимся на сокращении количества тестов, определив те, которые с наибольшей вероятностью выявят новый баг. Используя структуру тестовой пирамиды, мы запускаем модульные тесты чаще — потому что они обычно выполняются быстрее, являются менее хрупкими и дают более конкретную обратную связь. В частности, мы выделяем набор тестов, которые должны выполняться в рамках CI: до и после интеграции. Затем создаём конвейер развёртывания, чтобы позже запускать более медленные тесты.
Переформулирую проблему: если бы тесты выполнялись бесконечно быстро, мы бы постоянно выполняли все тесты, но это не так, поэтому нужно соизмерять затраты и ценность.
В этой статье я подробно рассказываю о новой области Computer Science, связанной с тестированием, в которой Microsoft занимает лидирующие позиции, и на которую компаниям с длинными наборами автотестов стоит обратить внимание. Если вы работаете в экосистеме .NET, вы можете сразу же воспользоваться достижениями Microsoft в области «анализа влияния тестирования». Если же вы не работаете в .NET, вы должны быть в состоянии разработать что-то самостоятельно и довольно дёшево. Один мой бывший работодатель сам разработал продукт, основанный на проверке концепции, о чём я и рассказываю ниже.
Традиционные стратегии сокращения сроков автоматизации тестирования
Для полноты картины я напомню о традиционных стратегиях «запуска подмножества тестов» — они по-прежнему доминируют в индустрии с учётом новых реалий параллельного выполнения тестов и виртуализации сервисов.
Создание наборов и тегов
Основные группы модульных, сервисных и функциональных UI тестов. Внутри модульных тестов теги для значимых подгрупп, включая один «экспресс», который проверяет подмножество остальных.
После запуска тестов 'Shopping Cart' все, кроме одного, прошли.
Этот подход работает с технологиями рекурсивной сборки, такими как Ant, Maven, MSBuild, Rake и подобными.
Исторически сложилось так, что команды отказывались от идеи сделать свои тесты бесконечно быстрыми и использовали наборы или теги для нацеливания на подмножество тестов в каждый момент времени. С созданием наборов и тегов подмножество тестов можно описывать словесно. Например, «тесты пользовательского интерфейса для корзины». Теги или наборы могут относиться к бизнес-областям приложения, техническим или многоуровневым группам. Определение тегов и наборов требует творческого подхода дизайнера. По крайней мере, для того, чтобы продвинуться к оптимальным группировкам. Это подразумевает, что они могут быть недостаточно, неточно и неправильно сгруппированы — и это встречается достаточно часто, хотя человеку трудно это определить. Слишком малое или слишком большое количество одновременно выполняемых тестов приводит к высокой вероятности запуска только одного набора — что является нерациональным использованием вычислительных ресурсов и времени, а также риском пропустить баг. Команды могут выбрать CI-задачи, которые задействуют меньший набор для каждого коммита, а также ночную сборку, которая выполняет все тесты. Очевидно, что это противоречит целям непрерывной интеграции.
Однако именно с помощью наборов и тегов большинство разработчиков программного обеспечения организуют свою базу тестового кода.
Предварительно рассчитанные графы соотношения тестов
276 тестов с условными обозначениями их «размеров».
Те, которые выполняются для заданных коммитов, с двумя неудачами в результате. Некоторые из них оказываются маленькими, другие — средними или большими. Я изобразил дерево/фрактал только потому, что это помогает объяснить концепции (на самом деле всё не так).
Известная внутренняя система сборки Blaze от Google за годы своего существования была взята за основу для нескольких новых технологий с открытым исходным кодом. Наиболее заметными являются Buck от Facebook и Bazel от Google, Pants от Twitter, Foursquare и Square. Blaze от Google управляет единым направленным графом по всему монорепозиторию. В Blaze есть механизм прямой связи тестового и продакшен кода — он представляет собой мелкозернистое дерево каталогов продакшен исходников и связанных с ними тестовых исходников. В нём есть явные объявления зависимостей через проверенные BUILD-файлы. Эти BUILD-файлы могут поддерживаться и развиваться разработчиками, но также могут быть верицифированы как правильные или неправильные с помощью автоматизированных инструментов. Этот процесс повторяется в течение долгого времени и позволяет сделать направленные графы корректными и эффективными.
Важно, что этот инструментарий может указывать на избыточные утверждения о зависимостях. Таким образом, для заданной директории / пакета / пространства имён разработчик мог довольно легко запустить подмножество тестов — но только тех, которые возможны через направленные графы из BUILD-файлов. Конечной экономией времени, как для разработчика перед интеграцией, так и для интеграционной инфраструктуры — масштабируемой CI-инфраструктуры 'Forge' (позже TAP) — стало автоматическое выделение подмножества тестов для запуска в каждом коммите на основе этого встроенного интеллекта.
В статье Taming Google-Scale Continuous Testing приводится множество умопомрачительных статистических данных. На мой взгляд, эта штука обошлась Google в десятки миллионов, но принесла за годы работы десятки миллиардов.
Анализ влияния тестов
Анализ влияния тестов (Test Impact Analysis, TIA) — это техника, которая помогает определить, какое подмножество тестов необходимо для данного набора изменений.
Аналогичное изображение для тестов, запускаемых для гипотетического изменения.
Ключ к этой идее заключается в том, что не все тесты проверяют каждый рабочий исходный файл (или классы, созданные на основе этого исходного файла). Покрытие кода или инструментарий, используемый во время выполнения тестов — вот механизм, с помощью которого можно получить эту информацию. В конечном итоге эти сведения представляют собой карту исходного кода для продакшена и тестов, которые будут их выполнять, но начинаются они с карты того, какие продакшен источники будет выполнять тот или иной тест.
Один тест (из многих) проверяет подмножество продакшен источников.
Один источник выполняется подмножеством тестов (будь то модульные, интеграционные или функциональные).
Обратите внимание, что стилизованная диаграмма выполненных тестов такая же, как и для технологий построения направленного графа, описанных выше. По сути, это одно и то же, так как управление BUILD-файлами с течением времени приводит примерно к тому же результату, что и TIA.
Карты TIA действительно можно использовать только для изменений по сравнению с точкой отсчёта. Это может быть такой же простой работой, которую разработчик будет коммитить или уже закоммитил. Это также может быть куча коммитов — например, всё, что было закоммичено сегодня (ночная сборка) или с момента последнего релиза.
Один из примеров использования подхода TIA заключается в том, что у вас слишком много тестов, которые покрывают одни и те же фрагменты продакшен кода. Если это прямые дубликаты, то удаление тестов после анализа теста и путей через продакшен код, которые он отрабатывает, вполне возможно. Однако часто это не так, и выяснение способа фокусирования тестов на том, что нужно протестировать, и совсем не на транзитивных зависимостях в коде, — это другая область внимания. Она неизбежно опирается на устоявшуюся практику использования тестовых двойников и, в последнее время — метод виртуализации сервера (для интеграционных тестов).
Минимальный уровень создания списка изменений — это «Изменённые продакшен исходники», но идеальный вариант — определить, какие методы / функции изменились, и затем сузить выборку тестов только до тех тестов, которые бы проверяли эти изменения. Однако на данный момент есть одна готовая технология от Microsoft, которая работает на уровне исходных файлов, и один метод многократного использования (разработанный мной). Читайте далее.
Анализ влияния тестов с помощью обычных инструментов покрытия кода и скриптов
Когда я работал в HedgeServ, у меня возникла идея использовать современные готовые инструменты покрытия кода для анализа воздействия. На основе этой идеи я сделал две публикации о подтверждении концепции (с соответствующим кодом на Github): одна для Maven и Java, а другая — для Python. Мне тогда казалось, что я изобрёл что-то новое, однако оказалось, что в этой области существует довольно много предшественников. Показанная мной техника дешева для разработки в рамках набора инструментов, даже если она может стоить затрат на запуск в вашей CI-инфраструктуре.
Простая реализация идеи анализа влияния тестирования требует от вас некоторых предварительных действий:
- Запустить один тест и собрать для него покрытие кода.
- Из исходных продакшен файлов, затронутых тестом, создать временную карту исходных продакшен файлов (ключ) к пути / имени теста (значение).
- Обновить исходные файлы, содержащие главную карту, заменив все предыдущие данные для этого теста.
- Закоммитить изменённый исходный файл карты в VCS (разрешение на это должно быть только у данного задания CI).
- Очистить данные о покрытии (чтобы не запутаться в отчётах о покрытии по каждому тесту).
- Вернуться к пункту #1 для следующего теста (сначала самые последние изменённые исходники / тесты?)
После выполнения всех тестов по одному у вас будет исчерпывающая карта, связывающая продакшен код с тестами, которые его покрывают.
Затем, когда вы позже внесёте изменения в продакшен код, вы сможете выяснить, какие тесты использовали этот код и, следовательно, будут информативными при выполнении. Любые возникшие в тестах сбои — это единственные сбои, которые могли произойти в результате внесённых изменений. Два примера доказательства концепции, упомянутые выше, содержат небольшое количество Python-кода, который пытается:
- Рассчитать карты TIA заранее;
- Использовать карты TIA в ситуации, предшествующей интеграции (с небольшими модификациями это можно использовать и в CI-задачах).
Тестовая база HedgeServ состоит из обычных быстрых модульных тестов, за которыми следуют интеграционные тесты, представляющие собой электронные таблицы Microsoft Excel, которые, в свою очередь, косвенно вызывают Python. Всего их 12 000. Это многочасовые тесты обширных и продвинутых алгоритмов, которые было бы невозможно выполнить в рамках CI-инфраструктуры без стратегии «запускать их поменьше». Их DevOps команда задействовала доказательство концепции под названием «Test Reducer» (первоначальное название, которое я дал этой технологии) — и теперь самые быстрые перестановки занимают минут десять. Фантастическое улучшение. Разработчики и тестировщики могут запускать их до интеграции, и CI-инфраструктура может делать то же самое. Управляющий директор HedgeServ по разработке Кевин Лоо говорит мне, что «разработчики рассчитывают на более быстрые прогоны тестов, и темпы разработки усиливаются из-за возросшей уверенности».
Поскольку используются общие инструменты покрытия кода, аспект TIA приходится выполнять по одному тесту за раз, что требует дополнительных затрат. По этой причине карта, полученная в результате анализа, проверяется в системе контроля исходного кода и инкрементально обновляется. Поэтому она должна быть текстовой и упорядоченной, чтобы различия были имели определённый смысл. Проверка карты в системе контроля исходного кода также приносит пользу CI-инфраструктуре и отдельным разработчикам, которые хотят проводить меньше тестов перед интеграцией (и код-ревью).
Эта структура TIA имеет ограничение, связанное с природой инструментов оценки покрытия кода: одновременно можно выполнять только один тест, чтобы рассчитать точный график воздействия. Чтобы использовать данные карты, необходимо запустить «git status» (или git show >hash>) и затем проанализировать результаты, чтобы найти 'измененные/добавленные/удаленные' ('changed/added/deleted') источники продакшен кода. Это и есть ключи к карте влияния, которые приводят к списку тестов для запуска. Только CI-задача по сбору данных имеет ограничение «один тест за раз», поэтому вы более или менее считаете его вечно запущенным.
Технология тестирования может быть совершенно чуждой основному языку, на котором написан продакшен код. В случае HedgeServ их алгоритмические тесты были в файлах Microsoft Excel под контролем исходников. Если это возможно, то это возможно и с TestComplete от SmartBear, и с Unified Functional Testing (UFT — бывший QTP) от HP, и, конечно, с Selenium. Единственное требование — чтобы тесты можно было запускать по одному за раз (пока вы строите карту TIA). Вам также придётся взять на себя обязательство обновлять карту с определённой периодичностью после её создания — используйте CI-инфраструктуру.
Затем вам предстоит решить, где хранить данные карты. Вы можете выбрать файловый ресурс, или хранилище документов, или реляционную схему. Я выбрал (и вам рекомендую) текстовые файлы в каталоге, который находится в том же репозитории / ветке, что и сам исходный код для продакшена. Это, по крайней мере, позволяет работать с ветвлениями (какой бы ни была ваша модель ветвления) и иметь разные карты влияния, возможно, отражающие разную природу кода.
Недавно я работал с проектом одного клиента, использующего KDB и Q для своей системы, и пытался посоветовать ему, как сократить время тестирования. Однако для них не существовало технологий покрытия кода, так что на этом разговор закончился.
VectorCAST/QA — приложение
Компания Vector Software выпустила продукт под названием VectorCAST/QA, который представляет собой приложение, использующее покрытие кода для выполнения меньшего количества затронутых тестов. Их технология в основном продаётся в автомобильной (и смежной) промышленности, где используется программное обеспечение на языках C, C++ и Ada.
NCrunch для VisualStudio
NCrunch для команд .NET был запущен в 2011 году после нескольких лет разработки. Это сложный плагин для Visual Studio, который может оптимизировать порядок выполнения тестов на основе алгоритмов, предсказывающих, какие из них с наибольшей вероятностью сломаются при том или ином изменении. В 2014 году были добавлены дополнительные фичи, позволяющие сократить тесты только до затронутых изменениями. Также в 2014 году NCrunch стал совместим с CI-системами в целом. В частности, он смог организовывать выполнение MsBuild вне пользовательского интерфейса VisualStudio с той же экономией времени, на которую вы могли бы рассчитывать. Сырые данные карты влияния не хранятся в системе контроля версий, так как являются двоичными, но могут быть переданы разработчикам и CI-инфраструктуре через общий сетевой ресурс. NCrunch является коммерческим продуктом, но за разумную лицензионную плату на одного разработчика (и одного тестировщика). CI-узлы бесплатны. Создатель NCrunch, Ремко Малдер, согласен с тем, что никто не должен платить за что-либо дважды или расплачиваться за то, что в 2017 году масштабировал свою CI-инфраструктуру с помощью Docker и т. п.