Автоматизация тестирования: «беспилотник» Acronis Kernel
(http://bp-la.ru/bespilotnyj-apparat-danem)
Билд => Тест => Не пройден => и километры логов, разбросанных по разным системам, и десятки минут сведения концов с концами в поисках причины сбоя. Знакомо?
А если иначе?
Билд => Тест => Не пройден => Тикет в JIRA — и разработчик берет баг в работу, потому как вся информация у него уже есть.
Работая в команде Acronis Kernel, я задался целью создать именно такой автотест.
Под катом — моя история.
Введение
Тестирование программного обеспечения — это исследование с целью снабдить заинтересованные стороны (Stakeholders, далее Заказчики) информацией о качестве продукта или услуги (из Википедии).
Заказчики воспринимают результаты тестирования по-разному:
- Продукт менеджер смотрит, какие фичи продукта готовы к релизу, а какие придется упростить \ отложить \ выбросить.
- Тест менеджера интересуют детали исследования: типы выполненных тестов, покрытие кода \ требований, затраченное время, детальные результаты, а также любые сбои \ ошибки \ сложности, которые могут негативно отразиться на объективности результата тестов.
- Разработчику нужны дефекты: понятно описанные, воспроизводимые, включающие всю необходимую для фикса информацию.
- Тестировщик получает задачу, выполняет тест, анализирует результат, репортит баги. По возможности, расширяет тестовое покрытие путем добавления новых тестов или тестовых окружений (сред).
Данные должны быть доступны как можно скорее, в идеале — в реальном времени, сразу по появлению новой сборки продукта.
Теперь представим рабочий процесс, достаточный для выполнения указанных требований:
- Тесты стартуют сразу по появлению новой сборки продукта;
- Время выполнения тестов определяется принятым в компании процессом разработки, но не должно превышать среднего времени между появлениями новой сборки;
- Обнаруженные ошибки автоматически анализируются, уже известные ошибки приписываются к существующим дефектам, остальные регистрируются в виде новых дефектов — в течение считанных минут после обнаружения;
- Результаты тестов отмечаются на «Карте качества продукта».
Здесь, в команде Acronis Kernel, мы построили такой процесс — не сразу, конечно.
Сперва расскажу, с чего мы начинали.
Prehistoric
(http://spongebob.wikia.com/wiki/Primitive_Sponge)
Машинерия
- [велосипед] Control Center (СС) — написанный на питоне планировщик задач, также хранит тест планы и рисует отчеты
- [велосипед] AutoTest Management System (ATMS) — java-based менеджер виртуальных и физических ресурсов
- [велосипед] Кастомный DSL для настройки тестового окружения
- [велосипед] Кастомный Python unittest-based фреймворк для написания собственно тестов, с XML конфигами. Кое-где в конфиги была встроена логика теста
- [велосипед] Кастомная версия TestLink — здесь, по идее, должны жить подробные описания тест кейсов и результаты их выполнения. По факту, использовался в основном для получения уникального ID сценария (группы тестов)
- Виртуальные машины на ESX-i
Работало это все примерно так
- Появилась новая сборка.
- CC отмечал появление сборки, и, согласно хранящемуся в нем же плану тестирования, создавал новые задачи в Testlink.
- ATMS находил задачи в TestLink и запрашивал для них ресурсы у гипервизора. Очередей задач не было: кто успел захватить ресурс, тот и прав.
- Получив требуемый набор VM, ATMS настраивал в них Guest OS. Рецепт настройки задавался в виде кастомного DSL.
- Далее управление передавалось Python библиотеке, которая завершала конфигурирование окружения, деплоила билд и запускала тесты.
- По завершению тестов, АТМС собирал логи и результаты теста, обновлял статус задачи в TestLink.
- CC видел завершение задачи в TestLink, забирал результаты, обновлял свою базу статистики и отправлял письмом отчет о результатах тестирования. Позже Control Center взял на себя функции TestLink, и задачи стали создаваться в его внутренней базе, эмулирующей Testlink для клиента — ATMS.
Тесты шли несколько часов, часто давая случайный (невоспроизводимый) результат. Для анализа фейлов проходили целый квест с посещением ATMS, CC, шары с логами, детальным разбором логов и поиском аналогичных багов в Jira — все врукопашную.
Зарегистрированные сбои иногда воспроизводились, чаще — нет. По большинству заведенных багов разработчики просили уточнить шаги, предоставить виртуальную машину или приложить забытые логи.
Примерно раз в неделю ATMS падал. Если тест завис, или по другой причине ресурсы не освободились, приходилось вручную удалять виртуальные машины, снимать задачу в АТМС и обнулять счетчик занятости хоста.
Сравнить результаты тестов на разных сборках можно было по статичным email-репортам с графиком Тип результата / Номер сборки, или перебирая результаты вручную в СС. Чтобы сравнить результаты одного и того же теста на разных операционных системах, приходилось вручную просматривать логи тестов с каждой ОС.
В результате, девелоперы не доверяли автотестам, более полагаясь на ручной запуск собственных тестов на своем окружении. Подобная «механизация» меня никак не устраивала, ситуацию надо было исправлять.
Brave New World
(http://dkrack.wikispaces.com/Brave+New+World)
В основу архитектуры новой системы автотестов легли:
- Jenkins — планировщик задач, менеджер ресурсов, хранитель истории тестов и детальных результатов
- Виртуальные машины на ESX-i
- Python, Pytest — поиск тестов по тегам, параметризованный запуск, контроль исполнения и вывод результата в формате junit.xml (стандартный формат для Jenkins)
- JIRA — результаты теста в виде багов, метрики успешности проекта
0 (ноль) велосипедов.
Путь задачи
- При успешной сборке билдсервер (тоже Jenkins) стартует проект на тестовом Jenkins, ставя тест в очередь.
- Тестовый Jenkins резервирует ресурсы (VM linked clone), выкачивает свежий код тестов из SVN, запускает CMD скрипт для настройки окружения и зовет pytest.
- Pytest с помощью встроенной функции test discovery подбирает кейсы и стартует тест. Код фреймворка выполняется на Gate VM — контрольной машине, а System Under Test (в нашем случае kernel driver) разворачивается на Test VM, чтобы в случае BSOD не потерять результаты.
- Стандартная python logging библиотека пишет info лог и debug лог в два разных файла:
a) Info лог содержит шаги теста и отвечает двум требованиям: 1) human readable формат, 2) информации достаточно для воспроизведения сбоя.
b) Debug лог включает таймштамп, адрес \ номер строки выполняемого кода и развернутое сообщение. Лог позволяет отследить детальную историю событий, прямо не относящихся к сути теста, но влияющих на результат: удалось ли установить соединение, сколько времени выполнялся ребут, etc. - Тест останавливается при обнаружении первого же сбоя (результат assert = False). Pytest записывает результат + трейс в junit xml.
- Стандартная python logging библиотека пишет info лог и debug лог в два разных файла:
- Jenkins (JUnit Plugin) публикует отчет и стартует python скрипт по репорту багов.
- Скрипт ищет уже известные открытые баги в Jira, если находит — оставляет комментарий «Воспроизведено там-то», если нет — регистрирует новый баг. Сообщение об ошибке (pytest assert) идет в заголовок, шаги из Info лога — в описание, сами логи теста и драйверов аттачатся к багу.
Приведу схему для наглядности:
(© Acronis)
Имя бага добавляется суффиксом к имени VM, так что девелоперы легко могут найти машину при необходимости. Машинка, на которой воспроизвелся уже известный баг, будет автоматически удалена через три дня. Машинка с новым багом будет автоматически удалена после того, как разработчик переведет ее в статус Resolved, а соответствующий тест пройдет без ошибок.
Пример автоматически заведенного бага
(© Acronis)
Раньше автоматизатор вынужден был 80–90% времени тратить на ручной разбор результатов тестов. Теперь достаточно посмотреть на список багов в Jira. Баг продукта идет разработчикам, сбой тестов автоматизатор забирает себе. Если какой-то информации в баг репорте не хватает, не нужно учить людей заводить баги иначе — достаточно изменить код.
Пример общения разработчика с автоматическим баг репортером
(© Acronis)
Поддержка тестов свелась к обработке в коде еще неучтенных видов сбоев. Corner cases будут всегда, это надо понимать, и не стоит ставить целью избавление от 100% сбоев автотеста\тестовой инфраструктуры. Достаточно превратить эти сбои в конкретные action items — баги в Jira, в нашем случае, и исправлять их один за другим.
Карта качества продукта
Общий обзор состояния тестируемых компонент теперь можно получить, взглянув на Jenkins dashboard:
(© Acronis)
Дашборд реализован с помощью плагина https://wiki.jenkins-ci.org/display/JENKINS/Dashboard+View.
Возможно не все читатели знакомы с Jenkins, так что поясню значения колонок:
- S (Status) — результат последней сборки (в нашем случае теста);
- Name — название теста;
- W (Weather) — иконографика, показывающая историю качества сборки, на 5 сборок назад. Солнышко означает, что все 5 сборок успешны, грозовая туча — все 5 сборок плохи;
- Build Parameters — в нашем случае указан путь, соответствующий ветке кода и содержащий номер сборки;
- Last Duration — время выполнения последней сборки, начиная с момента постановки заказа в очередь, и до момента завершения сбора логов с последнего окружения и отправки отчета о результатах теста;
- Build Description — в описание сборки автотест добавляет номера багов, автоматически заведенных в Jira, с указанием, новый ли это баг (new), или уже известный (upd);
- Last Success, Last Failure — как давно была сделана последняя успешная / неуспешная сборка.
Результаты
Систему, которую я описал выше, мы построили и отладили к концу осени прошлого года, и затем активно добавляли новые сценарии для тестирования. С февраля 2016 года я перешел full time на другой проект.
За время моего отсутствия (полгода):
- Найдено и заведено автоматически 129 корректных багов — примерно по одному новому багу каждый рабочий день.
- Из других источников заведено 48 багов.
Проект полгода жил и развивался усилиями только разработчиков, без единого тестировщика. Разработчики самостоятельно добавили новый компонент, создав Jenkins проекты и Pyhton код по аналогии с существующими.
Некорректных багов за это время тоже заведено довольно много, в основном дубликатов, рожденных некорректным сетапом нового теста или отказами тестового сервера. Однако это уже тема для отдельной статьи.