Это краткое содержание выпуска подкаста «Запуск завтра» о тестировании. «Практикум» — партнёр подкаста, а мы — дети «Практикума». Если есть время — послушайте выпуск.
О герое
Максим Садым программировал в СКБ «Контур», но по политическим причинам уехал за рубеж. Потом занимался тестированием в «Амазоне», а мечтал попасть в «Гугл». Несколько лет спустя он осуществил свою мечту.
С чего начинает каждый тестировщик
Ведущий Самат Галимов: Когда я был техническим директором «Медузы», у меня было семь разных телефонов и один компьютер. Чтобы понять, как статья будет вести себя на разных девайсах, я открывал её на всех этих устройствах.
В статье я специально использовал все возможности форматирования и добавлял разные типы контента. Я смотрел, как будет выглядеть длинный заголовок, как отображаются картинка и видео, что происходит, если интегрировать посты из «Твиттера».
Максим Садым: То, что ты описал, это стандартное UI-тестирование — тестирование пользовательского интерфейса. С этого начинают все. Если ты делаешь стартап, первое, что ты делаешь, — тестируешь всё «руками». Вопрос: что ты будешь делать потом, когда появится много клиентов, разработчиков и фичей.
Существуют два пути. Первый — нанять большое количество тестировщиков, которые будут на куче девайсов пробовать воспроизводить разные хитрые сценарии.
Второй — написать программу, которая будет автоматически прогонять тесты. Вместо того чтобы Самат сидел и сам кликал на семи телефонах разные кнопки, мы как бы одновременно запускаем программу на семи, на ста разных телефонах.
Сценарии тестирования — это то, что нужно сделать с программой, чтобы проверить определённую штуку. Например, открыл главную страницу «Медузы», нажал на статью.
В этот момент статья должна открыться. Другой вариант — ты открываешь список товаров, выбираешь конкретный продукт и добавляешь его в корзину. Теперь в корзине должен появиться товар.
Как часто прогонять тесты
Если ты поработал с модулем, то вручную запускаешь тесты, которые касаются этого модуля. Дальше тесты запускаются в обязательном порядке перед тем, как эти модули будут выложены.
Как это было в «Амазоне». Есть несколько стадий выкатывания новой версии, перед каждой обязательно прогоняются все пользовательские сценарии. Если хотя бы один из них «упал» — в нём была ошибка, релиз дальше не идёт.
В «Амазоне» я работал над проектом «Гифт ресивер». Идея такая: тебе приходит подарок с «Амазона», и я как отправитель могу либо прислать тебе ПДФ-файл, либо распечатать листочек, на котором написано: «Вот, пожалуйста, твой подарок. Если подарок тебе не понравится, ты можешь перейти по ссылке и вернуть его. Я об этом ничего не узнаю». Проект был направлен на то, чтобы люди не боялись дарить подарки и меньше выкидывали вещи, которые им не нужны.
Это небольшой проект, всего около восьми страниц на сайте. А UI-тестов было около 200. Конечно, можно сделать один тест, который проверяет все сценарии. В ходе такого теста человек зашёл на сайт, прошёл по всем страницам, сделал всё, что ты можешь представить, и вышел. Такой тест нужен один: если он сломается, не так трудно будет понять, что именно сломалось. Если ещё на сайте что-то изменится, этот тест будет довольно сложно поддерживать.
Тест нужно менять и обновлять так же часто, как обновляешь программу. Например, я сделал более красивую кнопочку, а тест мне говорит: «Наша кнопочка не нашлась, ты всё сломал».
Например, я получил подарок и зашёл на страницу заказов. У меня есть несколько кнопок: «Вернуть подарок», «Написать имейл: „Спасибо за такой великолепный подарок!“». Я выбираю второй вариант и пытаюсь отправить ссылку на вредоносный сайт. Это тоже сценарий, который нужно протестировать.
Разработка по поведению пользователя
Существует подход, например, BDD — Behavior-driven development, разработка по поведению пользователя. Сначала мы описываем, что ожидаем от системы, а потом разрабатываем. Например, есть пользователь, который залогинен на сайте. Когда он нажимает на кнопку «Добавить товар в корзину», товар добавляется в корзину.
Этот сценарий можно описать не только алгоритмом, но и естественным языком. Есть система, которая позволяет на английском языке описать сценарий, а потом превращает текст в сценарий программы для тестирования. Чтобы это сработало, разработчикам нужно описать саму систему. Определить, что означают конкретные слова: глаголы, существительные и так далее.
Как правило, программисты сильно страдают и ругаются оттого, что не могут напрямую написать строчку на языке программирования. Им нужно изгаляться: превращать английский язык в код, потом этот код запускать и так далее.
Люди, которые далеки от программирования, с помощью этой системы приготовят сценарий для тестирования, при этом не написав ни строчки кода. Тестировщики могут не понадобиться: их заменят аналитики или другие люди, которые описывают поведение системы на английском языке. На русском языке таких систем нет.
Почему тестировщики иногда ненавидят разработчиков
Представь, что у тебя есть отдел тестирования, ты делаешь релиз, выкладываешь новую версию. При этом у тебя 500 обычных сценариев поведения пользователей. Чтобы их проверить, отдел должен сидеть неделю, две или три. Десять человек распределяют эти 500 сценариев между собой и воспроизводят их. Это делается перед каждым релизом, тестировщики повторяют одну и ту же работу. Получается, что очень много времени уходит на то, чтобы перепроверить то, что может проверить машина.
Ещё одна проблема выглядит так. Тестировщик нашёл ошибку и сказал об этом разработчику. Разработчик пофиксил проблему и изменил код. Теперь тестировщику нужно заново всё проверять: когда программист менял код в одном месте, могло сломаться где-то в другом.
Несмотря на это, отношения отдела тестирования с отделом разработчиков очень тёплые, иначе им невозможно работать друг с другом — они начнут друг друга взаимно ненавидеть.
Что делать, когда прилетают «чёрные лебеди», и как сделать, чтобы они прилетали реже
Самат Галимов: У меня был проект — интернет-магазин iGoods, мы над ним работали во время начала коронавируса. Конечно, тогда мы ещё не знали, что начнётся пандемия. Потом показали послание Путина про то, что мы две недели будем сидеть по домам: работать не будем, денег платить тоже не будут. А ещё нельзя будет ходить в магазины.
iGooods — сервис, в котором можно заказывать товары на дом. К нам пришёл огромный поток людей — в пять или в десять раз больше, чем обычно. У нас лёг сайт.
Расскажи, что такое нагрузочное тестирование и как оно помогает защититься от таких ситуаций?
Максим Садым: Сценарий, который ты описал, называется «чёрный лебедь». То есть произошло событие, к которому никто не был готов. К счастью, «чёрные лебеди» прилетают очень редко. После того как «чёрный лебедь» прилетел, он перестал быть «чёрным лебедем»: мы уже знаем, что такие сценарии бывают.
Приведу пример с «Амазоном». Рождество наступает каждый год, люди покупают подарки. У нас была по этому поводу шутка: существует единственный дедлайн — Рождество. Мы увеличивали количество серверов в 10 раз.
Представь, что на твой сервис пришло в 10 раз больше пользователей, чем обычно. Уверен ли ты, что система выдержит? Уверен ли ты, что можешь добавить какое-то количество дополнительных компьютеров? Уверен ли ты, что они между собой будут корректно взаимодействовать? Уверен ли ты, что не будет проблем, например, с подключением к интернету или с локальной сетью? Чтобы ответить на все эти вопросы, существует нагрузочное тестирование.
Как это работает: мы пишем программу, которая симулирует большое количество реальных пользователей. Как правило, одна машина не справляется с тем, чтобы симулировать нужное количество, поэтому это делают сразу несколько машин. Они приходят в систему, симулируют реальное действие, проверяют правильный результат и замеряют скорость ответа. Если страница отвечает через минуту, скорее всего, пользователя там уже нет.
«Например, пришло 1 000 человек, и они начали складывать запросы в базу данных. База данных не справляется. Мы должны убедиться, что новые пользователи не топят базу ещё сильнее.
В какой-то момент мы должны принять волевое решение и сказать: „Извините, мы не можем сейчас ничего сделать, подождите, пожалуйста“».
Для этого существует load balancers — промежуточные слои между базой данных и сервисом. Load balancer в определённый момент понимает, что время обработки запросов слишком большое, и говорит: «Извините, мы больше заказов не принимаем».
При этом нам нужно убедиться, что сайт корректно отображается пользователям. Существует сценарий логичного правильного поведения: если не получилось, надо повторить — скорее всего, во второй раз получится. В абсолютном большинстве случаев такая логика приведёт к трагедии. Если мы начнём бездумно повторять запросы, нагрузка на базу данных возрастёт лавинообразно.
Например, сервис просит у базы данных: «Покажи мне всех плюшевых мишек на сайте». Если база данных при этом занята, load balancer отвечает: «Извини, у нас всё слишком плохо, мы не справляемся». Но сервис тут же снова говорит: «Нет, ты мне всё-таки покажи мишек». Тогда уже к load balancer будет очень много запросов. В итоге уже начнёт задыхаться load balancer, а не база данных.
Перед каждым большим событием в «Амазоне» мы должны были обязательно запустить нагрузочное тестирование. Мы тестировали, как большое количество пользователей приходит на сайт и начинает возвращать товары, писать благодарности и так далее. Это и правда так происходит, но мы репетировали, чтобы убедиться, что у нас всё работает хорошо.
Зачем нужен инженер по разработке тестов
Перед каждым большим праздником нужно провести нагрузочное тестирование. Этим занимаются разработчики, по крайней мере в «Амазоне». Но в конце концов тестирование сводится к тыканию в кнопочки.
Самат Галимов: Тебе нужно тыкать кнопочки на десятках компьютеров, 1 000 кнопочек в секунду и при этом замерять скорость ответов кнопочек. Это уже не так просто, как семь телефонов на столе.
Максим Садым: В «Амазоне» существует должность SDET — это инженер по разработке тестов. Он не пишет тесты сам — он пишет программы, чтобы другим разработчикам было проще писать тесты для своих программ.
У SDET более «горизонтальное» покрытие. Один инженер по разработке тестов работает сразу на несколько проектов. Он готовит инфраструктуру, чтобы несколько команд могли делать, например, нагрузочные тестирования. Поскольку мы работаем в одном сете технологий, используем примерно одни сервисы и языки программирования, несколько проектов могут использовать одну инфраструктуру для тестов.
При этом программисты сами пишут UI-тесты и могут запускать нагрузочные тестирования. Но чтобы все подготовить и сделать программистам удобно, существует инженер по разработке тестов.
Работа тестировщика в «Гугле» и «Амазоне»: постоянно неисправные компьютеры и хаос-тестирование
Максим Садым: В «Амазон» я пришёл обычным программистом, но так получилось, что больше занимался тестами. Тестов было мало, а я тупой, не понимаю, как всё работает.
А если я не понимаю, как работает, то хочу это зафиксировать. Хочу, чтобы оно работало так же всегда, а дальше мы разберёмся.
Я начал фокусироваться на тестах и в конце концов перешёл в разработку тестов. Один из проектов SDET, над которым я работал, называется «Хаос-тестирование».
Есть такая шутка: не существует цифрового облака, это всегда чей-то компьютер. Это правда. А если это всегда чей-то компьютер, то у него может случиться проблема: сломаться жёсткий диск, выйти из строя процессор или переполниться память.
Самат Галимов: «Гугл» или «Амазон» — десятки или сотни тысяч серверов. При таких объёмах у тебя постоянно что-то в них будет ломаться. В каждый момент времени у тебя есть компьютер, который сломан. Как жить дальше? Я не хочу давать определение хаос-тестированию, расскажи, как ты это видишь?
Максим Садым: Ты правильно сказал. У нас физически постоянно что-то где-то сломано. Вопрос: как мы с этим работаем? Например, если у нас сломался компьютер, но мы продолжаем к нему ходить, значит, пользователь получает ошибку, потому что он не может получить результат.
Более того — иногда, когда мы привязываем пользователя к конкретному сервису, по какой-то причине он будет дальше получать ошибки. Нам нужно убедиться: если физически компьютер сломался, то мы с этим нормально живём.
Представь, что на компьютере по какой-то причине процессор занят на 100% — неважно чем. Пользователь пришёл и говорит: «Дай мне список мишек». А физический компьютер, который стоит где-то в дата-центре, говорит: «Сейчас дам».
По-прежнему у него 100% загрузки процессора — он не может ничего сделать, но он пообещал. Это неправильно. У нас есть такая штука, называется тайм-аут. Через какое-то время запрос должен прекратиться, а мы должны сказать: «Ой, извини, мы не можем. Мы сильно заняты, сходи к кому-нибудь ещё».
Мы должны протестировать, что тайм-аут работает правильно. Нужно убедиться: если память процессора полностью занята, у нас всё работает хорошо. Нужно убедиться: когда процессор освободится, программа не будет выдавать ошибку.
Самат Галимов: Когда ты пишешь программу, Happy path — когда всё нормально идёт — это 10% твоей программы. А остальные 90% работы — описать, что происходит, когда что-то пошло не так. Очень хорошая аналогия — медицина. В ней одна книжка — анатомия здорового человека, а дальше 10 книг о том, что может пойти не так и что в этой ситуации делать. Здесь ровно такая же проблема.
Максим Садым: Ты сейчас идеально описал мою работу.
С одной стороны, список того, что может пойти не так, бесконечный. С другой — у нас уже есть опыт. Мы смотрим, что шло не так, пытаемся предположить, что может пойти не так, и проверяем сценарии.
Хаос-тестирование — довольно сложная технически штука. Его начал делать Нетфликс: они это сделали открытым кодом, теперь кто угодно может его использовать.
В основном хаос-тестирования проводят большие компании, для маленьких это слишком дорого.
«Мы просто пишем программу, которая воспроизводит такие проблемы и делаем большую красную кнопку „Проверить“. Разработчики перед Рождеством нажимают на эту кнопку и смотрят красивые графики».
Юнит-тесты и мутационное тестирование
Проверка небольшой части кода называется юнит-тест. Расскажу мой любимый пример. У нас была проблема: очень небольшой кусок кода, который проверяет, какой квартал сейчас по дате. Что может пойти не так? Первый квартал начался 1 января, второй — 1 апреля, а 1 июля третий квартал не начался. Так получилось, потому что мы закладывали на квартал четыре месяца, делили на четыре. А чтобы всё сошлось, вычитали где-то единицу.
Если бы мы провели юнит-тест, всё тут же бы выяснилось. У тебя четыре квартала в году, ты проверяешь: здесь начинается и здесь заканчивается.
Юнит-тест выглядел бы так. Есть модуль программы, который проверяет квартал. Задача программы — проверить, что такого-то числа начинается первый квартал, такого-то числа второй, такого-то числа третий. То есть ты подаёшь кусочку своей программы на вход дату и проверяешь — он возвращает: «Да, квартал начался».
Если у тебя нет юнит-тестов, ты узнаёшь о проблемах, которые сам создал, слишком поздно. Потом их сложнее решать.
Моя любимая тема — мутационное тестирование. Например, ты написал модуль и юнит-тест для него. Есть метрика coverage — покрытие тестами. Чтобы её измерить, ты запускаешь программу, которая проверяет каждую строчку кода: заходила ли программа проверки в каждую строчку кода или нет.
Грубо говоря, берём весь текст программы и закрашиваем зелёным те кусочки, по которым прошёл тест. Таким образом мы находим кусочки, которые не были покрыты тестами. Эти кусочки не окрашены зелёным — значит, в них может быть проблема, о которой мы не узнаем, потому что тестов там не было.
Мутационное тестирование делает следующее: если мы прошли через строчку кода, это не значит, что мы её проверили. Чтобы стать уверенными, мы меняем эту строчку.
Существует список мутаторов — элементов на подмену. Программа берёт каждую строчку, каждый элемент и меняет один элемент на другой. У тебя был «плюс» — стал «минус», у тебя было «больше» — стало «больше или равно» и так далее. Программа делает мутанта из твоего кода, добавляет по одному мутанту за раз, прогоняет все юнит-тесты повторно. Тесты должны упасть. Если тест не упал — значит, мутант пробрался на базу, всё плохо.
Иногда, если ты поменял «больше» на «больше или равно», ты говоришь: хорошо, это приемлемый риск, я с этим живу. Тогда ты знаешь, что именно ты протестировал. Это сильно упрощает жизнь разработчика: посмотреть на coverage report после своей работы — обязательное дело. Если ты не посмотрел, откуда ты можешь знать, что сделал. Посмотреть на mutation report — это ещё и получить подсказки о том, что ты протестировал или не протестировал.
Что ещё в подкасте
Что такое Test-Driven Development, как сложилась карьера нашего героя и каково было программировать в эмиграции. Слушайте в «Яндекс-музыке».