Между холиваром и оверинжинирингом: что, если разработчик не доверяет тестам тестировщика
Вы — разработчик и хотя бы раз говорили тестировщику «Докажи руками»? Или вы — тестировщик и хотя бы раз слышали такое от коллег-разрабов? Либо вы — продакт или тимлид, в команде которого случались или могут случиться такие конфликты? Тогда эта статья для вас!
Кто-то после «А докажи, что это все действительно работает» или «А как ты проверял?» звереет и начинает открыто ругаться с коллегами — что ж, устраивать холивары, конечно, интересно, но бесполезно. Кто-то действительно начинает тратить ресурсы на воспроизведение бага. Однако можно выстроить такой процесс коммуникации, в котором разработчик доверяет результатам команды тестирования и даже иногда сам дополняет тесты, при этом не скатываясь в оверинжиниринг.
Меня зовут Илья Колесов, я — Senior SDET (Software Development Engineer in Test) в команде KasperskyOS Automotive & Embedded Quality Control «Лаборатории Касперского» и занимаюсь разработкой автоматизированных тестов на стыке embedded- и desktop-систем. В этой сфере я прошел весь путь с нуля до готовых решений. И в этой статье расскажу о взаимодействии с разработкой через автоматизацию тестирования — поделюсь своим опытом того, как удается преодолеть недоверие и сделать коммуникации более эффективными.
Почему это важно
Для начала немного контекста.
В «Лаборатории Касперского» мы разрабатываем автомобильный шлюз безопасности на базе собственной микроядерной операционной системы KasperskyOS. Это такая железка, которая ставится в машину и умеет работать с внешними устройствами, например смартфонами, через облако. Одновременно она работает с блоками внутри машины.
Казалось бы, простая история: сигнал туда ходит, сюда не ходит. Но это довольно важный компонент автомобиля, и всегда есть люди, которые хотят устройство взломать, а сигнал подменить.
Это накладывает ограничения на наши тестовые стенды: мы тестируем реальное устройство, которое имеет свои свойства, лимиты по скорости загрузки и работы. Получается, что каждый прогон теста для нас довольно дорог, потому что мы тратим время на инициализацию и загрузку устройства. И поэтому мы не хотим тратить время на пустые прогоны, показывающие, что все плохо просто потому, что мы где-то забыли поставить точку с запятой.
Пример из жизни и аналогия
Начнем с недавнего примера.
У нас на конвейере вдруг сломался приемочный тест, который осуществлял простую проверку того, что сообщения конвертируются из одного протокола в другой (mqtt→can). Казалось бы, у нас все есть, чтобы завести хороший баг: результаты теста из CI, логи, описание сценария и тестовый прогон. Но есть и небольшой нюанс — тесты с похожими сценариями проходят.
Мы завели баг и буквально через полчаса получили его от разработчиков обратно с комментарием, что у них не воспроизводится.
Выполняем тест локально — ничего не изменилось, тест не проходит. Добавляем новые логи и уточняем описание, после чего возвращаем разработке… Но у них опять не воспроизводится.
История продолжается созвоном с демонстрацией экрана разработчика — у них действительно все работает, сценарий успешно выполняется. Тестировщики начинают сомневаться в себе: что же они такое написали, что в одном месте работает, а в другом нет? По просьбе разработки записывают видео ручного выполнения тест-кейса с подтверждением дефекта…
Для меня это уже триггер: что-то идет не так. Видео с воспроизведением дефекта классно записывать, если тестируется то, что видно, — GUI, когда важно показать, что кнопка стала красной или сменила состояние недостаточно быстро. А когда в продукте не ходят сообщения, видео особо не поможет.
Когда копнули глубже — сравнили написанный кейс и автотест — оказалось, что кейс пропускает один момент сетапа, который выставляет уровень логирования на debug. А при уровне info дефект не воспроизводится. Кейс обновили, дефект исправили. Но в целом такая карусель еще и с негативом, направленным друг на друга, могла бы продолжаться дольше. В такие моменты стоит остановиться и поглубже разобраться с проблемой.
В этой статье поговорим как раз о том, как можно укоротить циклы передачи дефектов от разработки на тестирование и обратно.
Продукт — это машина, которая куда-то едет.
Знанием особенностей трассы обладает только штурман, пилот ведет машину, опираясь на них. Можно ехать и без подсказок, но тогда время прохождения трассы будет неконкурентоспособным, быстро ехать, опираясь только «на глаза», — страшно.
В экипаже официально нет таких понятий, как пилот и штурман, есть «водитель 1» и «водитель 2», да и в моей аналогии не важно, является ли разработчик штурманом или пилотом. Результат в равной степени зависит от обоих, и, если пилот и штурман плохо между собой взаимодействуют, то они просто не смогут быстро ехать.
Какие цели мы преследуем в ралли (и в продукте)? Как минимум — добраться до финиша. Это всегда цель номер один. Если ты где-то сломался, и хотя до этого ехал очень быстро, но до финиша не доехал, результат — сход и последнее место. На следующем месте — задача добраться до финиша первым. Но есть и третья цель — добраться до финиша первым и без поломок, чтобы не тратить потом время на восстановление.
Наш продукт — автомобильный шлюз безопасности Kaspersky Automotive Secure Gateway. Помимо ограничений по скорости мы зажаты рядом стандартов и нужно четко следовать траектории — трассе, которую мы нарисовали. Любое отклонение может привести к тому, что мы не доедем до финиша (или не сможем сделать это без поломок).
Все фото — авторские
Тестовые сценарии
В ралли мы сначала передвигаемся по трассе медленно, вдумчиво, размышляем, как здесь проехать, и прописываем траекторию. Диктуем повороты, записываем в специальную книжечку. Переоценили допустимую скорость? Недооценили сложность поворота или рельеф покрытия — мы оказываемся в кювете. В целом ничего страшного — приезжают товарищи, достают из кювета. Но неприятно, потому что мы теряем время.
Как не терять это время? Поговорим об автоматизации.
В целом процесс разработки автоматизированных тестов и сценариев для них похож в разных командах. Все делают примерно одинаково. У нас есть определенные требования, ограничения от разработчиков и аналитиков, ревью. Разрабатывая функционал, мы параллельно создаем и сценарии тестирования.
Пример из жизни. Мы тестировали язык программирования, который строится на базе функционально-блочной диаграммы. То есть, чтобы реализовать какую-то логику и получить результат вычисления, необходимо разместить нужные блоки и между ними провести связь.
Готовлю простейший сценарий: добавить на диаграмму два блока, провести связь и получить результат. Спрашиваю у разработки: сможем ли мы это автоматизировать в тестах?
Конечно сможем! И мы расходимся по своим командам…
Ребята делают функционал, мы пишем и расширяем сценарии (много блоков и связей — много сценариев). Созваниваемся с разработчиками и еще раз обсуждаем вопрос автоматизации:
Оказалось, что для добавления двух блоков на диаграмму, нужна вся эта прекрасная часть слева — нужно сделать мок GUI, для чего сформировать модель и подключить какие-то Qt-шные либы. И это еще до того, как мы добавили на диаграмму какие-то блоки.
Решение никому не понравилось. Тестировщикам было сложно погружаться в эти инструменты, чтобы все автоматизировать. Поддерживать это еще сложнее. А разработчикам не понравилось, что к ним приходят тестировщики и задают массу вопросов о добавлении блоков — отвлекают от работы.
На базе этой истории мы разработали другой подход. В следующий раз в аналогичной ситуации вопрос ставили так: как мы можем добавить блоки, какие инструменты для этого с вашей стороны нам могут пригодиться?
Вопрос поставлен по-другому: не «да или нет», а «какие инструменты мы можем использовать, чтобы это сделать». В нашем случае это был объект внутри кода продукта, который позволяет взаимодействовать с базой данных. Это может быть любой другой инструмент, позволяющий работать с API, — вариантов миллион.
Чтобы заручиться доверием, взгляните на кейсы с точки зрения автоматизации и просите:
- инструменты;
- API;
- детали реализации;
- ревью кейсов.
С таким подходом вы:
- показываете кейсы разработчикам еще до того, как начнете их автоматизировать. Разработчики могут глубже заглянуть в кейсы, будут понимать, что и как вы делаете;
- если разработчики дают какие-то инструменты, они уверены, что вы с их БД работаете штатными средствами. Будут понимать инструменты, которые вы используете, и через это будут доверять.
Написание кода
Дисклеймер: в этой аварии пострадали только гордость и самолюбие пилота, потому что машина предназначена для таких приключений. Ну и на самом деле продукт также рассчитан на то, что иногда что-то идет не так.
Бывает так, что мы очень быстро поехали, поверив в себя и свои тесты, но где-то что-то пропустили — прочитали не тот поворот, повернули вместо направо налево — и, условно говоря, приехали в березу. Вышли, посмотрели, почесали голову. Как это ни печально, в этот раз до финиша не доехали, потеряли сколько-то времени, а потом еще больше на восстановление системы.
Что делать, когда выкатывают изменение, которое ломает тесты, и его надо быстро поддержать? Как при этом не приезжать в деревья?
Еще один пример из жизни.
Нам добавили в авторизацию пользователя сертификаты. Все почему-то это очень любят. Для тестов это всегда больно, потому что это дополнительное действие, которое влияет на каждый тест. Боль была и в этот раз: изменение поддержали, но локально у меня 9 из 10 прогонов проходили, а на CI — наоборот, 9 из 10 падали.
Можно создать задачу на разработку с формулировкой, что иногда работает, а иногда нет, навалить туда логов, ссылок на прогоны и гневно подписать «раньше работало».
На такие баги рискуем получить в ответ сообщения, похожие на то, что мне сейчас шлют из автосервиса:
Цифры замазали, чтобы никого не травмировать.
Но с этим можно работать по-другому.
Начнем издалека. Тесты в создании и поддержке никогда не проще продукта. Они иногда даже сложнее. Поэтому вам как разработчикам автотестов необходимо играть по тем же правилам, что и разработчикам: разработать гайдлайны, поддерживать документацию и следить за кодовой базой. Можно просто позаимствовать лучшие практики у разработчиков. У них же наверняка есть документ, по которому они пишут код. Используя его, вы будете с разработкой говорить на одном языке. Пишите код так, чтобы кто угодно мог его прочитать и понять.
Очень полезно делать внутри команды pull-реквесты и ревью, чтобы все в целом понимали, как правильно и хорошо писать тесты. Иногда какие-то базовые ошибки проще отлавливать на этих этапах.
И еще один момент — за код не должно быть стыдно. Рано или поздно к вам придет разработка и скажет: «Показывай код!» Это может быть в двух случаях: когда вы нашли дефект, который не воспроизводится руками, или когда вы сами просите помощи.
Когда человек видит «плохо пахнущий код», ему проще сказать, что проблема в самих тестах. В этой ситуации вы будете долго доказывать, что хоть код и написан плохо, но дефект он находит. Поэтому за код не должно быть стыдно.
Когда у нас появляется какой-то новый функционал, который нужно срочно поддержать (как сертификаты), умение разговаривать с разработкой на одном языке помогает. Мы умеем пользоваться отладчиком, у нас есть средства мониторинга, мы что-то логируем и можем вместо «ничего не работает после внедрения сертификатов» создать тему «у нас не проходит тест соединения с сервисом сертификатов», добавив туда детальное описание проблемы: что мы делали и какой был результат. Можем даже высказать какие-то гипотезы, почему это не работает, и попросить помощи. По моему опыту разработчики никогда не отказывают, если вы приходите с хорошо описанной проблемой, понятной постановкой задачи и кодом. Это хороший способ получить качественную экспертизу от людей, которые пишут код и тестируют продукт.
Таким путем мы, опять же, повышаем понимание всей команды, что и как работает в тестах. Бонусом мы можем не писать тест, а передать эту функцию разработке. У нас пока не получилось так сделать, но, может быть, однажды…
В целом, может быть, это сейчас выглядит, как принцип «Нормально делай — нормально будет». Но суть здесь немного в другом. Например, в проблеме с сертификатами я решил, что не работает именно соединение с продуктом. Написал код с определенными повторами и слипами (честно говоря, он выглядел ужасно):
#in case the client didn't connected properly to TLS we need to make full reconnect while not self._client.connected_flag and tries < max_retries_num: logging.info("Broker hasn't started yet") self._client.disconnect() self._client.loop_stop() self._client.connect_async(self.broker, self.port) self._client.loop_start() for x in range(10): #additional wait for flag appearance with 100ms sleeps if self._client.connected_flag: break self._client.reconnect() time.sleep(0.1) tries += 1
Первое, что сказал разработчик, которого я привлек на помощь: «Пожалуйста, давай это перепишем». Мы переписали, и это не помогло.
Но в этот момент я понял, что дело просто не в коде теста. Дело может быть в инфраструктуре, в среде. И мы нашли проблему именно там — в этом нам помогла уверенность в коде. У нас большие стенды, в них много компонент и наверняка есть не одно узкое место. Уверенность в коде помогает быстро переключиться на поиск проблемы в других местах.
В целом «Нормально делай — нормально будет» — это не всегда просто. Тесты должны быть хорошо параметризованные и читаемые, внутри должны быть понятны все функции, а код должен соответствовать стандартам команды. Поддерживать такие условия непросто, и иногда в этом нет необходимости. Но во всех сложных дефектах это отрабатывает. И поддерживать хорошо написанный код проще — на расширение какого-то теста в будущем мы потратим меньше времени, поскольку будем лучше ориентироваться в написанном и в код заложена такая возможность.
Чтобы помочь поверить в ваши тесты на стороне разработки, нужно пройти некоторый путь. Старайтесь писать код так, чтобы его можно было показывать. Нужно, чтобы люди вас понимали и могли разговаривать с вами на одном языке. Показывайте:
- сложные в реализации функции;
- мигающие тесты;
- как устроена инфраструктура.
А еще лучше, если разработчик сам поможет написать какие-то функции. После этого он будет частью этого корабля (и критиковать этот корабль уже не так просто, как если бы он абстрагировался: «Это тестировщики что-то там написали»).
Запуск тестов
Финальный этап — запуск тестов.
Вернусь к аналогии с ралли. Перед тем как провести гонку, техническая комиссия осматривает автомобиль на предмет безопасности на трассе, проверяет, что на нем действительно можно ехать и что, если что-то пойдет не так, с участниками будет все хорошо. Как мы можем провести аналогичную процедуру — подготовиться к запуску — в тестах?
Начну с того, что история с «работает на моей машине» больше никому не интересна. Тесты на машине тестировщика никому не нужны, потому что никто не знает, как у него настроена среда. Это может вообще ни у кого больше не воспроизводиться. Поэтому здесь рекомендую хранить все — конфигурационные файлы, настройки среды, окружения и т. п. — в виде кода. Так их проще менять и отдавать людям на запуск.
Никто не любит магию. Если вы отдаете тест, перед которым нужно задать некие параметры по умолчанию, сконфигурировать стенд, передать артефакт, создать пользователя и т. п., и кто-то ошибется на одном из этапов, вам просто скажут, что локально тесты не запускаются. Нужно стремиться к тому, чтобы все конфигурации лежали в репозитории.
С одной стороны, мы стремимся запускать все в CI и результаты смотрим оттуда же (казалось бы, локальные запуски не нужны). С другой стороны, иногда нужно запустить продукт именно локально, чтобы в нем подключиться отладчиком и посмотреть, что делает тест. Именно в этот момент — самый сложный — разработчик придет и скажет: «Либо ты у себя настраивай среду разработки, либо настраивай мне свой тест». Такое в моем опыте было не раз. И инструкции не спасают, потому что их надо поддерживать. Если из условных 13 пунктов два устарело, один пропущен, то ничего не заработает.
И еще один пример из практики. Попробуйте найти одно отличие:
\\storage\address\projectName\folder1\v8.8.8.89\folder 2 \\storage\address\projectName\folder1\v8.9.8.89\folder 2
Я его искал здесь порядка часа, потому что строчка длинная. Когда нашел, выяснилось, что тесты не работали просто потому, что запускал не на той прошивке.
Старайтесь убирать все в переменные, в конфигурацию и делать строку запуска максимально короткой.
Чтобы вашим запускам верили разработчики, слушайте. На вас выливается поток вопросов:
- Не ясно, что тестирует.
- Не понятно, почему упал.
- То проходит, то нет.
- Код теста не читаем.
- Долго выполняется.
- Требует сложной ручной подготовки среды.
Не стоит на них остро реагировать.
Один скопированный вами артефакт к дефекту может сэкономить разработчику или другому вашему коллеге несколько часов. Например, он откроет логи и увидит, в чем проблема, — и ему не нужно будет разворачивать и настраивать у себя среду. В итоге он исправит дефект за час, а не за сутки, и это очень сильно ускорит тестирование. А самое главное, это улучшит ваши отношения с командой. Когда команда видит, что вы ее слушаете и пытаетесь выстроить процесс так, чтобы им прилагать минимум усилий, разбираясь в вашей проблеме, они будут вам благодарны.
На одном из проектов мы пришли к тому, что после падения тестов с машины забирали логи, базы данных, конфигурационные файлы и выкладывали их на Artifactory. Создавая дефект, прикладывали к нему ссылку на это хранилище, и все знали, как с этим работать.
Выводы
Выводы довольно простые.
- Просите (и ищите) на ранних этапах инструменты, для того чтобы эффективно автоматизировать.
- Показывайте код.
- Слушайте обратную связь, работайте с ней и старайтесь эту обратную связь поддержать.
- Верьте в свои тесты.
Это путешествие, которое начинается с любого дня вашей работы:
- Повышайте экспертизу в разработке.
- Привлекайте разработчиков в сложных кейсах. Возможно, кто-то из них не так охотно идет на контакт.
- Собирайте обратную связь.
Это маленькие шаги, которые помогают построить доверие к тестам в команде. Мы — экипаж и работаем вместе, и задача у нас одна — обеспечить качество продукта. И работает это в обе стороны — постепенно приходит понимание друг друга.
Если вдруг вы не согласны — добро пожаловать в комментарии :)
Ну, а если согласны — вы очень крутой SDET, и возможно, именно вас мы ждем в команде «Лаборатории Касперского».