Русский
Русский
English
Статистика
Реклама

Тестирование it-систем

Перевод Коды ошибок это гораздо медленнее, чем исключения

18.12.2020 16:04:56 | Автор: admin
На современных 64-битных PC-архитектурах использование C++-исключений означает всего лишь добавление к функциям недостижимого кода с вызовами деструктора и ухудшение производительность менее чем на 1%. Такие небольшие ухудшения производительности сложно даже измерить. Обработка редких ошибок с использованием возвращаемых значений требует дополнительных операций ветвления, которые, в реалистичных сценариях, замедляют программы примерно на 5%. Такой подход, кроме того, менее удобен, чем использование исключений. Если выбрасывается исключение, то на раскрутку каждого кадра стека тратится примерно 2 мкс.



C считается самым быстрым языком программирования. В C++ есть возможности, которые лишь повышают удобство работы, не влияя на производительность, в сравнении с C, и возможности, которые на производительность влияют. Эти возможности очень помогают в деле улучшения качества кода. В результате ими, несмотря ни на что, достаточно часто пользуются. Полиморфизм времени выполнения это буквально вездесущая возможность, а вот исключения распространены меньше.

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

Но как велико это воздействие на производительность? На большинстве современных 64-битных платформ исключения реализованы так, что системные ресурсы на них практически не тратятся в том случае, если они не выбрасываются. В сгенерированных функциях нет проверок на предмет выброшенных исключений, выполнение кода, при обработке исключения, переключается на специальные функции и специальные данные. Но нельзя сказать о том, что использование исключений вообще никак не сказывается на производительности. Иногда возникает необходимость в обработке ошибок, возникающих очень редко. Один из возможных вариантов решения подобной задачи заключается в полной остановке программы. Из-за этого на диске остаются не полностью сформированные данные, что ведёт к очень неприятным впечатлениям, которые испытывают пользователи соответствующих программ. Например, по такой схеме работают Unreal Engine и Unity Engine. Там неправильное использование API в коде приводит к аварийному завершению работы редактора. Он будет продолжать останавливаться до тех пор, пока вручную не будут удалены некорректные бинарные файлы. Ещё один вариант решения задачи обработки ошибок представляет собой обработку кодов ошибок. При таком подходе функции сообщают о том, что не могут нормально работать. Предполагается, что код, вызывающий такие функции, может адекватно отреагировать на подобные сообщения. Это не очень удобно для программистов, так как требует выполнения дополнительных проверок после возврата из функции, но часто этот подход используется из соображений, связанных с производительностью.

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

В каких ситуациях не стоит использовать исключения?


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

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

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

В современных 64-битных архитектурах используется модель zero-cost exceptions (исключения нулевой стоимости). При таком подходе обработка ошибок с применением исключений оптимизируется исключительно в расчёте на правильное выполнение программ, когда исключения не выбрасываются. Делается это ценой очень низкой производительности обработки выброшенных исключений.

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

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

Тест 1: парсинг XML-данных


Для проведения этого теста я написал XML-парсер. Я решил написать именно парсер, так как подобная программа может дать сбой во многих ситуациях и не зависит от подсистемы ввода/вывода. Эта программа, определённо, не рассчитана на соответствие стандартам, не гарантирован её отказ при встрече с любыми ошибками. Но она может разбирать обычные конфигурационные XML-файлы и должна завершать работу с ошибкой в большинстве случаев, когда файл синтаксически некорректен. Её код представлен низкоуровневыми конструкциями и должен быть достаточно быстрым (в районе 150 МиБ/с), но я его не оптимизировал и использовал STL-контейнеры для повышения удобства работы с ним (в противоположность применения прямого парсинга). Я написал эту программу с множеством проверок #ifdef, применяемых для переключения между вариантами программы, в которых используются исключения, коды ошибок и остановка при возникновении ошибки. Управление этим всем осуществляется с помощью аргументов компилятора. В результате различие между разными вариантами программы будет заключаться лишь в том, какой именно механизм обработки ошибок используется в конкретном варианте.

Я испытал производительность этой программы с использованием XML-файла, имитирующего типичный конфигурационный файл какой-нибудь компьютерной игры. Он имеет размер 32 КиБ и полностью загружается в память перед началом испытания. Процедура парсинга файла повторяется 10000 раз, данные по длительности выполнения этой операции усредняются. Это повторяется 10 раз для того чтобы обеспечить погрешность измерений, не превышающую 1%.

Код скомпилирован с использованием GCC 9 на Ubuntu 20.04. В компьютере, на котором выполнялись измерения, установлен процессор Intel i7-9750H, его максимальная частота в однопоточном режиме составляет 4,5 ГГц. Я запускал все варианты теста один за другим, ничего не делая между запусками. Поступил я именно так для того чтобы добиться одинакового влияния на мои тесты использования кеша другими программами. Но даже при таком подходе в полученных мной данных были такие, которые сильно выбивались из общей картины. Я от них избавился.

Та версия программы, которая останавливалась при возникновении ошибки, была столь же быстрой, как та, в которой использовались исключения. А вот версия, в которой использовались коды ошибок, оказалась на 5% медленнее.

По каким-то причинам, в том случае, когда ошибки обрабатывались специальной функцией, которая выводит сведения об ошибке и завершает программу, это приводило к небольшому (примерно на 1%) падению производительности в сравнении с версией программы, в которой применяются исключения. Мне понадобилось использовать макрос для того чтобы сделать результаты подобных измерений сравнимыми со скоростью кода, использующего исключения. То же самое повторялось и в других тестах.

Тест 2: заполнение структур данных классов разобранными XML-данными


Для этого теста я подготовил несколько классов, которые должны были представлять структуры XML-файла. Я написал код, в котором выполняется заполнение структур данных этих классов с использованием распарсенных XML-структур. Этот код был примерно в 10 раз быстрее, возможно из-за того, что тут было гораздо меньше операций динамического выделения памяти.

Различия в измерениях, выполненных для кода с исключениями и для кода без обработки ошибок, укладываются в допустимую погрешность измерений, но на выполнение кода с исключениями ушло на 0,6% больше времени. А вот вариант программы, в котором использовались коды ошибок, был на 4% медленнее. С похожим замедлением я сталкивался в тех случаях, когда забывал пользоваться семантикой перемещения.

Тест 3: чтение данных из бинарного потока и обработка сообщений разных типов


Этот тест имитирует использование асинхронного API для чтения данных из TCP-сокета (вроде Boost Asio или Unix Sockets). Подобные API используются так: из потока читается некоторый объём данных, потом эти данные обрабатываются, а потом читается новая порция данных. Для повышения скорости работы программы и для того, чтобы в ходе теста приходилось бы передавать меньше информации, данные представлены в бинарном виде. Так как в играх сетевые данные передаются постоянно, ожидание конца потока нецелесообразно.

В моём API применяются сообщения трёх типов, указывающие на различные изменения в игре. Эти сообщения имеют разную длину. Поэтому при обработке данных из потока нельзя точно узнать о том, всё ли сообщение было в нём передано. В результате функция, которая идентифицирует сообщения и вызывает соответствующий код для их парсинга, часто даёт сбои. Происходит это даже в том случае, когда система работает в штатном режиме. Это значит, что для обработки подобных ошибок исключения использовать нельзя. Другие ошибки обрабатываются с помощью исключений (в том варианте программы, где используются исключения). Это появление сообщений, тип которых определить не удаётся, неверная идентификация объектов, необычно сильные изменения значений (это либо последствия действий читеров, либо признак повреждения данных)

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

Результаты этого теста похожи на результаты предыдущих испытаний. Код, в котором для обработки использовались исключения, на 0,8% медленнее, чем код, который просто останавливается при возникновении ошибки. Это значение тоже находится в пределах допустимой погрешности измерений. А вот код, в котором применяются коды ошибок, оказался медленнее на 6%.

Результаты


Результаты тестов сведены в следующую таблицу. За 100% приняты результаты тех вариантов кода, которые просто останавливаются при возникновении ошибки.

Тест Остановка выполнения Выбросисключения Возврат кода ошибки
Парсинг 100% 100% 106,2%
Заполнение структур данных 100% 100,6% 104,2%
Чтение сообщений 100% 100,8% 106,2%

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

Исходный код программы можно найти здесь.

Обработка ошибок и чистый код


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

Вот строка из сектора инициализации конструктора, использованного в тесте 2:

animation(*source.getChild("animation")),

Она осуществляет перенаправление дочернего XML-тега animation из её аргумента в конструктор члена класса animation. Конструктор может дать сбой из-за некорректного содержимого XML-тега. Сбой может дать функция getChild, что может случиться из-за отсутствия всего тега. Подобные ошибки прерывают создание структуры, они могут помешать и ещё каким-то процессам в коде, находящемся в блоке catch.

Если сведения об ошибках передаются через возвращаемые значения (или через выходные аргументы), то код будет выглядеть примерно так:

std::shared_ptr<Xml> animationTag;auto problem = source.getChild("animation", animationTag);if (problem)return problem;problem = animation.fromTag(animationTag);if (problem)return problem;

Этот код, используя макросы, можно сократить (обычно лямбда-выражения могут заменить макросы, но не в этом случае):

std::shared_ptr<XML> animationTag;PROPAGATE_ERROR(source.getChild("animation", animationTag));PROPAGATE_ERROR(animation.fromTag(animationTag));

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

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

У написания кода таким способом нет преимуществ. Увеличение длины программы усложняет логику. В коде появляются дополнительные объявления, выходные аргументы, макросы. Ошибку можно случайно проглядеть. Этот подход не даёт нормально пользоваться идиомой RAII. И код, в любом случае, из-за большого числа ранних возвратов, должен быть устойчив к исключениям.

Другие результаты


В режиме отладки использование исключений заметно воздействует на производительность. Замедление составляет примерно 2%. Но это всё равно быстрее, чем вариант, в котором используются коды ошибок.

Добавление в код множества дополнительных (ненужных) блоков try тоже плохо сказывается на производительности. Возникает такое ощущение, что за снижение производительности версий тестов, в которых используется обработка исключений, ответственны именно блоки try. Но эксперименты это не подтвердили.

Отключение RTTI при использовании исключений не оказало заметного влияния на производительность блоков try (в этот раз объект исключения, который нужно было перехватить с помощь catch(), был недоступен, и сообщение об ошибке надо было сохранить в переменной thread_local).

Если же исключения, и правда, выбрасываются, то результаты уже сильно отличаются друг от друга. Обработка исключения занимает примерно 2 микросекунды на каждую функцию, из которой осуществляется выход. Это немного, но эквивалентно замедлению, вызываемому примерно десяти тысячам вызовов функций, в которых используются коды ошибок. В результате использование исключений неэффективно в том случае, если вероятность того, что они будут выброшены, по грубым оценкам, превышает 0,01%. Это не должно оказывать сильного воздействия на код, где используются исключения. Ведь цель разработчика заключается в том, чтобы обеспечить быстрое выполнение программ при их правильном использовании. Хотя программы должны завершаться корректно, нет нужды оптимизировать те их части, которые отвечают за обработку ошибок.

Использование исключений увеличивает размеры исполняемых файлов. Так, размер исполняемого файла тестовой программы, в котором используются исключения, составляет 74,5 КиБ. А файл, в котором исключения не используются, имеет размер 64 КиБ. Если ещё отключить RTTI, то размер файла уменьшается до 54,8 КиБ. Я не изучал вопрос о том, что именно вызывает подобные изменения, но уже само изменение размеров файла говорит о том, что при подготовке исполняемых файлов для встраиваемых платформ, ресурсы которых ограничены, может понадобиться отключить исключения.

Я, кроме того, проанализировал сгенерированный компилятором код с помощью Compiler Explorer. Включение исключений не меняет тела функций (то есть не вносит в код дополнительного ветвления или дополнительных возвращаемых значений). Но функции оканчиваются блоком кода для обработки исключений, который обычно недостижим (блоки try тоже обычно входят в состав недостижимого кода). Этот код вызывает деструкторы и возвращает управление функции обработки исключений. Этот код, хотя он и не выполняется, занимает кеш (это похоже на ранний возврат из функции). Этот код не генерируется для функций, которые не выделяют память в стеке для чего-либо, использующего деструкторы, или для функций, помеченных как noexcept. В результате очень важный код, сильно влияющий на производительность, которому не нужно обрабатывать ошибки, можно оптимизировать с помощь noexcept. А если он использует что-то такое, что может выбросить исключение, оптимизировать его можно, избегая выделения памяти в стеке под объекты с деструкторами (но я не изучал вопроса о том, быстрее ли C++, используемый как C с исключениями, чем C).

Итоги


Ошибки удобнее всего обрабатывать с использованием исключений. Я протестировал воздействие разных механизмов обработки ошибок на реалистичных примерах. На 64-битных архитектурах включение использования исключений приводит к замедлению кода примерно на 1% в сравнении с кодом, который просто останавливается при возникновении ошибки. Коды ошибок, обычная альтернатива исключениям, используемая для обработки ошибок, после возникновения которых работу можно продолжить, снижают производительность примерно на 5%. В результате отключение обработки исключений в программах, рассчитанных на PC-архитектуры, не только вызывает неудобства, но и, вероятно, плохо влияет на производительность.

Как вы обрабатываете ошибки в своих C++-проектах?



Подробнее..

Из тестировщиков в агенты изменений департамента путь в 10 лет и два выгорания

22.12.2020 10:22:28 | Автор: admin
image

Хабр, привет! Меня зовут Ася, я ведущий инженер-тестировщик (QA Lead) в КРОК.

Недавно я отметила десятилетний юбилей в компании и в тестировании одновременно да, за столько лет мне не надоело ни там, ни там. Хотя насчет тестирования было по-разному успела даже дважды выгореть (один раз из-за декрета), стать контрол-фриком и влюбиться в профессию заново.

Про профессию тестировщика часто слышу, что это самый легкий и быстрый порог входа в ИТ а там и на разработчика переучиться можно. Я же наоборот училась на разработчика и даже успела им немного поработать, но душа к этому не лежала, потому что искать баги намного веселее. За всю мою карьеру я участвовала в совершенно разнообразных проектах: документооборот, файлообменники, статистические наблюдения, обработка обращений пассажиров в ЦППК, учет оборудования. А потом поняла, что этот опыт можно масштабировать на свою жизнь и даже на работу целого департамента. Так я стала агентом изменений департамента разработки программного обеспечения (ДРПО).

В этой статье хочу рассказать про свой путь и постараться ответить на вопрос, который мучает многих тестировщиков а есть ли жизнь на Марсе задор и челленджи после многих лет в тестировании?

Как технарское во мне победило гуманитарное


Вообще после школы я готовилась стать гуманитарием, но обстоятельства сложились так, что я стала технарем пусть и не с первого раза. Мои родители учились в МГУ, и в старших классах я не могла даже допустить мысли, что буду учиться где-то в другом месте. Но мама крайне не советовала мехмат (Ася, девочкам там очень тяжело), и тогда я стала выбирать себе факультет по принципу от противного. Я точно не хотела связываться с физикой, историей, биологией, химией так методом исключения остался филфак (с русским у меня всегда было хорошо), я начала готовиться к поступлению на прикладную лингвистику.

Сочинение 4, математика 4, английский 2 Естественно, не прохожу, а в другие вузы даже пытаться не стала я хотела только в МГУ. После школы я нигде не работаю и готовлюсь к поступлению. И вот, следующий год, МГУ, все тот же филфак. Сочинение 3, математика 5, английский опять двойка

За год мое желание учиться только в МГУ немного поутихло, и я подобрала себе несколько запасных вариантов, одним из которых был МИСиС, в котором среди специальностей про сплавы и металлургию я нашла нейтральную прикладную математику. У меня, вопреки стереотипам про гуманитариев, с математикой всегда было так же хорошо, как с русским. В итоге этот запасной вариант выстрелил после нового фейла с МГУ так я стала студентом кафедры инженерной кибернетики. Как я потом узнала, девочки на прикладной лингвистике ездили летом на практику в деревни Мордовии, изучали мордовские диалекты и составляли частотные словари. Сейчас я думаю: Деревни Мордовии, частотные словари вообще какая-то параллельная Вселенная.

Как я фиксила баги собеседований и дофиксилась до тестировщика


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

Свою первую работу в ИТ я получила в 2008 году опыт этот длился где-то полгода, пока мою компанию не накрыл кризис, а вместе с ним сокращение штата. По трудовой я
была инженером-программистом, по факту системным аналитиком, а на самом деле занималась всем подряд и ничем одновременно. Из того что помню: я постоянно обновляла календарный план, составляла план работ проходил месяц, и я снова обновляла план работ. Работа была дурацкая, но что в ней было хорошо (кроме того, что я поняла, что вау, за мои знания можно получать деньги!) это ощущение взрослости и реальности того, чему я учусь. Я начала понимать, как на самом деле создается программное обеспечение, что есть заказчики и как проходят с ними встречи, что в команде с разработчиком работают еще аналитики.

Погуляв и отдохнув летом после увольнения, я перешла на 5 курс и пошла искать работу. Я была профоргом в группе, покупала на всех проездные (московские студенты середины 00-ых, вы помните такое?) в один из таких разов я наткнулась в профкоме на карьерную брошюру, где были неплохие советы о составлении резюме. Так появилось мое первое резюме и первое письмо работодателю.

image

Его я вывесила на hh.ru и сайт для студентов, которые ищут первую работу (он еще был в каком-то желтом дизайне). Когда готовила пост, решила немного окунуться в ностальгию и посмотреть, на что откликалась у меня почему-то отложилось, что я искала только вакансии разработчиков. Но оказалось, что я была готова работать и в техподдержке, и инженером-математиком. Мне важно было попасть в ИТ а кем и куда, было не важно. И хотя у меня был уже опыт (т.е. я вроде уже попала туда), человеком с опытом я себя не ощущала, поэтому по-прежнему считала себя начинающим. Смотрю и завидую даже немного нынешним джунам за нами тогда компании не охотились, в тележке, Хабре и Гитхабе не выискивали :))

image

Один из моих откликов выстрелил это была вакансия тестировщика в Яндексе. Кто такой тестировщик, я не особо понимала, но это казалось чем-то очень простым и доступным ну просто проверяет он и всё, что тут такого. В качестве тестового задания мне предложили протестировать функцию я составила много тест-кейсов, мама не горюй. И вот я отправляю свои варианты проверок, а мне в ответ: Вы еще не все проверили. Сажусь еще раз думать, что-то еще из себя выжимаю. И мне отказывают если честно, гештальт до сих пор не закрыт. Делала скрины для статьи с закрытыми глазами :)
Мое первое тестовое
image
image

А вот такой был ответ от Яндекса:

image

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

Собеседовалась я на стажера-разработчика все прошло нормально, я решила ситуационные задачки, ответила на вопросы. Но меня не звали. И потом отказали. Тогда я набралась смелости и попросила фидбэк, чтобы понять, как могу усилиться на рынке труда (осторожно, в письме сквозит темное прошлое с мыслями о филфаке). В общем, мне повезло мне перезвонили и пригласили на позицию стажера-тестировщика. Вот так корректировка бага собеседования привела меня в мир тестирования.

image

Взрослая работа и первые ошибки


1 декабря 2009 года был мой первый рабочий день в КРОК, но на серьезный проект меня поставили только в феврале. Мы писали софт для Всероссийской переписи населения 2010 года. Одним из условий работы на нем была необходимость сидеть всей командой в одной комнате. Не помню точно, сколько нас было кажется, почти под 50 человек. Это был кайф кайфный, даже жалею, что такого опыта больше пока что не случилось теперь команда не то что по комнатам-этажам раскидана, но даже по городам и странам. Кто бы что ни говорил про возможность работать из-под пальмы, но работать бок-о-бок непередаваемо-вдохновляющие ощущения.

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

Тестировщиков (включая меня) было 6, один из них тест-лид. Тогда у нас не было никакого скрама, было что-то вроде MSF мы писали большие и полные тест-кейсы, вели всю документацию. В тестировании старались дать каждому тестировщику отдельную подсистему, но бывало, что вместе проверяли одну. У нас был календарный план, и мне почему-то помнится, что у нас был каждый месяц релиз. Про проект, кстати, подробно рассказывали на Хабре, вот здесь можно почитать.

Меня очень вдохновляло то, что я делаю. Ведь результатом моей работы будет то, что несколько тысяч людей, которые будут работать с нашими подсистемами, смогут выполнять свою работу хорошо, значительно сэкономив свое время. И смогут собрать результаты Всероссийской переписи населения, и наверное, разработать какие-то полезные законы. В общем, в то время чувство ответственности зашкаливало, ошибки пропустить просто нельзя. Считаю нереальным везением, что удалось попасть на такой проект в начале карьеры мне кажется, что для молодого спеца особенно важно понимать, что результат работы можно пощупать, что он реально приносит пользу, а ты не просто что-то тестируешь (как я думала, когда шла в Яндекс). Если этот пост читает кто-то из джунов, мой вам совет идите в страшные проекты, это реально лучшее, что вы можете сделать для своего профразвития. Кстати, пока вспоминала первые месяцы работы, прокручивала в голове факапы, и кажется, собрала комбо. Поделитесь в комментах, какие у вас были кейсы?

У меня было вот так:
  • проверяла сначала негативные кейсы, не проверив позитивные
  • проверяла по настроению, без системы проверок (чтобы быть уверенным, что ты выполнил полное тестирование и ничего не забыл. с этим я разберусь позже, когда займусь своим образованием)
  • проверяла числовое поле, вводила туда буквы, а они не вводились. А потом оказывалось, что с помощью Ctrl+C-CTRL+V буквы в числовое поле очень даже прекрасно вводились:)
  • была и совсем дичь: проводя функциональные тесты через UI, не смотрела что там происходит в базе данных, как вся информация сохранилась.

Я подмастерье vs Я наставник


В КРОК развита система наставничества у каждого нового сотрудника есть человек, который помогает ему адаптироваться в компании в целом и в рабочей команде. Ну и вообще в любом профразвитии без наставника никуда :) Как правило, им логично становится наиболее опытный член команды. Сейчас я вспоминаю и понимаю, что мой наставник меня редко хвалил и как-то редко был доволен моей работой в общем, суровый тестировщик. Я из тех людей, которых мотивирует не злость от того, что он получил тройку (уу, ну я сейчас всех победю!), а пятерка с минусом. Мой наставник же действовал от противного (почему я об этом не говорила вопрос хороший) он был очень дотошный (что не удивительно для тестировщика) и делал акценты на том, что у меня не выходило, объяснял, как это можно исправить или не допустить в следующий раз. Но зато его первую похвалу я помню до сих пор. Я написала тест-кейсы для одной подсистемы и он сказал: Идеально, отличная компоновка кейсов, всё логично, проверки хорошие. Всё здорово. Видимо, меня это так поразило, что я помню это до сих пор. И тогда же я поставила себе галочку- поняла, что могу, что я не потеряна для тестирования:)

Когда я сама стала наставником, мне выпал шанс закрыть все профгештальты за себя и за того парня, как говорится. Я тогда только стала тест-лидом, к нам на проект пришел молодой человек из другого отдела. По факту, это была я несколько лет назад все, что он знал про тестирование, было ошибки есть, их не может не быть (Дима, если ты читаешь сейчас эту статью, а я надеюсь, что ты читаешь, я передаю тебе огромный привет!)

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

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

Порефлексировав, я поняла, что этот опыт, кроме радости воспитания нового спеца дал мне еще вот что:

  • я изменила свой подход к работе иначе стала смотреть на тщательность у нас появились Definition of Done. Если я раньше считала нормой что-то вроде Умри, но найди все ошибки, будь мастером придирчивости и дотошности, то я сейчас я выстраиваю свои проверки ровно с ожиданиями и договоренностями, не пытаюсь перепрыгнуть через свою голову.
  • умею определять критичные компоненты проекта
  • стала более заботливым специалистом я лучше понимаю, как себя ощущает новичок в карьере, развила профессиональную эмпатию.

Тестировщик без базы быть или не быть


После 1,5 лет работы подмастерьем я задумалась о профильном образовании университетские знания совсем не пригодились. Ну, разве что при тестировании методом белого ящика я читала код и понимала, что там написано (как никак, мы разные языки программирования в универе изучали). Но справедливости ради добавлю, что именно математическая база мне точно помогает как минимум, комбинаторика каждый день используется. В нагрузочном тестировании нужна теория вероятности, всякие там перцентили, когда анализируешь результаты и структурируешь. Еще теория случайных процессов наличие этой базы мне позволяет структурировать свои проверки по процессам, где есть статусы, переходы из статусов, длинные пути, циклы внутри пути.

Я была тестировщиком без базы и хотя работала я хорошо (пусть и под странным девизом найди все, что не так, а как найдешь не важно), понимала, что мне не хватает системы, чтобы как-то оптимизировать хаотичный набор моих действий. Помню, когда впервые узнала о существовании тест-дизайна, классов эквивалентности, граничных условиях, Pairwise и такая: аааа, так вот как надо!

В попытках образоваться я прошла несколько курсов на software-testing, но если честно, они были слегка мимо меня, не могу сказать, что было полезно. Но зато я очень оценила (и всячески рекомендую) их Школу тест-менеджеров она все супер разложила по полочкам и упорядочила мой огромный практический опыт. На этом курсе было 8 вебинаров учили оценке трудозатрат, целям тестирования, техникам тест-дизайна, какие метрики собирать. Тот курс меня так зарядил, что я потом все то время, что работала тест-лидом, продолжала распространять свои знания и понятия о прекрасном тестировании, как надо и как не надо. Совершенно точно после курса про тест-менеджмент я стала осознанным тест-лидом, проактивным, а не реактивным, стала вести статистику.

Как бы все сложилось, если бы у меня была база? Хочется дать ответ, что с ней мое развитие пошло бы быстрее. Но ведь не факт. Сейчас, когда провожу много собеседований, вижу, как многие прошли курсы по тестированию, сыпят всякими словами вроде: ооо, я провожу смоук тестирование, оооо, тестирование черного ящика. Но обширная теоретическая база часто означает недостаток практики. У меня же было полно практики, и скорее, когда я училась, то примеряла свои примеры из работы на ту теорию, которую рассказывали, и сразу представляла, что и где могла бы применить, или как стоило по-другому построить кейсы.

Еще одним мощным рывком в моем профессиональном развитии стал мой первый проект в роли тест-лида все потому, что я люто накосячила и узнала реальную цену ошибок. А также, что вообще есть такие понятия, как Severity и Priority, что может быть блокирующий дефект, но который не нужно исправлять в первую очередь. А есть критический и вот им как раз нужно заняться самым первым. С тех пор я с первых дней работы на новом проекте определяю, что точно должно работать в любых обстоятельствах, количество пользователей, кто они, на что это может повлиять. Больше рассказать не могу :)

image

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

image

На фотке: наша командная магнитная доска в комнате, свободная зона для творчества:) Мистика в кубе, объезжаемая на сивой козе. Эта фраза очень хорошо описывала одну ситуацию на том проекте :)

Два выгорания за 5 лет


В 2016 году я была в профессии семь лет стала крутым тест-лидом и любила свою работу даже слишком. Только спустя время я поняла, что это было выгорание, а не преданность делу. Я не психолог, но, возможно, мой опыт будет полезен выгорание не всегда проявляется только усталостью и апатией. Что было у меня:

  • я стала контрол-фриком я знала абсолютно все о каждой детали проекта и ни на секунду не давала себе и окружающим расслабиться
  • я повернулась на идеальности ошибок быть не могло, нужно было делать на 101% сразу, другой результат я от себя не принимала
  • я думала только о работе я просыпалась с почтой и засыпала с почтой. За ужином дома я говорила только о работе. Тогда я ждала первого ребенка, но даже мысли о таком крутом изменении в моей жизни не отвлекали меня от задач на проекте. Я даже год в декрете заходила в почту каждый день, чтобы проверить, не развалилось ли там без меня все.

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

Я вернулась в офис незадолго до пандемии успела побыть в старых условиях буквально пару месяцев. Меня немного подотпустило оказаться в привычной среде было приятно, а заодно выйти на один из самых крутых проектов в моей карьере (он пока под NDA, но если будет можно, расскажу об этом в отдельном посте).

Но чувство, что я попробовала в тестировании уже все, что можно, не отпускало. Ирония в том, что идти по классическому пути развития тестировщика в разработчики, большие руководители, бизнес-аналитики мне не хотелось. Мне нравилось (и нравится до сих пор) тестирование просто был нужен новый челлендж, что-то совсем необычное.

Несколько месяцев назад я поняла, что еще больше, чем тестированием, мне хочется заниматься улучшением процессов, поиском проблем в процессах именно в проектах и командах. Так я стала агентом изменений в департаменте разработки программного обеспечения (ДРПО) помогаю командам стать адаптивными.

Что это за зверь такой агент изменений?


Начну издалека.

В феврале 2020 года (еще до всего) я сходила на очный тренинг по Scrum. Очные тренинги, кто помнит такое?:) Я и до этого давным давно слышала про всякие стори-поинты, спринты, дейли скрам, но применять на проектах не получалось то ли знаний не хватало, то ли еще чего-то. А тут получилось и информацию новую получить, и сразу же обкатать в работе в группах.

Как был устроен тренинг: после каждого часа теории мы разбивались на группы для отработки навыков на практике. Задания на первый взгляд могут показаться очень легкими (например, нам раздали бумажки и предложили найти соответствие между ценностью Agile и ее описанием), но здорово помогали в игровой форме закрепить информацию. А еще была понятна структура вот 5 событий, у каждого есть таймбокс, присутствующие люди, цель, артефакты на входе и выходе.

image

На фото самая впечатляющая часть тренинга. Города + Lego = любовь. С каждым раундом мы понимали, что лажаем и лажаем но к третьему выровнялись и получилось круто.

После двухдневного тренинга я вернулась к себе в проект вся заряженная, с воплями и предложениями. А давайте начнем проводить дейли? А давайте начнем проводить ретроспективы? Понемногу процесс пошел, но мы еще не были скрамом. Потом в мае в команде КРОК появились Agile-коучи, и на первую встречу-знакомство я пришла, вооруженная кучей вопросов про ретроспективы. В середине сентября мне рассказали про идею агентов изменений в нашем департаменте, и я с радостью туда вписалась. Почему? Потому что еще два года назад, когда я была в декрете и грустила, я думала: А что же я люблю делать? Что же у меня хорошо получается? Кем же мне работать? Ответив самой себе на эти вопросы я поняла, что мне очень нравится вдохновлять людей какой-то идеей. Когда я сама чем-то проникаюсь и ко мне спускается инсайт, мне хочется как можно скорее им поделиться со всеми доступными мне людьми :)

Я сразу же побежала к своему РМ (ресурс-менеджеру) и поделилась, что узнала про такую инициативу и мне туда очень нужно, потому что это мое на все 100%. РМ, конечно, расстроился, ведь это значило потерять ведущего тестировщика в команде, но поддержал меня в этом решении принуждаловки у нас нет, внутри всегда поддерживают желание сотрудника менять вектор развития своей карьеры, даже если он такой неожиданный и нестандартный.

Как появилась идея агентов изменений? Честно говоря, не знаю всей предыстории, потому что ворвалась в проект не на этапе идеи, но расскажу, как я понимаю нашу мотивацию сейчас.

Три главных человека у нас в департаменте решили, что будущее точно за гибкими методологиями, они в них верят. И это значит, что команды должны быть точно такими же адаптивными и самоорганизующимися. Почему? У адаптивных команд никогда не бывает ситуации, что на почту пришло письмо про проблему на стенде, а потом ее каждый час дергают: ну что, получилось? А кто ответственный? А какие сроки? В адаптивных командах люди сами нацелены на решение проблем, им важно не просто завершать свои задачи, а работать как единая команда, совершенствовать процессы внутри себя и им для этого не нужен менеджер, который придет и скажет: С сегодняшнего дня на дейли отвечаем на три вопроса! Команды САМИ решают проблемы, потому что они сложившийся коллектив с большой степенью осознанности.

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

Если кратко, то вот результат, к которому мы стремимся:

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

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

Из ближайших целей мы хотим, чтобы про нашу инициативу знали не только в ДРПО (нас около 600 человек сейчас), но и как минимум 1000 человек за пределами департамента всего нас в компании 3000+. Хотим поработать с 15 проектами и оценивать их конкретными измеримыми метриками, а не только на уровне ощущений было-стало.

Сейчас в нашей команде 1 Agile-коуч и 8 агентов изменений. Мы еженедельно собираемся на синхронизации, ведем общую доску активностей в Trello с нашими общими целями и целями каждого агента изменений. Работаем по спринтам, в конце спринта анализируем, что удалось, а что нет.

Что мы делаем?

Мы приходим в проект, из которого поступил запрос, и начинаем исследование посещаем несколько проектных встреч, где наблюдаем, как проходит дискуссия, вокруг чего она строится, как команда общается друг с другом на встрече. Затем мы проводим интервью с каждым членом команды и готовим перечень проблем с пояснением и доказательствами того, как она связана с конкретными потерями (чаще всего это потеря времени, см. бережливое производство, оно же Lean). По каждой проблеме мы даем рекомендации какие Agile-инструменты могут помочь ее решить., которые бы помогли проработать эти проблемы. Рекомендации это что из Agile-инструментов (есть вот такая громадная карта инструментов от Deloitte, часть из них мы используем).

image

Примеры конкретных рекомендаций практики из Kanban Maturity Model, которые помогают команде перейти с одного уровня зрелости на следующий, какие-то события из Scrum, своевременная обратная связь друг другу.

В одном проекте мы раньше сроков выкатили финальный релиз, причем не увеличивая команду просто по-другому распределили работу. Стали по-другому проводить демо заказчику: раньше просто показывали то, что сделали за спринт, фиксировали замечания, отвечали на вопросы. А теперь мы стали еще спрашивать обратную связь как им вообще результат. И оказалось, что заказчики не только про замечания и доработки они еще бывают в восторге. Это очень сильно стало мотивировать команду на работу над следующими спринтами. Как будто тебе не только двойку в дневник поставили, но и пятерку туда за домашку застолбили:) А еще мы изменили нашу доску в Jira, стали пользоваться там Kanban-доской и добавили фильтры по имени каждого человека, добавили визуальное отображение заблокированных задач (а раньше это никак не было видно, нужно было держать в уме). Вся проектная команда перестала забивать на доску, и доска в Jira зажила активной социальной жизнью. А еще придумали Trello-доску для организации работы с другими компаниями, которые тоже были задействованы в этом проекте.

Сейчас я получаю огромный заряд энергии и вдохновения от того, чем занимаюсь я провожу много встреч и вижу, как они влияют на людей, сколько инсайтов с них они уносят. Тестирование перестало меня зажигать так, как раньше. Да, я по-прежнему люблю улучшать мир, но нашла себе более актуальное применение, где количество энергии (ресурса) у меня в разы больше.

Мне кажется, что такое развитие тестировщика не исключение из правил. Знаю, что многие тестировщики уходят в менеджеры проектов, в скрам-мастера мне кажется, что тестирование и обеспечение качества (QA) (кстати, интересно, какое количество в мире холиварных статей на тему QA, QC и просто тестирования существует:)) очень близки по духу скрам-мастерам и агентам изменений. Я бы сказала, что агент изменений это что-то вроде скрам-мастера, только он помогает настроить вообще все, что угодно, и при этом нацелен на осознанность.

***
Закончить пост хочу вопросами.

Тестировщики, за что вы любите свою профессию и что вас держит? Как начинали свой путь? Если развиваетесь в автотестах тоже очень интересно узнать, как к этому пришли? (кстати, мы сейчас расширяем команду QA Automation инженеров детали тут). Если вы ушли из тестирования, то что делаете сейчас и делает ли вас это счастливыми? Если вернулись, то почему?

Моя почта для связи AnLivenskaya@croc.ru. В комментах тоже буду рада пообщаться :)
Подробнее..

VirtualBox Запуск Android эмулятора в виртуальной среде для тестирования Android проекта

19.12.2020 00:19:44 | Автор: admin

Введение

В данной статье я постараюсь описать пример инфраструктуры для автотестов Android приложений (mobile automation), а именно, среду для проведения тестранов UI автотестов на эмуляторе Android девайса в виртуальной среде.

Требования:

Для Android эмулятора нужна поддержка Intel Virtualization Technology или AMD Virtualization. Поэтому часто тестировщик сталкивается с необходимостью запуска тестранов только в нативной среде ПК с прямым доступом к центральному процессору.

В этом случае схема получается такая:

Трудности:

  1. Невозможно легко пересоздать среду эмулятора.

  2. Среда не создаётся перед проведением тестирования, и после проведения не удаляется, поэтому среда может влиять на тестируемое приложение.

  3. Починка и настройка среды занимает много времени.

Предлагаемое решение в данной статье:

  1. Создать VM с использованием возможностей nested virtualization VirtualBox (более подробное описание технологии в этой статье).

  2. Пробросить поддержку Intel-VT или KVM внутрь созданной виртуальной машины.

  3. Изнутри VM создать и запустить Android эмулятор девайса.

  4. Провести тестран UI тестов приложения.

  5. После проведения тестирования уничтожить VM.

В этом случае схема получится такая:

Предполагаемые преимущества:

  1. VM можно автоматически создавать перед проведением тестирования, а после окончания уничтожать. В таком случае каждый новый тестран будет проведен в идеально чистых условиях.

  2. Уменьшится время поддержки среды и управляющего ПО, так как не нужно каждый раз руками ничего устанавливать и чинить неисправности инвайронмента.

В настоящей статье будет использоваться оборудование:

  • процеcсор: Intel i5-1035G1

  • память: 12Gb

  • в BIOS включена поддержка виртуализации процессора

  • OC: Ubuntu 20.4

Шаг 1: Установка ПО на нативную OS

Отдельно обращу внимание на управление машиной. Будем использовать протокол VNC для создания удобного удаленного рабочего стола. Протокол универсальный, для Linux, Windows, Mac и т.д.

x11vnc сервер

Установка:

sudo apt-get update #обновляем пакетыsudo apt install x11vnc #устанавливаем x11vncsudo x11vnc -storepasswd <вводим пароль сервера> /etc/x11vnc.pass #создаём пароль в файликеsudo chmod ugo+r /etc/x11vnc.pass #разрешаем использовать файлик с паролем

Запуск с параметрами:

x11vnc -nevershared -forever -dontdisconnect -many -noxfixes -rfbauth /etc/x11vnc.pass

Установка VirtualBox

Вводим в командной строке:

sudo apt-get updatesudo apt install gcc make linux-headers-$(uname -r) dkmswget -q https://www.virtualbox.org/download/oracle_vbox_2016.asc -O- | sudo apt-key add -wget -q https://www.virtualbox.org/download/oracle_vbox.asc -O- | sudo apt-key add -sudo sh -c 'echo "deb http://download.virtualbox.org/virtualbox/debian $(lsb_release -sc) contrib" >> /etc/apt/sources.list.d/virtualbox.list'sudo apt update #обновляем репозиторийsudo apt install virtualbox-6.1

Создание VM

Мы пойдем по самому простому пути и создадим VM из интерфейса VirtualBox с такими характеристиками. В дальнейшем создание VM будет code-first

  • Количество CPU - не больше половины имеющихся на Вашем процессоре (в идеале половина)

  • Оперативная память - будет достаточно 4Gb

Nested Virtualization можно также включить из командной строки:

VBoxManage modifyvm <Имя VM> --nested-hw-virt on

Далее переходим в саму VM.

Шаг 2: Установка ПО на VM

В первый раз мы установим всё руками. В дальнейшем весь установочный сценарий будет помещен в Packer, что позволит нам создавать VM с нужными настройками каждый раз перед началом тестирования.

Устанавливаем последний образ Ubuntu с официального сайта.

Установка KVM

egrep -c '(vmx|svm)' /proc/cpuinfo #Если в результате будет возвращено 0 - значит Ваш процессор не поддерживает аппаратной виртуализации, если 1 или больше - то вы можете использовать KVM на своей машинеsudo apt-get update #Обновляем пакетыsudo apt install qemu qemu-kvm libvirt-daemon libvirt-clients bridge-utils virt-manager #Установка KVM и сопроводительные либыsudo usermod -G libvirt -a ubuntu #Добавление пользователя ubuntu в группу libvirtsudo systemctl status libvirtd #Проверка запуска сервиса libvirtsudo kvm-ok #Проверка статуса KVM

Установка Android command line tools

sudo apt-get update #обновляем пакетыyes | sudo apt install android-sdk #устанавливаем Android SDKsudo apt install unzip #Устанавливаем unzip для распаковки архивовcd ~/Downloads #переходим в каталог Downloadswget https://dl.google.com/android/repository/commandlinetools-linux-6858069_latest.zip #скачиваем архив с command line tools с официального сайта Googlesudo unzip commandlinetools-linux-6858069_latest.zip -d /usr/lib/android-sdk/cmdline-tools/ #распаковываемsudo mv /usr/lib/android-sdk/cmdline-tools/cmdline-tools /usr/lib/android-sdk/cmdline-tools/tools #переименовываем каталог с тулами. Сейчас странная ситуация, Google раздаёт тулу с одним каталогом, а SDK ищет его в другом каталогеexport ANDROID_SDK_ROOT=/usr/lib/android-sdk #регистируем переменнуюexport PATH=$PATH:$ANDROID_SDK_ROOT/cmdline-tools/latest/bin:$ANDROID_SDK_ROOT/cmdline-tools/tools/bin #регистрируем новый Pathexport PATH=$PATH:$ANDROID_SDK_ROOT/emulator #регистируем новый Path

Проверяем, что sdkmanager работает и Android SDK доступен:

sdkmanager --version

Устанавливаем Android tools

yes | sdkmanager --licenses #принимаем лицензииsudo chown $USER:$USER $ANDROID_SDK_ROOT -R #Ставим для текущего юзера право менять содержимое папки с ANDROID_SDK_ROOTyes | sdkmanager "cmdline-tools;latest" #устанавливаем cmdline-toolssdkmanager "build-tools;30.0.3" #Устанавливаем build-toolssdkmanager "platform-tools" #Устанавливаем platform-toolssdkmanager "platforms;android-30"sdkmanager "sources;android-30"sdkmanager "emulator" #Устанавливаем AVD manageremulator -accel-check #Проверяем, есть ли поддержка виртуализацииyes | sdkmanager "system-images;android-23;google_apis;x86_64" #Устанавливаем образ для эмулятораsdkmanager --list #Выводим список установленных пакетов. Обычно для CI оставляю.no | avdmanager create avd -n android-23_google_apis_x86_64 -k "system-images;android-23;google_apis;x86_64" #создаём эмулятор из образаemulator -list-avds #проверяем наличие созданного эмулятора

Устанавливаем Git и клонируем проект

В данном примере я использую пустой проект мобильного Android приложения. В нём уже есть дефолтный интеграционный тест. Нам этого будет вполне достаточно.

sudo apt update #обновляем пакетыyes | sudo apt install git #установка Gitgit --version #проверка установкиmkdir ~/workspace #создаём каталог для проектовcd ~/workspace #переходим в каталог для проектовgit clone https://github.com/panarik/AndroidClearApp.git #клонируем проект на локалcd ~/workspace/AndroidClearApp #переходим в каталог проекта

Шаг 3: Проведение тестирования проекта на созданном Android эмуляторе

./gradlew assembleDebug --no-daemon #билдим APKemulator -avd android-23_google_apis_x86_64 -no-audio -no-window -verbose -gpu off -accel off #запускаем эмулятор из ранее созданныхsleep 240 #аналог будильника, ждём четыре минуты пока загрузится эмуляторadb get-state #проверяем, видит ли ADB запущенный эмулятор. Если нет, то ждем еще

ADB видит подключенный к нему эмулятор:

Запускаем тестран:

./gradlew connectedAndroidTest --no-daemon

Ура! Тест пройден!

Негативный тест

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

Подготовка:

  • Переустановка VirtualBox на родительской машине (чтобы избежать ошибочное сохранение конфигов)

sudo apt purge virtualbox-6.1
  • VM мы создаём без проброса виртуализации и с одним CPU:

  • В созданной VM мы не устанавливаем:

    • VBoxClient

    • KVM

Остальные шаги аналогичны шагу 2 с установкой ПО. Попробуем еще раз наш тестран. Обратите внимание, что ADB также видит эмулятор:

Ура! Тест не пройден! Никогда еще так не радовался проваленному тестрану:

Падает PackageManager, как и обычно при запуске из виртуальной среды без аппаратной поддержки процессора:

Заключение

Мы сделали первый этап построения инфраструктуры для проведения автотестов Android приложений. Следующим этапом должно стать упаковка описанного выше сценария в Packer (ссылка на официальный сайт) который умеет работать с образами VirtualBox. Затем весь сценарий мы попробуем запустить из CI Jenkins. Если учесть, что плагин для него уже порядком устарел, то будет очень интересно.

Все результаты опубликую, как пополнения к этой статье.

В идеале, у нас должна получится code-first инфраструктура для тестрана UI и интеграционных автотестов для Android приложений, которую можно поднять на любом современном офисном ПК, которая работает автономно, билдит тесты на родных Android эмуляторах и есть не просит.

Спасибо большое за внимание!

П.С.

Можно Вас в комментариях попросить привести пример Вашей инфраструктуры с использованием Android эмулятора? К примеру, эмуляторы в докер-контейнерах (https://github.com/budtmo/docker-android) может быть еще какие-нибудь интересные примеры.

Подробнее..

Поездка в Китай маркировка обуви на фабрике

25.12.2020 14:07:02 | Автор: admin
Мы уже больше 15 лет разрабатываем софт для мобильного учёта товаров по штрихкодам. Последние несколько лет он стал очень востребован для работы с продукцией, попавшей под обязательную маркировку обувь, легпром, шины, парфюм и др.

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

image

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

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

Смонтированный сюжет будет в конце статьи, а кому интереснее читать, смотреть фото и небольшие видео с места событий, то просто продолжайте читать.

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

image

image

Купили местные симки. Заходим в сеть, Великий китайский файрвол начеку: Google не работает. Ставим VPN всё работает, хоть и чуть медленнее.

Кстати, сервисы Яндекса работают без VPN, но половина ссылок из выдачи просто не открывается. Вот такие дела. А ещё никто не предупредил нас, что это настоящие субтропики.


Но времени на адаптацию у нас не было, необходимо было лететь в город Вэнлинь, где и находилась наша цель.

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



Рано утром у гостиницы нас подобрал новенький Peugeot Ли, и мы поехали на обувную фабрику. Как же мы этого ждали! Наше путешествие выдалось совсем непростым, но теперь все трудности остались позади и можно было приступать к работе.


Фабрика


Что из себя представляет среднестатистическая фабрика по производству обуви в Китае?

Обычно это большое четырёх- или пятиэтажное здание. Каждый уровень выделен под конкретные цели со своим набором операций.

image

image

  • Первый уровень склад и разработка лекал;
  • Второй уровень раскройка, создание основных форм;
  • Третий уровень конвейеры по сборке и упаковке готовых изделий и зона контроля качества;
  • Четвёртый уровень склад готовой продукции.



О какой-то сумасшедшей автоматизации говорить не приходится.

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

Кстати, наши китайские коллеги попросили отметить, что на фабрике работают исключительно совершеннолетние.

Маркировка


Тут мы подбираемся к процессу маркировки, собственно, для чего мы и приехали.

Как происходит маркировка обуви сейчас


На линии сидят несколько девушек с заранее распечатанными штрихкодами. Линия идёт довольно спокойно.

Работник оклеивает этикетку на коробке, потом этикетку, прикреплённую к ботинку. На одну пару уходит где-то 1015 секунд.

image

В чём проблема


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

При этом нет никакой гарантии, что дальше по ленте всё ушло без ошибок.

image

Как наше ПО решает проблему


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

При этом скорость конвейера никак не мешает использовать ТСД и принтер в оклейке коробок с обувью.

image

Китай


Китай очень быстрый. Это и про местные электрички, которые разгоняются до 300 километров в час, и про ВВП, который гонит вперёд. Очевидно мы не увидели и десятой части всего, что происходит в этой стране, но даже то, что увидели, сильно впечатляет.

image

Учитывая, что ещё 2030 лет назад это была буквально нищая страна, то, как выглядит современная Поднебесная, поражает.

image

Динамика сумасшедшая, неудивительно, что работа с Китаем приносит огромные деньги. Но она же несёт риски и неопределённости.

Итог


Поездка в Китай получилась довольно продуктивной. Много полевых данных, которые ещё только предстоит проанализировать, но в целом успели опробовать самое главное.

Решение отлично показало себя при тестировании и уже внедрено более чем на 40 фабриках обуви и одежды в Китае. На сегодняшний день интерфейс доступен на китайском и английском языках.

Вот и обещанный видос:

Подробнее..

Принцип слоеного теста

23.12.2020 16:22:50 | Автор: admin
Всем неустрашимым на пути от отрицания до убеждения посвящается


image

Среди разработчиков бытует справедливое мнение, что если программист не покрывает код тестами, то попросту не понимает зачем они нужны и как их готовить. С этим трудно не согласиться, когда уже понимаешь о чем речь. Но как достичь этого драгоценного понимания?

Не судьба...


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

у нас есть метод, который подсчитывает сумму чисел

public Integer sum(Integer a, Integer b) {
return a+b
}

на данный метод можно написать тест

Test
public void testGoodOne() {
assertThat(sum(2,2), is(4));
}


Это не шутка, это упрощенный пример из типичной статьи про технологию unit тестирования, где в начале и конце общие фразы про пользу и необходимость, а в середине такое
Увидев такое, и перечитав для верности дважды, соискатель восклицает: Что за лютый бред?.. Ведь, у него в коде практически нет методов, которые все необходимое получают через аргументы, а затем отдают однозначный результат по ним. Это типичные утилитарные методы, и они практически не меняются. А как быть со сложными процедурами, с внедренными зависимостями, с методами без возврата значений? Там это подход не применим от слова совсем.

Если на этом этапе упорный соискатель не машет рукой и погружается дальше, то вскоре обнаруживает, что для зависимостей используются МОКи, для методов которых определяется некоторое условное поведение, фактически заглушка. Тут у соискателя может снести крышу окончательно, если рядом не найдется доброго и терпеливого мидла/сеньора готового и умеющего все разъяснить Иначе, соискатель истины совершенно теряет смысл того, что такое unit тесты, поскольку большая часть тестируемого метода оказывается некоей мок-фикцией, и что в таком случае тестируется непонятно. Тем более непонятно, как это организовать для большого, многослойного приложения и зачем такое нужно. Таким образом вопрос в лучшем случае откладывается до лучших времен, в худшем прячется в ящик прОклятых вещей.

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

Ключевая миссия.



Для начала, предлагаю сформулировать в двух словах ключевую функцию(миссию) unit тестов и ключевой выигрыш. Тут возможны разные живописные варианты, но предлагаю рассмотреть этот:

Ключевая функция unit тестов зафиксировать ожидаемое поведение системы.

и этот:

Ключевой выигрыш unit тестов возможность прогнать весь функционал приложения за считанные секунды.

Рекомендую запомнить это для собеседований и немножко поясню. Любой функционал подразумевает правила использования и результаты. Эти требования приходят от бизнеса, через системную аналитику и реализуются в коде. Но код постоянно развивается, приходят новые требования и доработки, которые могут незаметно и неожиданно изменить что-то в готовом функционале. Именно тут на страже стоят unit тесты, которые фиксируют утвержденные правила, по которым должна работать система! В тестах фиксируется сценарий, который важен для бизнеса, и если после очередной доработки тест падает, значит, что-то упущено: либо ошибся разработчик или аналитик, либо новые требования противоречат существующим и следует их уточнять и т.д. Самое главное сюрприз не проскочил.

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

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

Но, черт возьми, как?



Перейдем к самому интересному. Современные приложения активно избавляются от монолитности. Микросервисы, модули, слои основные принципы организации рабочего кода, позволяющие достигать независимости, удобства повторного использования, обмена и переноса в системы и т.д. В нашей теме имеет ключевое значение именно слоистая структура и внедрение зависимостей.

Рассмотрим слои типичного веб- приложения: контроллеры, сервисы, репозитории и т.п. Кроме того используются слои утилит, фасадов, моделей и DTO. Два последних не должны содержать функционала, т.е. методов кроме аксессоров(геттеры/сеттеры), поэтому покрывать их тестами не нужно. Остальные слои мы рассмотрим как цели для покрытия.
Как не напрашивается это вкусное сравнение, приложение нельзя сравнить со слоеным тортом по той причине, что слои эти внедряются друг в друга, как зависимости:
  • контроллер внедряет в себя сервис/ы, к которым обращается за результатом
  • сервис внедряет в себя репозитории (DAO), может внедрять утилитарные компоненты
  • фасад предназначен для комбинирования работы множества сервисов или компоненты, соответственно внедряет в себя их


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

Принцип слоеного теста.



Перейдем к примерам, простое приложение на Java Spring Boot, код будет элементарный, так что суть легко понятна и аналогично применима для других современных языков/фреймворков. Задача у приложения будет простая умножить число на 3, т.е. утроить (англ. triple), но при этом мы создадим многослойное приложение с внедрением зависимостей (dependency injection) и послойным покрытием с головы до пят.

image

В структуре созданы пакеты для трех слоев: controller, service, repo. Структура тестов аналогична.
Работать приложение будет так:
  1. с фронт-энда на контроллер приходит GET запрос с идентификатором числа, которое требуется утроить.
  2. контроллер запрашивает результат у своей зависимости сервиса
  3. сервис запрашивает данные у своей зависимости репозитория, умножает и возвращает результат контроллеру
  4. контроллер дополняет результат и возвращает на фронт-энд


Начнем с контроллера:

@RestController@RequiredArgsConstructorpublic class SomeController {   private final SomeService someService; // dependency injection   static final String RESP_PREFIX = "Результат: ";   static final String PATH_GET_TRIPLE = "/triple/{numberId}";   @GetMapping(path = PATH_GET_TRIPLE) // mapping method to GET with url=path   public ResponseEntity<String> triple(@PathVariable(name = "numberId") int numberId) {       int res = someService.tripleMethod(numberId);   // dependency call       String resp = RESP_PREFIX + res;                // own logic       return ResponseEntity.ok().body(resp);   }}


Типичный рест контроллер, имеет внедрение зависимости someService. Метод triple настроен на GET запрос по URL "/triple/{numberId}", где в переменной пути передается идентификатор числа. Сам метод можно разделить на две основные составляющие:

  • обращение к зависимости запрос данных извне, либо вызов процедуры без результата
  • собственная логика работа с имеющимися данными


Рассмотрим сервис:

@Service@RequiredArgsConstructorpublic class SomeService {   private final SomeRepository someRepository; // dependency injection   public int tripleMethod(int numberId) {       Integer fromDB = someRepository.findOne(numberId);  // dependency call       int res = fromDB * 3;                               // own logic       return res;   }}


Тут подобная ситуация: внедрение зависимости someRepository, а метод состоит из обращения к зависимости и собственной логики.

Наконец репозиторий, для простоты выполнен без базы данных:

@Repositorypublic class SomeRepository {   public Integer findOne(Integer id){       return id;   }}


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

Если запустить наше приложение, то по настроенному url можно увидеть:



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

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

@Test    void someMethod_test() {        // prepare...        int res = someService.someMethod();                 // check...    }


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

На примере контроллера попробуем подробно изобразить базовый алгоритм написания теста. Прежде всего, целевой метод контроллера принимает параметр int numberId, добавим его в наш сценарий:

int numberId = 42; // input path variable


Этот же numberId транзитом передается на вход методу сервиса, и тут самое время обеспечить сервис-мок:

@MockBeanprivate SomeService someService;


Собственный код метода контроллера работает с результатом полученным от сервиса, имитируем этот результат, а также вызов который его возвращает:

int serviceRes = numberId*3; // result from mock someService// prepare someService.tripleMethod behaviorwhen(someService.tripleMethod(eq(numberId))).thenReturn(serviceRes);


Эта запись означает: когда будет вызван someService.tripleMethod с аргументом равным numberId, вернуть значение serviceRes.
Кроме того, эта запись фиксирует факт, что данный метод сервиса должен быть вызван, что важный момент. Бывает что требуется зафиксировать вызов процедуры без результата, тогда используется иная запись, условно такая не делать ничего когда...:

Mockito.doNothing().when(someService).someMethod(eq(someParam));


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

int serviceRes = numberId*5; 


это не сломает текущий сценарий, т.к. тут фиксируется не поведение someService, а поведение контроллера, принимающего результат someService как должное. Это совершенно логично, ведь целевой класс не может отвечать за поведение внедряемой зависимости, а вынужден ей доверять.

Итак мы определили поведение мока в нашем сценарии, следовательно при выполнении теста, когда внутри вызова целевого метода дело дойдет до мока, он вернет что попросили serviceRes, и дальше с этим значением будет работать собственный код контроллера.
Далее помещаем в сценарий вызов целевого метода. Метод контроллера имеет особенность он не вызывается в коде явно, а привязан через HTTP метод GET и URL, поэтому в тестах вызывается через специальный тестовый клиент. В Spring это MockMvc, в других фреймворках есть аналоги, например WebTestCase.createClient в Symfony. Итак, далее просто выполнение метода контроллера через маппинг по GET и URL.

       //// mockMvc.perform       MockHttpServletRequestBuilder requestConfig = MockMvcRequestBuilders.get(SomeController.PATH_GET_TRIPLE, numberId);       MvcResult mvcResult = mockMvc.perform(requestConfig)           .andExpect(status().isOk())           //.andDo(MockMvcResultHandlers.print())           .andReturn()       ;//// mockMvc.perform


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

// check of callingMockito.verify(someService, Mockito.atLeastOnce()).tripleMethod(eq(numberId));


В нашем случае это избыточно, т.к. его единственный вызов мы уже зафиксировали через when, но иногда это способ уместен.

А теперь главное мы проверяем поведение собственного кода контроллера:

// check of resultassertEquals(SomeController.RESP_PREFIX+serviceRes, mvcResult.getResponse().getContentAsString());


Тут мы зафиксировали то, за что отвечает сам метод что результат полученный от someService конкатенируется с префиксом контроллера, и именно эта строка уходит в тело response. Кстати, воочию в содержимом Body можно убедиться, если раскомментировать строку

//.andDo(MockMvcResultHandlers.print())


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

Таким образом у нас получился тестовый метод в тестовом классе контроллера:

@WebMvcTest(SomeController.class)class SomeControllerTest {   @MockBean   private SomeService someService;   @Autowired   private MockMvc mockMvc;   @Test   void triple() throws Exception {       int numberId = 42; // input path variable       int serviceRes = numberId*3; // result from mock someService       // prepare someService.tripleMethod behavior       when(someService.tripleMethod(eq(numberId))).thenReturn(serviceRes);       //// mockMvc.perform       MockHttpServletRequestBuilder requestConfig = MockMvcRequestBuilders.get(SomeController.PATH_GET_TRIPLE, numberId);       MvcResult mvcResult = mockMvc.perform(requestConfig)           .andExpect(status().isOk())           //.andDo(MockMvcResultHandlers.print())           .andReturn()       ;//// mockMvc.perform       // check of calling       Mockito.verify(someService, Mockito.atLeastOnce()).tripleMethod(eq(numberId));       // check of result       assertEquals(SomeController.RESP_PREFIX+serviceRes, mvcResult.getResponse().getContentAsString());   }}


Теперь настало время честного теста метода someService.tripleMethod, где аналогично есть вызов зависимости и собственный код. Готовим произвольный входящий аргумент и имитируем поведение зависимости someRepository:
int numberId = 42;when(someRepository.findOne(eq(numberId))).then(AdditionalAnswers.returnsFirstArg());


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

assertEquals(numberId*3, res);


Фиксируем, что значение полученное от репозитория должно быть утроено собственной логикой метода. Теперь данный тест стоит на страже этого требования:

@ExtendWith(MockitoExtension.class)class SomeServiceTest {   @Mock   private SomeRepository someRepository; // то, что мокируем   @InjectMocks   private SomeService someService; // куда внедряем то, что мокируем   @Test   void tripleMethod() {       int numberId = 42;       when(someRepository.findOne(eq(numberId))).then(AdditionalAnswers.returnsFirstArg());       int res = someService.tripleMethod(numberId);       assertEquals(numberId*3, res);   }}


Поскольку репозиторий у нас условно-игрушечный, то и тест получился соответствующий:

class SomeRepositoryTest {   // no dependency injection   private final SomeRepository someRepository = new SomeRepository();   @Test   void findOne() {       int id = 777;       Integer fromDB = someRepository.findOne(id);       assertEquals(id, fromDB);   }}


Однако и тут весь скелет на месте: подготовка, вызов и проверка. Таким образом корректная работа someRepository.findOne зафиксирована.

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

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

Кроме того, тесты повышают качество кода. В рамках независимого тестирования слоями часто приходится пересмотреть подход к организации кода. Например в сервисе метод создан метод first, он не маленький, он содержит и собственный код и моки, и, допустим, дробить его не имеет смысла, он покрыт тестом/ми по полной программе определены все подготовки и проверки. Затем кто-то решает добавить в сервис метод second, в котором вызывается метод first. Вроде некогда обычная ситуация, но когда доходит до покрытия тестом что-то не складывается Для метода second придется описывать и сценарий second и дублировать сценарий подготовки first? Ведь не получится замокать метод first самого тестируемого класса.

Возможно, в таком случае уместно задуматься об иной организации кода. Есть два противоположных подхода:
  • вынести метод first в компонент-утилиту, которая внедряется как зависимость в сервис.
  • вынести метод second в некий сервис-фасад, который комбинирует разные методы внедренного сервиса или даже нескольких сервисов.

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

На дорожку...



Вопрос для собеседования: сколько раз в рамках тикета разработчик должен запускать тесты? Сколько угодно, но как минимум дважды:
  • перед началом работы, чтобы убедиться что все OK, а не выяснять потом, что уже было сломано, а не ты сломал
  • по окончании работы

Так зачем же писать тесты? Затем, что не стоит и пытаться в большом, сложном приложении все упомнить и предусмотреть, это нужно возложить на автоматику. Разработчик не владеющий авто-тестированием не готов участвовать в большом проекте, это сразу выявит любой собеседующий.

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


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

Код примера доступен по ссылке на github.com: https://github.com/denisorlov/examples/tree/main/unittestidea
Подробнее..

Вжух, и прогоны автотестов оптимизированы. Intellij IDEA плагины на службе QA Automation

16.12.2020 12:18:50 | Автор: admin


Привет, Хабр. Я работаю QA Automation инженером в компании Wrike и хотел бы поговорить о том, как нам удалось оптимизировать процесс код-ревью для репозитория с 30 000+ автотестов при помощи IntelliJ IDEA плагина. Я расскажу о внутреннем устройстве плагина и о том, какие проблемы он решает в нашей компании. А еще в конце статьи будет ссылка на Github репозиторий с кодом плагина, с помощью которого вы сможете попробовать встроить плагин в ваши процессы.

Автотесты и деплой в Wrike


Мы пишем много разнообразного кода на Java в проекте автотестов. Сейчас у нас существует более тридцати тысяч тестов, которые тестируют наш продукт через Selenium, REST API, WebSocket и т.д. Все тесты разбиты на множество Maven-модулей в соответствии со структурой кода фронтенда. Проект активно меняется (релизы 1-3 раза в день), и, чтобы поддерживать качество кода, мы используем хорошо развитый механизм код-ревью. Во время ревью проверяем не только качество, но и работоспособность тестов.

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

Чтобы избежать таких ситуаций, мы решили добавить в процесс код-ревью одно действие. Автор merge request должен приложить ссылку на зеленый прогон автотестов в Teamcity, а ревьюер перейти по ней и проверить, что прогон действительно зеленый.

Какие проблемы могут возникнуть при запуске автотестов


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

У нас есть два механизма запуска тестов:

  1. По группам продуктовая разметка вида Epic/Feature/Story.
  2. По идентификаторам (id) любой автотест помечается уникальным числом, и можно запускать прогон по набору таких чисел.

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

Дальше события могут развиваться по нескольким сценариям.

Сценарий 1: Запущено меньше тестов, чем в действительности затронуто новым кодом.

Первая проблема потенциальное замедление процесса деплоя. Ведь есть риск, что пропущен тест, который перестал работать из-за изменений в коде. Тогда он обнаружится в день деплоя и затормозит доставку продукта пользователям.

Сценарий 2: Запущено больше тестов, чем в действительности затронуто новым кодом.

Вторая проблема избыточная работа инфраструктуры для запуска тестов, за которую платит компания. Это увеличивает очередь на запуск тестов в Teamcity, что заставляет дольше ждать результат прогона.

Чтобы решить эти проблемы, мы создали Find Affected Tests IntelliJ IDEA плагин, который быстро и надежно находит список id тестов, затронутых изменениями в коде проекта автотестов.

От бизнес-проблем к реализации


Но почему именно IntelliJ IDEA плагин?, спросите вы. Чтобы создать инструмент, который решит наши проблемы, мне пришлось ответить на два вопроса:

  • Откуда брать изменения кода?
  • Как по изменениям в коде найти затронутые id?

На первый вопрос ответ был очевиден. Мы используем Git в качестве VCS и через него можем получать информацию про состояние текущей ветки.

Ответом на второй вопрос стал PSI (Program Structure Interface). Я выбрал именно этот инструмент по нескольким причинам:

  1. Мы используем IntelliJ IDEA в качестве IDE.
  2. IntelliJ Platform SDK включает в себя PSI удобный инструмент для работы с кодом проекта, который используется и самой IntelliJ IDEA.
  3. Функциональность IntelliJ IDEA можно расширить за счет механизма плагинов.

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

Структура плагина


Давайте посмотрим на UI плагина, чтобы представить, что видит автоматизатор перед запуском поиска id:


Так выглядит упрощенная версия, которая выложена на GitHub

Все интуитивно понятно: запустили поиск, посмотрели результат. Результат состоит из двух частей: id автотестов и списка Maven-модулей, в которых эти автотесты находятся (зачем нужен этот список я расскажу дальше в этой статье).

Внутренняя структура плагина состоит из двух основных модулей:

  • Git модуль получает информацию о текущей ветке и преобразует ее к формату, понятному PSI.
  • PSI модуль на основании данных от Git модуля ищет id тестов, которые затронуты изменениями в текущей ветке.

Взаимодействие UI-части плагина с модулями схематично выглядит так:


AffectedCodeUnit и DisplayedData это классы для передачи данных между модулями

Git модуль


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

Для каждого файла собираются следующие данные:

  • Имя файла, имя модуля и путь до файла относительно корня проекта. Это позволяет в дальнейшем упростить поиск файла через механизмы PSI.
  • Номера измененных строк в файле. Это позволяет понять, что именно было изменено в структуре Java-кода.
  • Был ли файл удален или создан с нуля. Это помогает оптимизировать работу с ним в будущем и избежать ошибок в некоторых ситуациях (например, не обращаться к удаленному файлу через PSI).

Все эти сведения содержатся в выводе команды git diff. Пример результата выглядит так:


В выводе команды можно сразу заметить путь до измененного файла. Данные о номерах строк измененного кода содержаться в строках вида @@ -10 +10,2 @@. В этой статье я не буду подробно объяснять их смысл, но примеры можете посмотреть на Stackoverflow или поискать информацию про git diff unified format

Аргументами для git diff выступают текущая локальная ветка пользователя и remote master, а ряд ключей позволяет сократить вывод команды до нужного размера.

Каким образом происходит взаимодействие с Git в коде плагина? Я попытался найти встроенные механизмы IntelliJ IDEA, чтобы не изобретать велосипед. В итоге обнаружил, что для Git в IntelliJ IDEA существует свой плагин git4Idea. По сути это GUI для стандартных операций с проектом. Для доступа к Git в нашем плагине используются интерфейсы из кода git4Idea.

В итоге получилась такая схема:



Через класс GitBranch запрашивается diff, затем diff отправляется в DiffParser. На выходе остается список объектов класса AffectedCodeUnit (в них содержится информация об измененных файлах с кодом). О судьбе этого списка я расскажу в описании PSI модуля.

PSI модуль


Теперь информацию об измененных файлах нужно применить для поиска id автотестов. Чтобы разобраться, как по номерам измененных строк найти элементы Java-кода, нужно подробнее посмотреть на устройство PSI.

Как IntelliJ IDEA работает с произвольным проектом. Для PSI любой файл преобразуется в дерево отдельных элементов. Каждый элемент реализует интерфейс PsiElement и представляет собой структурную единицу кода для конкретного языка программирования. Это может быть целый класс или метод, а может быть закрывающая скобка небольшого блока кода.

Для нашей задачи элемент Java кода и есть PsiElement-объект. Здесь мы приходим к новой формулировке вопроса. Как по номерам измененных строк кода найти PsiElement-объекты? В интерфейсе PsiFile (представление файла с кодом в виде объекта) был унаследован метод findElementAt, который по сдвигу относительно начала текстового представления файла умел находить PsiElement.

Но возникла новая проблема: каждая строка в файле потенциально содержит немало символов, и по номеру строки можно получить много разных PsiElement-объектов.

Как выбрать нужный PsiElement-объект. Дерево, в которое IntelliJ IDEA отображает Java-код, может состоять из огромного числа узлов. Конкретный узел может описывать незначительный элемент кода. Для меня важны узлы конкретных типов: PsiComment, PsiMethod, PsiField и PsiAnnotation.

И вот почему:

String parsedText = parser.parse("Text to parse");

В этом фрагменте можно изменить разные части кода: имя переменной parsedText, входной параметр метода parse, код после оператора присваивания (вместо вызова parse можно вызвать другой метод) и т.д. Видов изменений много, но для логики работы плагина важно то, что фрагмент кода может находиться внутри какого-то метода, как в этом примере:

public String parseWithPrefix(Parser parser, String prefix) {    String parsedText = parser.parse("Text to parse");    return prefix + parsedText;}

В такой ситуации нет смысла пытаться понять, что поменялось в строчке кода с объявлением String parsedText. Нам важно, что изменился метод parseWithPrefix. Таким образом, нам не важна излишняя точность при поиске PsiElement-объектов для измененной строки кода. Поэтому я решил брать символ посередине строки как измененный и искать привязанный к нему PsiElement. Такая процедура позволила получить список затронутых PsiElement объектов и по ним искать id автотестов.

Конечно, можно придумать примеры Java-кода с диким форматированием, в котором нужно учитывать больше условий для поиска PsiElement-объекта. Как в этом примере:

public String parseWithPrefix(Parser parser, String prefix) {    String parsedText = parser.parse("Text to parse");    return prefix + parsedText; } private Parser customParser = new Parser();

Изменения в последней строке могут затрагивать как метод parseWithPrefix, так и поле customParser. Но у нас есть механизмы анализа кода, которые не допустят подобное в мастер.

Как устроен базовый алгоритм получения id автотестов по набору PsiElement-объектов. Для начала нужно уметь по PsiElement-объекту получать его использования (usages) в коде. Это можно сделать с помощью интерфейса PsiReference, который реализует связь между объявлением элемента кода и его использованием.

Теперь сформулируем краткое описание алгоритма:

  1. Получаем список PsiElement-объектов от Git модуля.
  2. Для каждого PsiElement-объекта ищем все его PsiReference объекты.
  3. Для каждого PsiReference объекта проверяем наличие id и сохраняем, если нашли.
  4. Для каждого PsiReference объекта ищем его PsiElement-объект и рекурсивно запускаем на нем описанную процедуру.

Процедуру следует повторять, пока находятся PsiReference-объекты.

Для задачи поиска абстрактной информации в коде алгоритм выглядит так:



В нашей задаче нужно найти id автотестов. Вот так можно применить блок-схему для этого:



Оптимизация алгоритма. В процессе реализации алгоритма я столкнулся с одной проблемой. Алгоритм работает с PsiTree-объектом, который состоит из большого числа узлов. Каждый узел соответствует элементам Java-кода. Механизм поиска PsiReference-объектов по умолчанию может находить довольно незначительные элементы Java-кода, из-за чего алгоритм будет совершать много лишних действий в процессе движения по дереву. Это замедляет работу алгоритма на несколько минут в тех случаях, когда найдено около тысячи тестов или больше.

Пример для наглядности: предположим, что значение переменной wrikeName изменилось:

public List<User> nonWrikeUsers(List<User> users) {    String wrikeName = "Wrike, Inc.";    return users.stream()                     .filter(user -> !wrikeName.equals(user.getCompanyName()))                     .collect(Collectors.toList());}

Тогда в поисках использования wrikeName мы сначала придем к вызову метода equals, затем к отрицанию результата вызова equals, а после к лямбде внутри вызова filter. Но эту длинную цепочку шагов можно заменить на поиск использований изменившегося метода nonWrikeUsers.

И тогда вместо четырех итераций алгоритма по каждому узлу дерева мы получим всего одну:



Проблему лишних операций в алгоритме удалось решить благодаря тому же ограничению типа данных PsiElement-объектов до PsiComment, PsiMethod, PsiField и PsiAnnotation. Именно эти PSI-сущности содержат всю релевантную информацию для поиска id затронутых автотестов.

Пример релевантной информации: затронуто поле какого-то класса (объект типа PsiField), оно могло использоваться в каком-то автотесте.

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

В начало логики алгоритма я добавил поиск прародительского узла с нужным типом данных. Поиск осуществляется при помощи метода getParentOfType класса PsiTreeUtil. Для каждого типа PsiElement-объекта я создал реализацию логики обработки. Например, для прародителя типа PsiField отрабатывает объект класса FieldProcessing, а для PsiComment объект класса CommentProcessing. Код плагина исполняет подходящую логику в зависимости от результата работы метода getParentOfType.

В общем виде логика работы выглядит так:



Пример реализации логики обработки одного из типов PsiElement-объектов:



Подытожу основные основные особенности PSI модуля:

  1. Модуль работает по рекурсивному алгоритму поиска id автотестов. Краткая идея алгоритма заключается в поиске id для всех измененных PsiElement-объектов через механизм PsiReference.
  2. Алгоритм работает не со всеми узлами PsiTree-объекта, чтобы не совершать избыточные итерации.

Дополнительные возможности плагина


После того, как мы начали использовать плагин в работе, коллеги прислали несколько запросов по его улучшению. Это помогло усовершенствовать инструмент и оптимизировать прогоны автотестов.

Делаем работу с Git модулем гибче


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

Пример 1: автоматизатор создает merge request с одним коммитом. Он прогоняет 1000 тестов, которые затронуты этим коммитом. Ревьюер оставляет замечания по коду, автор merge request их исправляет. Эти правки затрагивают теперь только 200 тестов, но плагин предложит прогнать и 1000 тестов из изначального прогона, так как учтен первый коммит. Получаем лишние тесты для прогона.

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

Пример 2: автоматизатор создает интеграционную ветку для того, чтобы мерджить туда другие ветки на протяжении некоторого времени (код в мастер в итоге будет добавлен из интеграционной ветки). На код-ревью ветка для мерджа в интеграционную ветку будет включать в себя коммиты оттуда. Но эти коммиты уже могли быть проверены раньше при помощи плагина. В итоге мы каждый раз получаем лишние тесты для прогона.

Решение: я реализовал функциональность для сравнения локальной версии ветки с произвольной веткой (в общем виде с любым коммитом). Это позволяет гибко учитывать коммиты при работе с несколькими ветками.

Помогаем внедрить новую оптимизацию с помощью плагина


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

Тогда мы решили изменить сценарий запуска. Теперь пересобираются только модули, указанные вручную. Это позволило в среднем снизить время компиляции с 20 минут до 3-4.

Чтобы узнать, в каких именно модулях находятся затронутые тесты, я добавил в плагин логику вывода затронутых модулей. Для ее реализации во время получения id автотеста нужно взять пути до файла с тестом в операционной системе и относительно корня проекта. Зная структуру директорий типичного Maven проекта, можно обрезать все лишнее и оставить часть пути с именами модулей.

В итоге удалось быстрее внедрить новую логику запуска и как результат уменьшить время ожидания прогона тестов и КПД инфраструктуры.

Планы по развитию плагина


Плагин выходит в сеть. Мы автоматизировали сбор id затронутых тестов, но после автоматизатору все еще приходится выполнять набор однотипных действий для запуска прогона и дальнейшей работы с ним:

  1. Открыть нужный билд в Teamcity.
  2. Ввести данные для прогона.
  3. Сохранить ссылку на прогон для проверки его результатов.

Это отвлекает от решения других задач и приводит к возможным ошибкам: запуск в неверной сборке Teamcity, ошибки при копировании данных для прогона.

Так возникла задача: научить плагин запускать прогон в Teamcity самостоятельно. У нас есть специальный инструмент, который аккумулирует большинство данных, связанных с CI/CD процессами. Поэтому плагину проще отсылать запрос на запуск прогона этому инструменту (в нем же будет сохранена ссылка на прогон). Реализация этой логики позволит уменьшить количество рутинных действий после написания кода и сделает процесс запуска автотестов надежнее.

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

Идея состоит в том, чтобы несколько потоков одновременно занимались анализом разных узлов Psi-дерева. Для этого нужно грамотно выстроить работу с общими ресурсами (например, со множеством уже обработанных PsiElement-объектов). Одним из возможных решений может быть Java Fork/Join Framework, так как работа с деревом подразумевает рекурсивность. Но здесь есть подводные камни: у IntelliJ IDEA есть свой пул потоков, в который нужно грамотно встроить новую логику.

Несколько выводов


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

У нас есть сборка в Teamcity, в которой традиционно гоняются запущенные вручную автотесты. Она позволяет запустить прогон с указанием Maven-модулей. Я выбрал эту сборку для примера, потому что ссылку именно на нее автор merge request прикладывает во время код-ревью. А еще запуски в этой сборке выполняются вручную, и автоматизатор может взять набор id для запуска только из плагина (вряд ли кто-то в здравом уме будет собирать их руками).

Так выглядит график общего количества запусков в этой сборке:



В марте этого года (точка 03.2020) мы анонсировали сборку официально, а в апреле (точка 04.2020) я добавил в плагин вывод Maven-модулей, в которых находились затронутые автотесты. После этого количество запусков возросло в несколько раз. Следующий скачок произошел в сентябре, так как эта сборка стала использоваться для перезапуска автотестов в нашей внутренней деплой туле, но это нам не так интересно.

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



За последние полгода порядка 70-80% запусков автотестов происходят с указанием списка id: коллеги активно используют плагин по назначению. В апреле мы добавили в плагин вывод Maven-модулей. На графике видно, что это увеличило процент запусков с id с 50% до 75% за пару месяцев.

Действительно ли прогоны по id быстрее прогонов по продуктовой разметке? Статистика среднего времени прогона для данной сборки показывает, что да. Для id мы получаем примерно в три раза меньшее время, чем для продуктовой разметки: вместо условных 25-30 минут 8-10 минут.

Данные на графиках доказывают, что плагин активно используют. А это значит, что нам удалось увеличить количество прогонов в Teamcity, в которых запущенные автотесты подобраны по более надежному алгоритму, чем просто запуск по продуктовой разметке.

Пишите в комментариях про ваши успешные или не очень успешные примеры автоматизации.

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

Всем добра и побольше эффективных оптимизаций!
Подробнее..

Перевод Тестирование инфраструктуры как кода Terraform анализ модульных тестов и сквозной разработки путем тестирования поведен

16.12.2020 20:13:35 | Автор: admin


Для будущих студентов курса Infrastructure as a code in Ansible и всех интересующихся подготовили перевод полезного материала.

Также приглашаем записаться на открытый урок по теме Управление Kubernetes при помощи Kubespray.





С возвращением! Это очередная техническая статья из серии заметок о terraform и kubernetes на тему инфраструктуры как кода, подготовленных компанией Contino.

TL;DR
Размер команды не имеет значения. В любом случае реализация хорошего анализа конфигурации инфраструктуры на базе terraform и сквозного тестирования ее разумности не обязательно должна быть длительным и сложным процессом.

Мне подвернулась интереснейшая задача: изучить, разработать и представить подходящую платформу тестирования с открытым кодом для базы кода terraform в рамках конвейера выпуска инфраструктуры. Принцип контроль качества во всем далеко не нов. Но его реализация зависит от зрелости инфраструктуры организации и допустимого уровня риска до стадии выхода на [сколь-либо] готовый продукт.

Свежий взгляд на сферу тестирования кода для инфраструктуры позволил мне познакомиться с новейшими тестовыми платформами и обновить свои знания.

image

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

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

  • Контроль качества кода terraform fmt -check и terraform validate.
  • Предварительный просмотр terraform plan.
  • Построение TFLOG=debug terraform apply для дотошной проверки.

Средства статического анализа кода Terraform


Прочесывание Google выявило весьма обширный перечень потенциально пригодных средств тестирования terraform.

Но сначала пройдемся по списку наших требований.

  • Наличие модульных тестов в конфигурации ресурсов terraform и возможность расширять любой подобный общий список проверок соответствия передовым методам* для определенного поставщика облачных услуг. Кроме того, нас интересуют простые в использовании инструменты, с которыми можно начать работать без промедления.

* Отсутствие экземпляров ec2, открытых миру 0.0.0.0/0, и так далее.

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

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

  • Поддержка сообщества важна, когда дело касается программного обеспечения с открытым исходным кодом. Ну и самое последнее, любые выбранные платформы должны быть написаны на распространенном языке программирования, таком как Go или Python. Если понадобится углубиться в платформу и написать собственные методы, найти инженера с опытом работы с одним языком будет, пожалуй, проще, чем с двумя. Любой предыдущий опыт работы с подобными инструментами, который может быть у имеющихся сотрудников группы и организации в целом, также будет полезен. Итак, теперь я готов отправиться на поиски правильного средства тестирования и платформ для выполнения этой работы.

Подборка: анализ и сравнение средств и платформ тестирования Terraform

Надеюсь, что приведенный далее список облегчит ваши труды по статическому анализу инфраструктуры как кода и контролю качества. Обращаю внимание, это полный список всех найденных мной соответствующих инструментов тестирования terraform, представляющий собой смесь средств тестирования обоснованности конфигурации, контроля качества кода и передовых методов, ориентированных на secOps, с модульными тестами. Далее вашему вниманию предлагается этот список.



Выбранные средства тестирования


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

Рассмотрев преимущества и недостатки каждой платформы, я остановил свой выбор на инструменте checkov и платформе с очень подходящим названием terraform-compliance обе они написаны на python. Они удовлетворяли всем моим описанным выше требованиям.

Конвейер выпуска инфраструктуры как кода в общих чертах выглядит примерно так.

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

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


Модульное тестирование Checkov от BridgeCrew


www.checkov.io

Checkov это инструмент статического анализа кода для инфраструктуры как кода.

Он сканирует облачную инфраструктуру, подготовленную с помощью Terraform, Cloudformation, Kubernetes, Serverless или шаблонов ARM, и выявляет неправильную конфигурацию с точки зрения безопасности и соблюдения нормативных требований.

Есть несколько модульных тестов, выполняемых по умолчанию при сканировании репозитория кода terraform, которые показывают отклонения от передовых методов например, когда согласно конфигурации безопасности у вас есть виртуальная машина на порту 22, открытом миру (0.0.0.0/0).

Все тесты можно найти по этой ссылке на GitHub.

Начать работать с платформой очень просто.

  • Установите двоичный файл.
  • Инициализируйте каталог terraform командой terraform init.
  • Запустите chechov для этого каталога.

Список всех модульных тестов, выполняемых по умолчанию, можно вывести в командной строке. Кроме того, при работе checkov платформа по умолчанию выдает все пройденные и непройденные модульные тесты. Очень удобно, начать использовать просто. Передовые методы terraform проверяются, но не все. Это фундаментальное отличие.

Chechov с радостью оценит ТОЛЬКО ваш код terraform. Платформа может работать сразу после terraform init. Ей нет дела до вашего terraform plan со всеми преимуществами и недостатками. Платформа выполняет то, что заявлено, а именно статический анализ кода. Помните о возможных последствиях, а также любых соображениях относительно логики для ваших ресурсов.

image

image

Вывод после работы checkov с указанием пройденных и непройденных проверок.


Если вы готовы заниматься углубленной разработкой на Python, то сможете написать дополнительные модульные тесты. Язык разработки платформы входил в число моих требований, поскольку иногда мне приходится анализировать базу кода тестов, чтобы прикинуть, насколько трудно будет [при необходимости] создать подобные дополнительные методы. Этот момент в сочетании с вопросами обслуживания для группы в целом стал основным фактором выбора этой платформы вместо альтернативной, позволяющей получить такой же результат.

Резюмируя, в области статического анализа кода платформа checkov просто великолепна. В частности, если мне нужно, чтобы изначально определенный IP-адрес подсети был помещен в белый список. Но такой вариант не годится для тестов e2e, нуждающихся в отдельной платформе тестирования.

С другой стороны, в качестве решения я могу реплицировать модульный тест и жестко прописать параметры моей подсети/IP-адреса. А что тогда делать, если у меня несколько экземпляров и проектов, пропускать этот тест, даже если он нужен? Может быть. А может и нет.

Вот здесь-то в игру и вступает вторая платформа тестирования terraform-compliance.

Terraform-compliance


terraform-compliance.com

Terraform-compliance это облегченная платформа тестирования, предназначенная для проверки безопасности и соблюдения нормативных требований в terraform для обеспечения совместимости вашей инфраструктуры как кода с отрицательным тестированием.

Предыстория


Еще раз отмечу, сквозная разработка через тестирование поведения (BDD) стала использоваться как платформа тестирования недавно, подчеркнув потребность в универсальной платформе тестирования. Но это не единственная польза.Простота.

На самом деле, мне кажется, что BDD получает недостаточно внимания. Возможно, вы слышали о разработке посредством тестирования (TDD), которая глубоко пускает корни, прежде всего в среду разработки программного обеспечения. Но именно здесь платформы наподобие BDD упрощают создание дополнительной логики, предлагая обычному специалисту по обслуживанию инфраструктуры более простой, лаконичный и повторяемый способ разработки сквозных настраиваемых тестов без углубленного изучения какого-либо специализированного и нового языка программирования.

И хотя с помощью кода можно описать, по сути, все на свете, в конечном счете все упирается в управляемость, возможности постичь сложность кода (для чего может потребоваться подготовка обширной документации), не говоря уже о поддержке и обслуживании.Здесь можно подробнее познакомиться с BDD.

Cucumber.io это не просто язык, это система, упрощающая работу по тестированию за счет применения подхода WYSIWYG к созданию тестов, пониманию их работы и обслуживанию. Эти примеры определяются до начала разработки и используются в качестве критериев приемки.

Они являются частью определения.

Тестирование с помощью Terraform-Compliance


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

Вот пример такого теста, разработанного с помощью платформы terraform-compliance с применением BDD. Он позволяет выполнять достаточно сложное сквозное тестирование.

Платформа terraform-compliance использует вывод terraform plan. В результате это позволяет формировать полные планы выпусков и тщательно тестировать их. Например, контролировать использование правильной пары ключей шифрования [для вашего поставщика облачных услуг] для учетной записи, среды и т.п. У вас будет большая свобода для творчества, и самое важное работать с платформой очень просто.

Просто ознакомьтесь с приведенными ниже действиями и примерами.

  • Шаг 1. Инициализируйте каталог terraform:# terraform init
  • Шаг 2. Можно быстро сформировать terraform plan следующей командой: #terraform plan -out=plan.out
  • Шаг 3. Напишите несколько тестов. Дело нехитрое уже есть папка с примерами. Давайте пройдемся по моим собственным примерам тестов, приведенным ниже, написанным на основе моего вывода terraform plan.

Это фрагмент плана terraform конфигурации terraform, которая создает EKS с указанной группой запуска. Давайте удостоверимся, что в нашем коде инфраструктуры terraform не применяется instancetype, а используется одобренный вариант a1.xlarge или a1.2xlarge.

Теперь я намеренно изменю его на t2.small, чтобы имитировать непрохождение теста.

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

  • Шаг 4. Давайте заставим terraform-compliance оценить плат с использованием тестовых сценариев: #terraform-compliance -p plan.out -f ./<test-cases-folder>


Выполнение тестов


Пример результата с прохождением и непрохождением

image

Если в нашем коде инфраструктуры Terraform используется правильный instancetype, то все результаты будут зелеными SUCCESS.

Если же наш код инфраструктуры Terraform нарушает требование из-за наличия неправильного instancetype, то результаты будут красными FAIL.

Давайте напишем еще больше тестов

image

Еще несколько простых тестов, взятых из каталога примеров:

image

Если одна ситуация с непрохождением в этом случае пользователь увидит фактическое_значение, которое извлекается и показывается в целях справки и отладки.

Результаты тестов


После выполнения всех тестов отображается удобная итоговая сводка по всем пройденным и непройденным тестам, в которой также указываются пропущенные тесты. Она мне нравится тем, что позволяет написать длинный список тщательных тестов, а также найти в конце четкие сведения о том, какие тесты не были пройдены и когда. Кроме того, в случае непрохождения некоторые тесты могут быть пропущены с указанием тега @warning, как показано в примере ниже.
habrastorage.org/getpro/habr/upload_files/c22/910/cb9/c22910cb95fb4ccc7555d44bd8b5436b

Итоги


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

Я получил удовольствие, рассматривая обе эти платформы, и был особенно удивлен простотой интеграции checkov, а также потрясающей валидацией e2e terraform plan и вариантами нестандартного тестирования, которые предлагает terraform-compliance.

Последняя напоминает мне поведение behave, еще одной великолепной платформы тестирования BDD e2e kubernetes, с которой я работал в прошлом.

Полностью написанные на Python платформы тестирования упрощают общий обмен знаниями Python между платформами и сокращают объем интеллектуального труда для обслуживания и разработки тестов в будущем.

Если вам требуется проверка конфигурации на соответствие передовым методам, когда не требуется terraform plan, то, возможно, checkov это то, что вам нужно. В иных случаях ответом может быть платформа terraform-compliance, имеющая более богатый набор функций для валидации terraform plan. Лучше всего то, что, будучи платформой BDD, terraform-compliance очень проста в освоении.

Модульное тестирование прежде всего. Проще простого. Платформа Checkov от bridgecrewio позволяет выполнять проверку соответствия передовым методам без дополнительной настройки.

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

P.S. В компании Contino реализуется порядочное количество фантастических проектов. Если вам хотелось бы поработать над суперсовременными инфраструктурными проектами или вы ищете серьезные задачи свяжитесь с нами! Мы нанимаем персонал и ищем светлые умы на всех уровнях. Мы в компании Contino гордимся тем, что разрабатываем передовые проекты по трансформации облачных систем, предназначенные как для компаний среднего размера, так и для крупных предприятий.
Узнать подробнее о курсе Infrastructure as a code in Ansible.

Записаться на открытый урок по теме Управление Kubernetes при помощи Kubespray.

Подробнее..

Вам не нужны юнит-тесты

18.12.2020 12:07:33 | Автор: admin

Да, вы не ослышались именно так! В IT-сообществе прочно укоренилось мнение, что все эти тесты вам хоть как-то помогают, но так ли это на самом деле? Вы сами пробовали мыслить критически и анализировать это расхожее мнение? Хипстеры придумывают кучу парадигм TDD, BDD, ПДД, ГИБДД лишь чтобы создать иллюзию бурной деятельности и хоть как-то оправдать свою зарплату. Но задумайтесь, что будет, если вы (либо ваши программисты) начнете все свое время уделять исключительно написанию кода? Для тестирования есть отдельное направление и целые подразделения. Вы же не заставляете программистов писать требования, так? Тогда почему они должны писать тесты? Всех согласных и несогласных прошу проследовать внутрь поста, где я вам наглядно покажу, что юнит (и интеграционные) тесты великое зло!

Откуда вообще пошло тестирование

В стародавние времена никакого тестирования не было в принципе. Не было даже такого направления, что уж и говорить про такие термины, как блочное (модульное) и интеграционное тестирование. А про всякие e2e и, прости господи, пайплайны, я вообще молчу. И все это потому, что тестировать, собственно, было еще нечего. В те годы инженеры-программисты только пытались создать первые ЭВМ.

Как нам всем известно, первые ЭВМ были гигантских размеров, весили десятки тонн и стоили дороже этих ваших Apple MacBook Pro Retina 4k 512mb RAM 1Tb SSD Touch Bar USB Type-C. И в те времена разработчики действительно боялись, что во время работы что-нибудь пойдет не так. Думаю, вам известна история возникновения термина баг (bug) если вдруг нет, то почитайте, это очень интересно. И, так как программисты боялись всего на свете, они и придумали модульное тестирование.

Времена менялись, менялись и ЭВМ. Тестирование тоже менялось. Помимо блочных тестов, возникло также и целое направление, которое впоследствии получило название Quality Assurance.

Но разработчики тоже менялись. В наше время становится смешно от мысли, что кто-то боится запустить программу, потому что от этого может загореться сервер. В 2020 году программисты не боятся запускать свои программы. А если нет страха зачем тестировать?

Современные реалии

Повторю свой вопрос если ваш MacBook (или Xiaomi) не взорвется из-за ошибки в коде, зачем тогда тестировать? Вы просто запускаете и наслаждаетесь результатом. Предвосхищая ваше негодование по поводу дороговизны ошибок для заказчика пускай тестированием занимаются специально обученные люди.

На последнем хочу слегка заострить внимание. В современной разработке основная стоимость кроется не в аппаратном, а в программном обеспечении. И ошибки по-прежнему стоят дорого. Но ответственность за эти ошибки плавно перекочевала с плеч разработчиков на плечи тестировщиков. Как-никак, это они назвали себя Quality Assurance а раз проводишь проверку качества, делай это качественно \_()_/

В конце концов, отдел разработки называется Software Development, а не Unmistakable Development. Мы никому ничего не обещаем.

Хороший программист уверен в себе

Когда вы покрываете свой код юнит-тестами, вы будто заявляете всему миру: Смотрите, я не уверен в том, что оно работает. Будут ли вас за такие мысли уважать более опытные коллеги и начальник? Будет ли вам доверять заказчик?

Просто откройте свой проект и задумайтесь. Вы умный и образованный человек. Вы хороши собой. Зачем вам балласт в виде модульных тестов, которые, ко всему прочему, еще и портят вашу репутацию?

Задание: Прямо сейчас скажите себе Я уверен в качестве своего кода и удалите все юнит-тесты из проекта.

В идеальном случае, вы должны удалить все тесты прямо из ветки master, однако, это может быть запрещено правилами репозитория. В таком случае вам придется делать pull request и убеждать своих старомодных коллег в том, что тесты пережиток прошлого. Однако, как только ваши соратники примкнут к вашему мировоззрению, на проекте воцарится гармония.

Запомните несколько простых постулатов:

  1. Хороший программист не пишет тесты, так как не сомневается в качестве своей работы.

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

  3. Тщетные попытки найти ошибки в вашем коде оставьте тестировщикам.

Тесты отнимают время

Время программистов дорогое. Время тестировщиков дешевое. Какой тогда смысл заставлять программистов писать тесты? Это невыгодно даже с финансовой точки зрения.

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

В конце концов, тестировщики тоже люди, и не стоит лишать их хлеба. Если вы будете сами тестировать свой код, весь отдел QA станет попросту не нужен, и бедолаг уволят. Вспомните, что было во времена промышленного переворота в США когда машины начали заменять людей на производстве, начались самые настоящие бунты.

Поэтому не будьте машиной. Не провоцируйте тестировщиков на поднятие бунта.

Парадигмы запутывают

Unit-testing, Integration Testing, End 2 End, Pipelines, CI, CD что вы еще придумаете, лишь бы не работать? Есть мнение, что когда программист выгорает и начинает прокрастинировать, он идет настраивать пайплайн.

Все эти парадигмы и термины запутывают. Опять же, ваши мозговые ресурсы мгновенно тратятся, когда вы начинаете думать про e2e вместо того, чтобы просто программировать. Все это пугающе отвлекает вас и не дает сосредоточиться на главном на написании кода.

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

Вам не нужно знать ничего лишнего. Иными словами, вы программируете. Тестировщики тестируют. Девопсы ковыряются во всяких скриптах на bash. Менеджеры ну, менеджеры есть менеджеры.

Delivery In Time

Я предлагаю ввести лишь одно простое понятие: DIT Delivery In Time. Это схоже с известной парадигмой ППКБ (Просто Пиши Код Б****), но звучит гораздо современнее и толерантнее. Парадигма ППКБ ставит программистов в центр мироздания и не считается с работой других членов команды. Это, как минимум, неуважительно. В DIT мы верим, что программисты скромные служители, единственной целью которых является написание кода. При всем этом, мы не закрываем глаза на работу других коллег и уважаем их труды. Просто мы считаем, что каждый должен быть занят своим делом: программисты программировать, тестировщики тестировать, и тд. Когда каждый будет делать то, чему обучен, сроки перестанут срываться.

Парадигма DIT предлагает сплошные бонусы заказчикам. Они могут нанять исключительно разработчиков, чтобы те ППКБ (просто писали код), и все их бюджеты будут направлены непосредственно на создание продукта. При желании заказчик может также нанять и тестировщиков. То есть, простите, Quality Assurance инженеров. А может и не нанимать и запустить тестирование в продакшене.

Я однажды слышал один забавный диалог:

Сколько человек сейчас тестирует нашу систему?
Один человек.
Мы только что выкатили ее на прод.
Ну значит, нашу систему тестирует 1000 человек.

И это правильно. Можете платить штатным тестировщикам, а можете нанять тысячи внештатных совершенно бесплатно.

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

Совет: Чтобы не срывать сроки и доставлять вовремя лучше нанять разработчиков, а тестированием заниматься на продакшене. Даже если что-то пойдет не так, вы всегда можете возразить, что соблюли сроки, как и было обговорено. А о большем и не договаривались.

Про интеграционное тестирование

С модульным тестированием вроде разобрались, настало время поговорить о тестировании интеграционном. Именно оно отнимает больше всего времени.

Когда-то я был молодым и верил в то, что тесты (юнит, интеграционные, да всякие) несут добро. Хорошо написанные тесты гарантировали отсутствие регрессии, то есть вы могли изменять и рефакторить код без боязни, что вы где-то ошиблись. Выглядит здорово, правда? Делаешь кучу правок, запускаешь тесты и смотришь, допустил ли ты ошибку.

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

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

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

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

Просто будьте собой

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

Просто будьте собой!

В качестве заключения

Если вы дочитали до этого момента и не бросились писать гневный комментарий, то либо вы прекрасно понимаете важность тестов и сразу заметили иронию, либо просто обратили внимание на теги :)

Друзья, это были вредные советы. Однако, навеяны они были реальными историями. Сейчас у меня на рабочих проектах хорошее покрытие, и это действительно сильно облегчает работу. Но однажды, давно, я попал на проект с покрытием бранчей в районе 15%. Мы не вылезали из регрессии. Примерно тогда я начал осознавать всю важность тестов и стал задумываться о том, почему некоторые из нас ими пренебрегают.

А какой процент покрытия в ваших проектах? Дотягивает ли покрытие линий/веток до 80%? Или болтается где-то в районе 30? Если у вас частая регрессия и низкое покрытие вы догадываетесь, что стоит изменить?

Я понимаю, что подобный пост не совсем по тематике Хабра. Но сегодня пятница, к тому же на носу Новый Год, так что давайте немного расслабимся :)

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

Подробнее..

Разбираемся, безопасно ли стрелять по проду и чем могут быть полезны дашборды

24.12.2020 14:22:40 | Автор: admin
На подходе полезные видео с конференции ЮMoneyDay от специалистов по тестированию. Если заглянете под кат, то узнаете:

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




Стрельбы по проду. Как реализовали и что получили


Виктор Бодров, исследователь производительности
Какие задачи помогают решить исследования производительности, и как их результаты влияют на развитие ЮMoney.

0:54 О спикере и работе команды
1:24 Почему мы стреляем не по стенду, а по проду
2:37 Чем стрелять? Как мониторить
4:07 Как все начиналось
6:55 Нам понадобились свои пользователи
8:14 А что там с платежами?
11:37 Платежи картами
15:08 Работа с контрагентами
18:47 Стрельбы по компонентам
21:01 Capacity control
23:38 Автострельбы




Почему дашборды могут быть полезны

Егор Иванов, старший специалист по автоматизации тестирования

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

1:04 Что такое дашборд? Примеры из жизни. Определение термина, основные типы.
3:01 Знакомство с командой интеграционного тестирования. Схема взаимодействия инструментов: Jira, Autorun, Locker, Pinger, Jenkins
6:36 Что делать, когда что-то идет не так роль дежурного
9:49 Дашборд дежурного: мастштабирование задач, использование Grafana
10:58 Как происходит отсылка метрик. Типы метрик.
12:20 Процесс отправки метрик из Java и sh
13:03 Как построить дашборд? Как можно использовать дашборды?
13:23 Пример 1 дашборд как визуализатор метрик
17:00 Пример 2 дашборд как мотиватор
20:34 Пример 3 дашборд для анализа
24:00 Пример 4 дашборд для экономии времени
25:59 Подведение итогов: что мы получили от внедрения дашбордов

Дополнительно
Расшифровка доклада Сила дашбордов



Все доклады с большой ИТ-конференции ЮMoneyDay. На подходе материалы про PM, тестирование и мобильную разработку.

Подробнее..

Перевод Мы отрендерили миллион страниц, чтобы понять, из-за чего тормозит веб

30.12.2020 12:20:04 | Автор: admin
Мы отрендерили 1 миллион самых популярных страниц веба, фиксируя все мыслимые метрики производительности, записывая все ошибки и замечая все запрошенные URL. Похоже, таким образом мы создали первый в мире набор данных, связывающий производительность, ошибки и использование библиотек в сети. В этой статье мы проанализируем, что наши данные могут сообщить о создании высокопроизводительных веб-сайтов.


  • Посещён 1 миллион страниц
  • Записано по 65 метрик каждой страницы
  • Запрошен 21 миллион URL
  • Зафиксировано 383 тысячи ошибок
  • Сохранено 88 миллионов глобальных переменных

Можно ли превзойти наш анализ? Мы опубликовали наш набор данных на Kaggle, поэтому вы можете обработать данные самостоятельно.

Зачем рендерить миллион веб-страниц?


Сегодня распространено мнение о том, что веб почему-то стал более медленным и забагованным, чем 15 лет назад. Из-за постоянно растущей кучи JavaScript, фреймворков, веб-шрифтов и полифилов, мы съели все преимущества, которые даёт нам увеличение возможностей компьютеров, сетей и протоколов. По крайней мере, так утверждает молва. Мы хотели проверить, правда ли это на самом деле, а также найти общие факторы, которые становятся причиной торможения и поломок сайтов в 2020 году.

Общий план был простым: написать скрипт для веб-браузера, заставить его рендерить корневую страницу миллиона самых популярных доменов и зафиксировать все мыслимые метрики: время рендеринга, количество запросов, перерисовку, ошибки JavaScript, используемые библиотеки и т.п. Имея на руках все эти данные, мы могли бы начать задаваться вопросами о том, как один фактор корреллирует с другим. Какие факторы сильнее всего влияют на замедление рендеринга? Какие библиотеки увеличивают время до момента возможности взаимодействия со страницей (time-to-interactive)? Какие ошибки встречаются наиболее часто, и что их вызывает?

Для получения данных достаточно было написать немного кода, позволяющего Puppeteer управлять по скрипту браузером Chrome, запустить 200 инстансов EC2, отрендерить миллион веб-страниц за пару выходных дней и молиться о том, что мы действительно правильно поняли ценообразование AWS.

Общие значения



Протокол, используемый для корневого HTML-документа

HTTP 2 сейчас более распространён, чем HTTP 1.1, однако HTTP 3 по-прежнему встречается редко. (Примечание: мы считаем сайты, использующие протокол QUIC как использующие HTTP 3, даже если Chrome иногда говорит, что это HTTP 2 + QUIC.) Это данные для корневого документа, для связанных ресурсов значения выглядят немного иначе.


Протокол, используемый для связанных ресурсов

Для связанных ресурсов HTTP 3 используется почти в 100 раз чаще. Как такое может быть? Дело в том, что все сайты ссылаются на одно и то же:


Самые популярные URL ссылок

Есть несколько скриптов, связанных с большой частью веб-сайтов. И это ведь означает, что эти ресурсы будут в кэше, правильно? Увы, больше это не так: с момента выпуска Chrome 86 ресурсы, запрашиваемые с разных доменов, не имеют общего кэша. Firefox планирует реализовать такой же подход. Safari разделяет свой кэш уже многие годы.

Из-за чего тормозит веб: прогнозируем time-to-interactive


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


Корреляции метрик с dominteractive

По сути, каждая метрика позитивно коррелирует с dominteractive, за исключением переменной 01, обозначающей использование HTTP2 или более старшей версии. Многие из этих метрик также положительно коррелируют друг с другом. Нам нужен более сложный подход, чтобы выйти на отдельные факторы, влияющие на высокий показатель time-to-interactive.

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


Диаграмма размаха метрик таймингов. Оранжевая линия это медиана, ящик ограничивает с 25-го по 75-й перцентиль.

Один из способов определения отдельных факторов, влияющих на высокий показатель time-to-interactive выполнение линейной регрессии, при которой мы прогнозируем dominteractive на основании других метрик. Это означает, что мы назначаем каждой метрике вес и моделируем время dominteractive страницы как взвешенную сумму других метрик, плюс некая константа. Алгоритм оптимизации расставляет веса так, чтобы минимизировать погрешность прогноза для всего набора данных. Величина весов, определённая регрессией, сообщит нам что-то о том, как каждая метрика влияет на медленность страницы.

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


Коэффициенты регрессии для метрик, прогнозирующей dominteractive

Числа в скобках это коэффициенты регрессии, выведенные алгоритмом оптимизации. Можно интерпретировать их как величины в миллисекундах. Хотя к точным значениям нужно относиться скептически (см. примечание ниже), интересно увидеть порядок, назначенный каждому аспекту. Например, модель прогнозирует замедление в 354 мс для каждого перенаправления (редиректа), необходимого для доставки основного документа. Когда основной HTML-документ передаётся по HTTP2 или более высокой версии, модель прогнозирует снижение time-to-interactive на 477 мс. Для каждого запроса, причиной которого стал документ, она прогнозирует добавление 16 мс.

В процессе интерпретации коэффициентов регрессии нужно помнить о том, что мы работаем с упрощённой моделью реальности. На самом деле time-to-interactive не определяется взвешенной суммой этих входящих метрик. Очевидно, что есть причинные факторы, которые модель выявить не может. Бесспорной проблемой являются искажающие факторы. Например, если загрузка основного документа по HTTP2 коррелирует с загрузкой других запросов по HTTP2, то модель встроит это преимущество в веса main_doc_is_http2_or_greater, даже если ускорение вызвано запросами не к основному документу. Нам нужно быть аккуратными, сопоставляя показания модели с выводами о реальном положении дел.

Как на dominteractive влияет версия протокола HTTP?


Вот любопытный график dominteractive, разделённый по версиям протокола HTTP, используемым для доставки корневой HTML-страницы.


Диаграмма размаха dominteractive, разделённая по версиям протокола HTTP первого запроса. Оранжевая линия медиана, ящик ограничивает с 25-го по 75-й перцентиль. Проценты в скобках доля запросов, выполненная по этому протоколу.

Существует крошечная доля сайтов, до сих пор доставляемых по HTTP 0.9 и 1.0. И эти сайты оказываются быстрыми. Похоже, мы не можем отвязаться от того факта, что протоколы стали быстрее, а поэтому довольные программисты пользуются этим ускорением, доставляя в браузер больше данных.

Это показатели для версии протокола, используемой для доставки корневой HTML-страницы. А если мы рассмотрим влияние протокола, используемого для ресурсов, на которые ссылается этот документ? Если провести регрессию для количества запросов по версии протокола, то получится следующее.


Коэффициенты прогнозирующей dominteractive регрессии для количества запросов по версии протокола

Если бы мы поверили этим показателям, то пришли бы к выводу, что перемещение запрашиваемых ресурсов при переходе с HTTP 1.1 на 2 ускоряется 1,8 раза, а при переходе с HTTP 2 на 3 замедляется в 0,6 раза. Действительно ли протокол HTTP 3 более медленный? Нет: наиболее вероятное объяснение заключается в том, что HTTP 3 встречается реже, а немногочисленные ресурсы, передаваемые по HTTP 3 (например, Google Analytics), больше среднего влияют на dominteractive.

Как на dominteractive влияет тип контента?


Давайте спрогнозируем time-to-interactive в зависимости от количества переданных данных при разделении по типам передаваемых данных.


Коэффициенты прогнозирующей dominteractive регрессии для килобайтов, переданных инициатором запроса

Вот похожая регрессия, на этот раз здесь рассматривается количество запросов по типу инициатора запроса.


Коэффициенты прогнозирующей dominteractive регрессии для количества запросов, переданных инициатором запроса

Здесь запросы разделены по инициаторам запросов. Очевидно, что не все запросы равны. Запросы, вызванные элементом компоновки (например, CSS или файлов favicon), и запросы, вызванные CSS (например, шрифтов и других CSS), а также скриптами и iframe значительно замедляют работу. Выполнение запросов по XHR и fetch прогнозируемо выполняются быстрее, чем базовое время dominteractive (вероятно, потому, что эти запросы почти всегда асинхронны). CSS и скрипты часто загружаются так, что препятствуют рендерингу, поэтому неудивительно, что они связаны с замедлением time-to-interactive. Видео относительно малозатратно.

Выводы


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

  • Делайте как можно меньше запросов. Количество запросов важнее, чем количество запрошенных килобайтов.
  • По возможности делайте запросы по HTTP2 или более старшей версии протокола.
  • Стремитесь по возможности избегать запросов, мешающих рендерингу, старайтесь пользоваться асинхронной загрузкой.

Библиотеки


Чтобы разобраться, какие библиотеки используются на странице, мы воспользовались следующим подходом: на каждом сайте мы фиксировали глобальные переменные (например, свойства объекта окна). Далее каждая глобальная переменная, встречавшаяся более шести тысяч раз связывалась (когда это было возможно) с библиотекой JavaScript. Это очень кропотливая работа, но поскольку в наборе данных также содержались запрашиваемые URL для каждой страницы, мы могли изучить пересечение встречаемых переменных и запросов URL, и этого часто было достаточно, чтобы определить, какая библиотека задаёт каждую из глобальных переменных. Глобальные переменные, которые нельзя было с уверенностью связать с какой-то одной библиотекой, игнорировались. Такая методология в определённой мере обеспечивает неполный учёт: библиотеки JS не обязаны оставлять что-то в глобальном пространстве имён. Кроме того, она обладает некоторым шумом, когда разные библиотеки задают одно и то же свойство, и этот факт при привязке библиотек не учитывался.

Какие библиотеки JavaScript используются сегодня наиболее часто? Если следить за темами конференций и постов, было бы вполне логично предположить, что это React, Vue и Angular. Однако в нашем рейтинге они совершенно далеки от вершины.

10 самых используемых библиотек



Просмотреть полный список библиотек по уровню использования

Да, на вершине находится старый добрый jQuery. Первая версия JQuery появилась в 2006 году, то есть 14 человеческих лет назад, но в годах JavaScript это гораздо больше. Если измерять в версиях Angular, то это произошло, вероятно, сотни версий назад. 2006 год был совершенно иным временем. Самым популярным браузером был Internet Explorer 6, крупнейшей социальной сетью MySpace, а скруглённые углы на веб-страницах были такой революцией, что люди называли их Веб 2.0. Основная задача JQuery обеспечение кроссбраузерной совместимости, которая в 2020 году совершенно отличается от ситуации 2006 года. Тем не менее, 14 лет спустя аж половина веб-страниц из нашей выборки загружала jQuery.

Забавно, что 2,2% веб-сайтов выбрасывали ошибку, потому что JQuery не загружался.

Судя по этой десятке лучших, наши браузеры в основном выполняют аналитику, рекламу и код для совместимости со старыми браузерами. Почему-то 8% веб-сайтов определяют полифил setImmediate/clearImmediate для функции, реализация которой даже не планируется ни в одном из браузеров.

Прогнозирование time-to-interactive по использованию библиотек


Мы снова запустим линейную регрессию, прогнозирующую dominteractive по наличию библиотек. Входящими данными регрессии будет вектор X, где X.length == количество библиотек, где X[i] == 1.0, если библиотека i присутствует, X[i] == 0.0, если её нет. Разумеется, мы знаем, что на самом деле dominteractive не определяется наличием или отсутствием конкретных библиотек. Однако моделирование каждой библиотеки как имеющей вклад в замедление и выполнение регрессии для сотен тысяч примеров всё равно позволяет нам обнаружить интересные находки.

Наилучшие и наихудшие для time-to-interactive библиотеки по коэффициентам регрессии



Посмотреть полный список библиотек по коэффициентам регрессии, прогнозирующей dominteractive

Отрицательные коэффициенты означают, что модель прогнозирует меньшее time-to-interactive при наличии этих библиотек, чем при их отсутствии. Разумеется, это не означает, что добавление этих библиотек ускорит ваш сайт, это только значит, что сайты с этими библиотеками оказываются быстрее, чем некая базовая линия, установленная моделью. Результаты могут быть столь же социологическими, сколь и техническими. Например библиотеки для ленивой загрузки по прогнозу обеспечивают снижение time-to-interactive. Возможно, это просто вызвано тем, что страницы с этими библиотеками созданы программистами, потратившими время на оптимизацию с целью быстрой загрузки страниц, поскольку ленивая загрузка приводит непосредственно к этому. Мы не можем отвязать подобные факторы от данной структуры.

Наилучшие и наихудшие для времени onload библиотеки по коэффициентам регрессии


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


Посмотреть полный список библиотек по коэффициентам регрессии, прогнозирующей onloadtime

Наилучшие и наихудшие библиотеки для jsheapusedsize по коэффициентам регрессии


Это прогноз для размера в мегабайтах кучи, используемой JavaScript.


Посмотреть полный список библиотек по коэффициентам регрессии, прогнозирующей jsheapusedsize

Комментаторы в Интернете с любят высокомерно говорить, что корреляция не равна причинно-следственной связи, и мы в самом деле не можем напрямую вывести из этой модели причинности. При интерпретировании коэффициентов следует быть очень аккуратными, частично это вызвано тем, что может участвовать множество искажающих факторов. Однако этого определённо достаточно для того, чтобы задуматься. Тот факт, что модель связывает замедление time-to-interactive на 982 мс с наличием jQuery, и что половина сайтов загружает этот скрипт, должен навести нас на определённые мысли. Если вы оптимизируете свой сайт, то сверка его списка зависимостей с представленными здесь рейтингами и коэффициентами определённо обеспечит вам приличный показатель того, устранение какой зависимости даст вам наибольший рост производительности.

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



На правах рекламы


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

Подробнее..

21 и 22 января два бесплатных онлайн-митапа (QA и iOS)

14.01.2021 16:09:15 | Автор: admin

Привет! Новый год новые митапы.

Уже через неделю мы проведём два первых в этом году митапа, первый из которых будет полезен тестировщикам, а второй iOS-разработчикам. Спикеры будут из Альфа-Банка, а вот участники круглого стола по теме из Сбера, Тинькофф и Райффайзенбанка.

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

Программа под катом

21 января, 19.00 МСК QAчественное общение

В программе 3 доклада от Альфа-Банка и викторина с призами. Мы постараемся сделать эти пару часов максимально полезными для всех собравшихся.

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

19:05-19:40, Дмитрий Гадеев, Kubernetes. Жизнь ДО и ПОСЛЕ

Руководитель направления сопровождения инфраструктурных сервисов, Альфа-Банк

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

19:40-20:10, Ксения Коломиец, Сайт-конструктор. Тестирование без боли

Старший специалист по тестированию, Альфа-Банк

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

20:10-20:40, Александр Долинский, Тестирование API в большой команде

Руководитель группы тестирования, Альфа-Банк

Пройдем по истории развития тестирования мобильного банка. Соберем некоторые грабли большого проекта.

20:40-21:00, Викторина в Kahoot с розыгрышем призов

Страница регистрации

22 января, 19.00 МСК Mobile Talks

19:05-19:40, Василий Пономарев, Техническая сторона UI-компонентов

iOS-разработчик, Альфа-Банк

Василий расскажет о реализации UI-компонентов, принятых на вооружение архитектурных решениях. А также покажет пример из практики и разберет преимущества и недостатки данного подхода.

19:40-20:15, Евгений Онуфрейчик, Новый разработчик. От старта до выхода на орбиту

iOS-разработчик, Альфа-Банк

Женя расскажет о процессе погружения нового разработчика в проект мобильного приложения Альфа-Банка, о сложностях адаптации и как мы с ними справляемся. Объяснит, как выглядит процесс со стороны новичка что он ощущает и как понимает происходящее. А также расскажет, какова в этом роль и зоны ответственности наставника.

20:15-20:35, Викторина в Kahoot с розыгрышем призов

20:35-21:35, Круглый стол Качество vs скорость разработки как найти баланс?

  • Сергей Нанаев, Head of iOS, Альфа-Банк

  • Роман Голофаев, Mobile Community Lead, Райффайзенбанк

  • Александр Поломодов, Руководитель управления разработки цифровых экосистем, Тинькофф

  • Иван Бубнов, Руководитель направления мобильной разработки, Сбер

Страница регистрации.

Подробнее..

Сервисы с Apache Kafka и тестирование

09.01.2021 18:16:01 | Автор: admin

Когда сервисы интегрируются при помощи Kafka очень удобно использовать REST API, как универсальный и стандартный способ обмена сообщениями. При увеличении количества сервисов сложность коммуникаций увеличивается. Для контроля можно и нужно использовать интеграционное тестирование. Такие библиотеки как testcontainers или EmbeddedServer прекрасно помогают организовать такое тестирование. Существуют много примеров для micronaut, Spring Boot и т.д. Но в этих примерах опущены некоторые детали, которые не позволяют с первого раза запустить код. В статье приводятся примеры с подробным описанием и ссылками на код.


Пример


Для простоты можно принять такой REST API.


/runs POST-метод. Инициализирует запрос в канал связи. Принимает данные и возвращает ключ запроса.
/runs/{key}/status GET-метод. По ключу возвращает статус запроса. Может принимать следующие значения: UNKNOWN, RUNNING, DONE.
/runs /{key} GET-метод. По ключу возвращает результат запроса.


Подобный API реализован у livy, хотя и для других задач.


Реализация


Будут использоваться: micronaut, Spring Boot.


micronaut


Контроллер для API.


import io.micronaut.http.annotation.Body;import io.micronaut.http.annotation.Controller;import io.micronaut.http.annotation.Get;import io.micronaut.http.annotation.Post;import io.reactivex.Maybe;import io.reactivex.schedulers.Schedulers;import javax.inject.Inject;import java.util.UUID;@Controller("/runs")public class RunController {    @Inject    RunClient runClient;    @Inject    RunCache runCache;    @Post    public String runs(@Body String body) {        String key = UUID.randomUUID().toString();        runCache.statuses.put(key, RunStatus.RUNNING);        runCache.responses.put(key, "");        runClient.sendRun(key, new Run(key, RunType.REQUEST, "", body));        return key;    }    @Get("/{key}/status")    public Maybe<RunStatus> getRunStatus(String key) {        return Maybe.just(key)                .subscribeOn(Schedulers.io())                .map(it -> runCache.statuses.getOrDefault(it, RunStatus.UNKNOWN));    }    @Get("/{key}")    public Maybe<String> getRunResponse(String key) {        return Maybe.just(key)                .subscribeOn(Schedulers.io())                .map(it -> runCache.responses.getOrDefault(it, ""));    }}

Отправка сообщений в kafka.


import io.micronaut.configuration.kafka.annotation.*;import io.micronaut.messaging.annotation.Body;@KafkaClientpublic interface RunClient {    @Topic("runs")    void sendRun(@KafkaKey String key, @Body Run run);}

Получение сообщений из kafka.


import io.micronaut.configuration.kafka.annotation.*;import io.micronaut.messaging.annotation.Body;import javax.inject.Inject;@KafkaListener(offsetReset = OffsetReset.EARLIEST)public class RunListener {    @Inject    RunCalculator runCalculator;    @Topic("runs")    public void receive(@KafkaKey String key, @Body Run run) {        runCalculator.run(key, run);    }}

Обработка сообщений происходит в RunCalculator. Для тестов используется особая реализация, в которой происходит переброска сообщений.


import io.micronaut.context.annotation.Replaces;import javax.inject.Inject;import javax.inject.Singleton;import java.util.UUID;@Replaces(RunCalculatorImpl.class)@Singletonpublic class RunCalculatorWithWork implements RunCalculator {    @Inject    RunClient runClient;    @Inject    RunCache runCache;    @Override    public void run(String key, Run run) {        if (RunType.REQUEST.equals(run.getType())) {            String runKey = run.getKey();            String newKey = UUID.randomUUID().toString();            String runBody = run.getBody();            runClient.sendRun(newKey, new Run(newKey, RunType.RESPONSE, runKey, runBody + "_calculated"));        } else if (RunType.RESPONSE.equals(run.getType())) {            runCache.statuses.replace(run.getResponseKey(), RunStatus.DONE);            runCache.responses.replace(run.getResponseKey(), run.getBody());        }    }}

Тест.


import io.micronaut.http.HttpRequest;import io.micronaut.http.client.HttpClient;import static org.junit.jupiter.api.Assertions.assertEquals;public abstract class RunBase {    void run(HttpClient client) {        String key = client.toBlocking().retrieve(HttpRequest.POST("/runs", "body"));        RunStatus runStatus = RunStatus.UNKNOWN;        while (runStatus != RunStatus.DONE) {            runStatus = client.toBlocking().retrieve(HttpRequest.GET("/runs/" + key + "/status"), RunStatus.class);            try {                Thread.sleep(500);            } catch (InterruptedException e) {                e.printStackTrace();            }        }        String response = client.toBlocking().retrieve(HttpRequest.GET("/runs/" + key), String.class);        assertEquals("body_calculated", response);    }}

Для использования EmbeddedServer необходимо.


Подключить библиотеки:


testImplementation("org.apache.kafka:kafka-clients:2.6.0:test")testImplementation("org.apache.kafka:kafka_2.12:2.6.0")testImplementation("org.apache.kafka:kafka_2.12:2.6.0:test")

Тест может выглядеть так.


import io.micronaut.context.ApplicationContext;import io.micronaut.http.client.HttpClient;import io.micronaut.runtime.server.EmbeddedServer;import org.junit.jupiter.api.Test;import java.util.HashMap;import java.util.Map;public class RunKeTest extends RunBase {    @Test    void test() {        Map<String, Object> properties = new HashMap<>();        properties.put("kafka.bootstrap.servers", "localhost:9092");        properties.put("kafka.embedded.enabled", "true");        try (EmbeddedServer embeddedServer = ApplicationContext.run(EmbeddedServer.class, properties)) {            ApplicationContext applicationContext = embeddedServer.getApplicationContext();            HttpClient client = applicationContext.createBean(HttpClient.class, embeddedServer.getURI());            run(client);        }    }}

Для использования testcontainers необходимо.


Подключить библиотеки:


implementation("org.testcontainers:kafka:1.14.3")

Тест может выглядеть так.


import io.micronaut.context.ApplicationContext;import io.micronaut.http.client.HttpClient;import io.micronaut.runtime.server.EmbeddedServer;import org.junit.jupiter.api.Test;import org.testcontainers.containers.KafkaContainer;import org.testcontainers.utility.DockerImageName;import java.util.HashMap;import java.util.Map;public class RunTcTest extends RunBase {    @Test    public void test() {        try (KafkaContainer kafka = new KafkaContainer(DockerImageName.parse("confluentinc/cp-kafka:5.5.3"))) {            kafka.start();            Map<String, Object> properties = new HashMap<>();            properties.put("kafka.bootstrap.servers", kafka.getBootstrapServers());            try (EmbeddedServer embeddedServer = ApplicationContext.run(EmbeddedServer.class, properties)) {                ApplicationContext applicationContext = embeddedServer.getApplicationContext();                HttpClient client = applicationContext.createBean(HttpClient.class, embeddedServer.getURI());                run(client);            }        }    }}

Spring Boot


Контроллер для API.


import org.springframework.beans.factory.annotation.Autowired;import org.springframework.web.bind.annotation.*;import java.util.UUID;@RestController@RequestMapping("/runs")public class RunController {    @Autowired    private RunClient runClient;    @Autowired    private RunCache runCache;    @PostMapping()    public String runs(@RequestBody String body) {        String key = UUID.randomUUID().toString();        runCache.statuses.put(key, RunStatus.RUNNING);        runCache.responses.put(key, "");        runClient.sendRun(key, new Run(key, RunType.REQUEST, "", body));        return key;    }    @GetMapping("/{key}/status")    public RunStatus getRunStatus(@PathVariable String key) {        return runCache.statuses.getOrDefault(key, RunStatus.UNKNOWN);    }    @GetMapping("/{key}")    public String getRunResponse(@PathVariable String key) {        return runCache.responses.getOrDefault(key, "");    }}

Отправка сообщений в kafka.


import com.fasterxml.jackson.core.JsonProcessingException;import com.fasterxml.jackson.databind.ObjectMapper;import org.springframework.beans.factory.annotation.Autowired;import org.springframework.kafka.core.KafkaTemplate;import org.springframework.stereotype.Component;@Componentpublic class RunClient {    @Autowired    private KafkaTemplate<String, String> kafkaTemplate;    @Autowired    private ObjectMapper objectMapper;    public void sendRun(String key, Run run) {        String data = "";        try {            data = objectMapper.writerWithDefaultPrettyPrinter().writeValueAsString(run);        } catch (JsonProcessingException e) {            e.printStackTrace();        }        kafkaTemplate.send("runs", key, data);    }}

Получение сообщений из kafka.


import com.fasterxml.jackson.core.JsonProcessingException;import com.fasterxml.jackson.databind.ObjectMapper;import org.apache.kafka.clients.consumer.ConsumerRecord;import org.springframework.beans.factory.annotation.Autowired;import org.springframework.kafka.annotation.KafkaListener;import org.springframework.stereotype.Component;@Componentpublic class RunListener {    @Autowired    private ObjectMapper objectMapper;    @Autowired    private RunCalculator runCalculator;    @KafkaListener(topics = "runs", groupId = "m-group")    public void receive(ConsumerRecord<?, ?> consumerRecord) {        String key = consumerRecord.key().toString();        Run run = null;        try {            run = objectMapper.readValue(consumerRecord.value().toString(), Run.class);        } catch (JsonProcessingException e) {            e.printStackTrace();        }        runCalculator.run(key, run);    }}

Обработка сообщений происходит в RunCalculator. Для тестов используется особая реализация, в которой происходит переброска сообщений.


import org.springframework.beans.factory.annotation.Autowired;import org.springframework.stereotype.Component;import java.util.UUID;@Componentpublic class RunCalculatorWithWork implements RunCalculator {    @Autowired    RunClient runClient;    @Autowired    RunCache runCache;    @Override    public void run(String key, Run run) {        if (RunType.REQUEST.equals(run.getType())) {            String runKey = run.getKey();            String newKey = UUID.randomUUID().toString();            String runBody = run.getBody();            runClient.sendRun(newKey, new Run(newKey, RunType.RESPONSE, runKey, runBody + "_calculated"));        } else if (RunType.RESPONSE.equals(run.getType())) {            runCache.statuses.replace(run.getResponseKey(), RunStatus.DONE);            runCache.responses.replace(run.getResponseKey(), run.getBody());        }    }}

Тест.


import com.fasterxml.jackson.databind.ObjectMapper;import org.springframework.http.MediaType;import org.springframework.test.web.servlet.MockMvc;import org.springframework.test.web.servlet.MvcResult;import org.springframework.test.web.servlet.request.MockMvcRequestBuilders;import static org.junit.jupiter.api.Assertions.assertEquals;import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.status;public abstract class RunBase {    void run(MockMvc mockMvc, ObjectMapper objectMapper) throws Exception {        MvcResult keyResult = mockMvc.perform(MockMvcRequestBuilders.post("/runs")                .content("body")                .contentType(MediaType.APPLICATION_JSON)                .accept(MediaType.APPLICATION_JSON))                .andExpect(status().isOk())                .andReturn();        String key = keyResult.getResponse().getContentAsString();        RunStatus runStatus = RunStatus.UNKNOWN;        while (runStatus != RunStatus.DONE) {            MvcResult statusResult = mockMvc.perform(MockMvcRequestBuilders.get("/runs/" + key + "/status")                    .contentType(MediaType.APPLICATION_JSON)                    .accept(MediaType.APPLICATION_JSON))                    .andExpect(status().isOk())                    .andReturn();            runStatus = objectMapper.readValue(statusResult.getResponse().getContentAsString(), RunStatus.class);            try {                Thread.sleep(500);            } catch (InterruptedException e) {                e.printStackTrace();            }        }        String response = mockMvc.perform(MockMvcRequestBuilders.get("/runs/" + key)                .contentType(MediaType.APPLICATION_JSON)                .accept(MediaType.APPLICATION_JSON))                .andExpect(status().isOk())                .andReturn().getResponse().getContentAsString();        assertEquals("body_calculated", response);    }}

Для использования EmbeddedServer необходимо.


Подключить библиотеки:


<dependency>    <groupId>org.springframework.kafka</groupId>    <artifactId>spring-kafka</artifactId>    <version>2.5.10.RELEASE</version></dependency><dependency>    <groupId>org.springframework.kafka</groupId>    <artifactId>spring-kafka-test</artifactId>    <version>2.5.10.RELEASE</version>    <scope>test</scope></dependency>

Тест может выглядеть так.


import com.fasterxml.jackson.databind.ObjectMapper;import org.junit.jupiter.api.Test;import org.springframework.beans.factory.annotation.Autowired;import org.springframework.boot.test.autoconfigure.web.servlet.AutoConfigureMockMvc;import org.springframework.boot.test.context.SpringBootTest;import org.springframework.boot.test.context.TestConfiguration;import org.springframework.context.annotation.Bean;import org.springframework.context.annotation.Import;import org.springframework.kafka.test.context.EmbeddedKafka;import org.springframework.test.web.servlet.MockMvc;@AutoConfigureMockMvc@SpringBootTest@EmbeddedKafka(partitions = 1, brokerProperties = {"listeners=PLAINTEXT://localhost:9092", "port=9092"})@Import(RunKeTest.RunKeTestConfiguration.class)public class RunKeTest extends RunBase {    @Autowired    private MockMvc mockMvc;    @Autowired    private ObjectMapper objectMapper;    @Test    void test() throws Exception {        run(mockMvc, objectMapper);    }    @TestConfiguration    static class RunKeTestConfiguration {        @Autowired        private RunCache runCache;        @Autowired        private RunClient runClient;        @Bean        public RunCalculator runCalculator() {            RunCalculatorWithWork runCalculatorWithWork = new RunCalculatorWithWork();            runCalculatorWithWork.runCache = runCache;            runCalculatorWithWork.runClient = runClient;            return runCalculatorWithWork;        }    }}

Для использования testcontainers необходимо.


Подключить библиотеки:


<dependency>    <groupId>org.testcontainers</groupId>    <artifactId>kafka</artifactId>    <version>1.14.3</version>    <scope>test</scope></dependency>

Тест может выглядеть так.


import com.fasterxml.jackson.databind.ObjectMapper;import org.apache.kafka.clients.consumer.ConsumerConfig;import org.apache.kafka.clients.producer.ProducerConfig;import org.apache.kafka.common.serialization.StringDeserializer;import org.apache.kafka.common.serialization.StringSerializer;import org.junit.ClassRule;import org.junit.jupiter.api.Test;import org.springframework.beans.factory.annotation.Autowired;import org.springframework.boot.test.autoconfigure.web.servlet.AutoConfigureMockMvc;import org.springframework.boot.test.context.SpringBootTest;import org.springframework.boot.test.context.TestConfiguration;import org.springframework.context.annotation.Bean;import org.springframework.context.annotation.Import;import org.springframework.kafka.config.ConcurrentKafkaListenerContainerFactory;import org.springframework.kafka.core.*;import org.springframework.test.web.servlet.MockMvc;import org.testcontainers.containers.KafkaContainer;import org.testcontainers.utility.DockerImageName;import java.util.HashMap;import java.util.Map;@AutoConfigureMockMvc@SpringBootTest@Import(RunTcTest.RunTcTestConfiguration.class)public class RunTcTest extends RunBase {    @ClassRule    public static KafkaContainer kafka = new KafkaContainer(DockerImageName.parse("confluentinc/cp-kafka:5.5.3"));    static {        kafka.start();    }    @Autowired    private MockMvc mockMvc;    @Autowired    private ObjectMapper objectMapper;    @Test    void test() throws Exception {        run(mockMvc, objectMapper);    }    @TestConfiguration    static class RunTcTestConfiguration {        @Autowired        private RunCache runCache;        @Autowired        private RunClient runClient;        @Bean        ConcurrentKafkaListenerContainerFactory<Integer, String> kafkaListenerContainerFactory() {            ConcurrentKafkaListenerContainerFactory<Integer, String> factory = new ConcurrentKafkaListenerContainerFactory<>();            factory.setConsumerFactory(consumerFactory());            return factory;        }        @Bean        public ConsumerFactory<Integer, String> consumerFactory() {            return new DefaultKafkaConsumerFactory<>(consumerConfigs());        }        @Bean        public Map<String, Object> consumerConfigs() {            Map<String, Object> props = new HashMap<>();            props.put(ConsumerConfig.BOOTSTRAP_SERVERS_CONFIG, kafka.getBootstrapServers());            props.put(ConsumerConfig.AUTO_OFFSET_RESET_CONFIG, "earliest");            props.put(ConsumerConfig.GROUP_ID_CONFIG, "m-group");            props.put(ConsumerConfig.KEY_DESERIALIZER_CLASS_CONFIG, StringDeserializer.class);            props.put(ConsumerConfig.VALUE_DESERIALIZER_CLASS_CONFIG, StringDeserializer.class);            return props;        }        @Bean        public ProducerFactory<String, String> producerFactory() {            Map<String, Object> configProps = new HashMap<>();            configProps.put(ProducerConfig.BOOTSTRAP_SERVERS_CONFIG, kafka.getBootstrapServers());            configProps.put(ProducerConfig.KEY_SERIALIZER_CLASS_CONFIG, StringSerializer.class);            configProps.put(ProducerConfig.VALUE_SERIALIZER_CLASS_CONFIG, StringSerializer.class);            return new DefaultKafkaProducerFactory<>(configProps);        }        @Bean        public KafkaTemplate<String, String> kafkaTemplate() {            return new KafkaTemplate<>(producerFactory());        }        @Bean        public RunCalculator runCalculator() {            RunCalculatorWithWork runCalculatorWithWork = new RunCalculatorWithWork();            runCalculatorWithWork.runCache = runCache;            runCalculatorWithWork.runClient = runClient;            return runCalculatorWithWork;        }    }}

Перед всеми тестами необходимо стартовать kafka. Это делается вот таким вот образом:


kafka.start();

Дополнительные свойства для kafka в тестах можно задать в ресурсном файле.


application.yml

spring:  kafka:    consumer:      auto-offset-reset: earliest

Ресурсы и ссылки


Код для micronaut


Код для Spring Boot


PART 1: TESTING KAFKA MICROSERVICES WITH MICRONAUT


Testing Kafka and Spring Boot


Micronaut Kafka


Spring for Apache Kafka

Подробнее..

Что такое транзакция

16.01.2021 00:08:21 | Автор: admin

Транзация это набор операций по работе с базой данных (БД), объединенных в одну атомарную пачку.

(Предполагается, что вы знаете, что такое БД. Но чуть позже тут будет ссылка на статью что это такое)

Транзакционные базы данных (базы, работающие через транзакции) выполняют требования ACID, которые обеспечивают безопасность данных. В том числе финансовых данных =) Поэтому разработчики их и выбирают.

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

Содержание

Что такое транзакция

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

Представьте, что вы решили послать другу 10 файликов в мессенджере. Какие есть варианты:

  1. Кинуть каждый файлик отдельно.

  2. Сложить их в архив и отправить архив.

Вроде бы разницы особой нет. Но что, если что-то пойдет не так? Соединение оборвется на середине, сервер уйдет в ребут или просто выдаст ошибку...

В первом случае ваш друг получит 9 файлов, но не получит один.

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

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

И получается, что тебе надо уточнять у отправителя:

Ты мне сколько файлов посылал?

10

Да? У меня только 9... Давай искать, какой продолбался.

И сидите, сравниваете по названиям. А если файликов 100 и потеряно 2 штуки? А названия у них вовсе не Отчет 1, Отчет 2 и так далее, а hfdslafebx63542437457822nfhgeopjgrev0000444666589.xml и подобные... Уж лучше использовать архив! Тогда ты или точно всё получил, или не получил ничего и делаешь повторную попытку отправки.

Так вот! Транзакция это тот же архив для запросов. Принцип всё, или ничего. Или выполнены все запросы, которые разработчик упаковал в одну транзакцию, или ни один.

Допустим, вы переводите все деньги с одной карточки на другую. Выглядит это "внутри" системы как несколько операций:

delete from счет1 where счет = счет 1

insert into счет2values ('сумма')

Принцип всё или ничего тут очень помогает. Было бы обидно, если бы деньги со счета1 списались, но на счет2 не поступили... Потому что соединение оборвалось или вы в номере счета опечатались и система выдала ошибку...

Но благодаря объединению запросов в транзакцию при возникновении ошибки зачисления мы откатываем и операцию списания. Деньги снова вернулись на счет 1!

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

Как отправить транзакцию

Чтобы обратиться к базе данных, сначала надо открыть соединение с ней. Это называется коннект (от англ. connection, соединение). Коннект это просто труба, по которой мы посылаем запросы.

Чтобы сгруппировать запросы в одну атомарную пачку, используем транзакцию. Транзакцию надо:

  1. Открыть.

  2. Выполнить все операции внутри.

  3. Закрыть.

Как только мы закрыли транзакцию, труба освободилась. И ее можно переиспользовать, отправив следующую транзакцию.

Можно, конечно, каждый раз закрывать соединение с БД. И на каждое действие открывать новое. Но эффективнее переиспользовать текущие. Потому что создание нового коннекта тяжелая операция, долгая.

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

Разработчик берет соединение из пула и отправляет по нему транзакцию. Как только транзакция закрывается (неважно, успешно она прошла или откатилась), соединение возвращается в пул, и его может использовать следующая бизнес-операция.

Как открыть транзакцию

Зависит от базы данных. В Oracle транзакция открывается сама, по факту первой изменяющей операции. А в MySql надо явно писать start transaction.

Как закрыть транзакцию

Тут есть 2 варианта:

  1. COMMIT подтверждаем все внесенные изменения;

  2. ROLLBACK откатываем их;

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

Например, я пишу запрос:

insert into clients (name, surname) values ('Иван', 'Иванов');-- добавь в таблицу клиентов запись с именем Иван и фамилиев Иванов

Запрос выполнен успешно, хорошо! Теперь, если я сделаю select из этой таблицы, прям тут же, под своим запросом он находит Иванова! Я могу увидеть результат своего запроса.

Но! Если открыть графический интерфейс программы, никакого Иванова мы там не найдем. И даже если мы откроем новую вкладку в sql developer (или в другой программе, через которую вы подключаетесь к базе) и повторим там свой select Иванова не будет.

А все потому, что я не сделала коммит, не применила изменения:

insert into clients (name, surname) values ('Иван', 'Иванов');commit;

Я могу добавить кучу данных. Удалить полтаблицы. Изменить миллион строк. Но если я закрою вкладку sql developer, не сделав коммит, все эти изменения потеряются.

Когда я впервые столкнулась с базой на работе, я часто допускала такую ошибку: подправлю данные на лету для проведения теста, а в системе ничего не меняется! Почему? Потому что коммит сделать забыла.

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

Если имя = Тест

И фамилия = Тестовый

...

Удалили. Делаем select count посмотреть количество записей в таблице. А там вместо миллиона строк осталось 100 тысяч! Если база реальная, то это очень подозрительно. Врядли там было СТОЛЬКО тестовых записей.

Проверяем свой запрос, а мы там где-то ошиблись! Вместо И написали ИЛИ, или как-то еще. Упс... Хорошо еще изменения применить не успели. Вместо коммита делаем rollback.

Тут может возникнуть вопрос а зачем вообще нужен ROLLBACK? Ведь без коммита ничего не сохранится. Можно просто не делать его, и всё. Но тогда транзакция будет висеть в непонятном статусе. Потому что ее просто так никто кроме тебя не откатит.

Или другой вариант. Нафигачили изменений:

Удалить все строки, где имя Иван;

Поменять код города с 495 на 499;

....

Но видим, что операцию надо отменять. Проверочный select заметил, что база стала неконсистентной. А мы решили Ай, да ладно, коммит то не сделали? Значит, оно и не сохранится. И вернули соединение в пул.

Следующая операция бизнес-логики берет это самое соединение и продолжает в нем работать. А потом делает коммит. Этот коммит относился к тем 3 операциям, что были внутри текущей транзакции. Но мы закоммитили еще и 10 других тех, что в прошлый раз откатить поленились. Тех, которые делают базу неконсистентной...

Так что лучше сразу сделайте откат. Здоровей система будет!

Итого

Транзакция набор операций по работе с базой данных, объединенных в одну атомарную пачку.

Одной операции всегда соответствует одна транзакция, но в рамках одной транзакции можно совершить несколько операций (например, несколько разных insert можно сделать, или изменить и удалить данные...).

Чтобы отправить транзакцию к базе, нам нужно создать соединение с ней. Или переиспользовать уже существующее. Соединение называют также коннект (англ connection) это просто труба, по которой отправляются запросы. У базы есть пул соединений место, откуда можно взять любое и использовать, они там все свободные.

В некоторых системах транзакцию нужно открыть, в других она открывается сама. А вот закрыть ее нужно самостоятельно. Варианты:

  1. COMMIT подтверждаем все внесенные изменения;

  2. ROLLBACK откатываем их;

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

Не путайте соединение с базой (коннект) и саму транзакцию. Коннект это просто труба, операции (update, delete) мы посылаем по трубе, старт транзакции и commit /rollback это группировка операций в одну атомарную пачку.

PS больше полезных статей ищите в моем блоге по метке полезное. А полезные видео на моем youtube-канале

Подробнее..

Перевод Нагрузочное тестирование выполнять сложно, а инструменты далеки от совершенства. Почему?

07.01.2021 14:19:53 | Автор: admin


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

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

Если ваше приложение становится чуть сложнее, то вы переходите к инструментам наподобие Gatling. Они позволяют симулировать виртуальных пользователей, выполняющих различные сценарии, что намного полезнее, чем простая осада одного или нескольких URL. Но даже этого недостаточно, если вы пишете приложение, использующее одновременно WebSockets и HTTP-вызовы в течение долговременной сессии, а также требующее повторения по таймеру определённых действий. Возможно, я серьёзно недоглядел чего-то в документации, но мне не удалось найти способа, допустим, настроить периодическое событие, запускаемое каждые 30 секунд и выполняющее определённые действия при ответе на сообщение WebSocket, а также производящее действия по HTTP, и всё это в рамках одной HTTP-сессии. Я не нашёл такой возможности ни в одном инструменте нагрузочного тестирования (и именно поэтому написал на работе свой собственный инструмент, который надеюсь выложить в open source, если найду время на подчистку кода и отделения его от проприетарных частей).

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

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

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

Откуда берётся сложность


По сути, ситуация такова:

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

Давайте подробно рассмотрим каждый из пунктов, и разберёмся, от какой степени сложности можно избавиться.

Симуляция пользователей. Действительно ли она нужна?


Здесь я могу ответить да, хотя это может зависеть от конкретного приложения. В данном случае подразумевается пользователь сервиса: если у вас монолит, то это все ваши пользователи, но в случае микросервисов пользователем может быть один из сервисов! При создании приложений, над которыми я работал, мне удалось достичь незначительного успеха с таргетированными тестами отдельных конечных точек. Но в конечном итоге для этого требовалась такая сложная система, что это оказалось ненамного проще самого нагрузочного теста! И хотя мы получили некоторые результаты и усовершенствования, покрыть всё не удалось (в приложении могут быть взаимодействующие конечные точки) и мы не смогли получить реалистичную нагрузку.

Лучше задаться вопросом: А когда не нужно симулировать пользователей?. Насколько я понимаю, такое бывает, когда вы знаете, что все конечные точки независимы по производительности, отсутствуют запросы с отслеживанием состояния и порядок запросов не влияет на производительность. Это довольно серьёзные допущения, и без проверки их независимости сложно быть уверенным в них, поэтому нам приходится возвращаться к написанию теста.

Вероятно, лучшее, что вы можете здесь сделать, происходит на этапе проектирования API и системы, а не на этапе тестирования. Если упростить API, то тестировать придётся гораздо меньшую площадь поверхности. Если спроектировать систему с более независимыми частями (например, когда у каждого сервиса есть своя база данных), то их проще будет тестировать по отдельности, чем монолит. К тому же, при этом вы сможете использовать более простой инструмент двойная выгода!

Написание тестов сложная задача. Как и их поддержка.


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

А, да, и теперь вам нужно писать для всего этого ещё и интеграционные тесты.

Существуют исследования о том, как упростить некоторые из этих задач. Например, можно разобраться в том, что вам нужно для первоначального теста, и распознавать регрессии (отсутствующие новые нагрузки) по автоматизированному анализу логов. Но насколько я понимаю, на GitHub, не говоря уже о коммерческой доступности, нет продукта, способного выполнить за меня эту задачу. Поэтому похоже, что в нашей отрасли подобные системы не получили широкой поддержки. Для самостоятельной реализации этот проект был бы слишком большим, и в этом может быть причина его увядания (или он реализуется в больших компаниях и о нём не говорят).

Может быть, не подвергать всё нагрузочному тестированию?


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

Чтобы получить подробную картину производительности системы, можно воспользоваться одним из способов:

  • Старый добрый анализ. Усесться с ноутбуком, ручкой и пониманием системы в целом, выделить на это полдня, и вы получите примерные расчёты основных параметров и ограничений масштабирования системы. Когда вы наткнётесь на бутылочное горлышко или встретитесь с неизвестными переменными (сколько транзакций в секунду может поддерживать наша база данных? Сколько мы их генерируем?), то можно будет протестировать конкретно их!
  • Разворачивание фич. Если вы можете медленно разворачивать фичи на всех пользователей, то вам может и не понадобиться нагрузочное тестирование! Вы можете измерять производительность экспериментально и проверять, достаточно ли её. Достаточно? Разворачиваем дальше. Мало? Откатываемся назад.
  • Повторение трафика. Это совершенно не поможет с новыми фичами (для них воспользуйтесь предыдущим пунктом), но поспособствует пониманию критичных точек системы для уже имеющихся фич, при этом не требуя большого объёма разработки. Вы можете взять отслеженный ранее трафик и повторять его (многократно снова и снова, даже комбинируя трафик различных временных периодов), наблюдая за производительностью системы.



    На правах рекламы


    Серверы для разработки это эпичные от Вдсины.
    Используем исключительно быстрые NVMe накопители от Intel и не экономим на железе только брендовое оборудование и самые современные решения на рынке!

Подробнее..

Перевод Когда QA-специалист становится профессионалом в игровой индустрии?

21.12.2020 16:08:05 | Автор: admin

Крис Бьюик ветеран индустрии QA и член экспертного совета ассоциации Game Global. Он расскажет о работе в отделах обучения и повышения квалификации, а также об их роли в управлении QA-командами на проектах аутсорсинга.

5 причин, почему отдел обучения и повышения квалификации необходим для современного QA на аутсорсинге

Я уже 19 лет оказываю услуги по контролю качества в индустрии видеоигр. За это время в этой отрасли произошли колоссальные перемены. Современные игры сильно отличаются от тех, с которыми я работал в начале своей карьеры, в 2000 году. Они более глубокие, сложные и разнообразные.

Сейчас положение каждого поставщика услуг по-своему уникально: нам приходится работать с множеством разных клиентов, и мы стараемся тесно сотрудничать с ними. Благодаря этому сотрудничеству мы набираемся опыта и учимся адаптироваться к переменам. Когда клиент обращается за помощью к поставщику услуг QA, он хочет быть уверенным, что его обслуживают профессионалы, которые идут в ногу со временем и могут обеспечить контроль качества по современным стандартам.

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

Вот пять причин, почему, как я считаю, современный аутсорсинг QA не может существовать без отдела обучения и повышения квалификации (англ. Learning and Development, L&D).

1.Мышление консультанта

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

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

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

Без отдела L&D компании может быть сложно адаптироваться к изменениям на рынке QA, усваивать все его тонкости и постоянно предоставлять клиентам гибких специалистов по контролю качества.

2.Развитие партнерских отношений

Налаживая отношения с новым клиентом, очень важно учитывать его корпоративную культуру и владеть навыками, которые ему требуются. Без отдела L&D эти специфические знания о клиентах легко теряются из-за того, что сотрудники переходят на другие должности или увольняются.

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

Если клиент хочет, чтобы QA-команда участвовала в ежедневных SCRUM-собраниях, отдел L&D должен убедиться, что все сотрудники знают этот метод. Кроме того, L&D несет ответственность за налаживание связей с потенциальными клиентами. Сотрудники этого отдела изучают особенности культуры клиента и его подходы к работе, а затем обучают им других сотрудников и включают в будущие программы повышения квалификации.

3.Деловая зрелость

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

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

Необходимо создавать инструменты по индивидуальным требованиям и курировать работу с ними.Без поддержки со стороны отдела L&D эти процессы остаются в ведении руководителей QA-команд, а у них не всегда есть достаточно времени и опыта, чтобы помогать коллегам развивать деловую зрелость.

4. Взаимоуважение

Мы работаем в коллективе для того, чтобы достигать целей и обеспечивать соблюдение требований в рамках определенного проекта. Представим, что у нас есть отличная команда QA-специалистов, готовых прилагать больше усилий, чтобы гарантированно достичь целей.

По моему опыту, если компания ничего не дает взамен, сотрудники могут терять мотивацию или перестают уважать компанию. Речь не только о премиях, но и о заинтересованности. И это я не о пицце и пиве (хотя это, конечно, тоже помогает!), а о зрелом подходе к обучению: нужно мотивировать каждого человека, целые команды или группы.

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

5.Здоровая иерархия

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

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

Без отдела L&D сила иерархии и способность компании удерживать ценные кадры могут очень быстро ослабнуть, а путь к восстановлению после такого упадка может занять месяцы. Когда поддержке и обслуживанию иерархии уделяется особое внимание, она остается зрелой, и компания может справляться с самыми трудными ситуациями.

Об авторе

Крис Бьюик (Chris Bewick),

Региональный директор QA в Евразии, компания Keywords Studios

Крис Бьюик работает в индустрии игр и интерактивных развлечений более 19 лет. Сейчас Крис региональный руководитель подразделения FQA, он работает над формированием и совершенствованием отделений Keywords Studios в Европе и Азии. Ранее Крис Бьюик занимался развитием услуг по контролю качества в варшавском и лондонском офисах компании Testronics, разработал индивидуальное решение для сертификации консоли Microsoft Xbox One, был менеджером по соблюдению требований в Electronic Arts и зарекомендовал себя в отрасли, проработав 12 лет в Babel Media, где высшей точкой в его карьере стала позиция руководителя подразделения FQA-тестирования в Нью-Дели (Индия).


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

-Python QA Engineer

-Java QA Engineer

-Game QA Engineer

-QA Engineer (Базовый курс)

Подробнее..

Гена против Сандро история автоматизации одной сетевой партии в Героях 3

25.12.2020 08:10:45 | Автор: admin


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


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


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


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


Сандро не подозревал, что исход всех его приключений давно предопределён. Вся его история окончится через несколько минут (хоть для него это будет казаться целой неделей) ведь именно столько занимает прогон автотеста кроссплатформенной игровой партии по сети в Героях 3. Действиями Сандро управляет платформа Testo, которая готова прогонять его историю снова и снова.


Будучи разработчиком этой самой платформы Testo, я решил под Новый Год немного повеселиться и соединить своё профессиональное произведение и любимую игру, в которой затерялись тысячи часов моей жизни. И вот что из этого получилось.


Disclaimer

Уважаемые друзья! В этой статье, помимо привычных технических вещей, вы также найдёте историю противостояния некроманта Сандро и никудышного рыцаря Гены в сеттинге нашей любимой и обожаемой игры Герои меча и магии 3. Эта история изобилует большим количеством художественных вымыслов относительно игровых механик этого замечательного произведения игростроения. Пожалуйста, не стоит относиться к этим вымыслам слишком серьёзно: я точно так же, как и вы, люблю и ценю Героев 3, и лишь предлагаю взглянуть на привычные всем вещи с новой, забавной стороны. Любые совпадения случайны, я отказываюсь от любых намёков на какие-либо реальные вещи в реальном мире.


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


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


Сложно в это поверить, но на новом месте Гена освоился лучше некуда: всего за месяц он познакомился со всеми местными малочисленными гарнизонами (благо его талант сомелье к этому очень располагал, как и десятки бочек вина в погребах замка) и узнал у местных патрулей, что защищать Балашов ему, фактически, не от кого: последнее враждебное существо (волка) в этих краях видели аж 2 года назад.


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


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


Начальные условия


Для запуска Героев я решил использовать проект VCMI. В этом замечательном открытом проекте все ключевые компоненты Героев 3 были с нуля переписаны на С++. Проект является кроссплатформенным, поэтому с его помощью вы можете играть в Героев по сети на Макбуке с другом, который едет в метро и сидит в своём телефоне на Андроиде. Как раз-таки этой кроссплатформенностью я и воспользовался для этой статьи.


Похождения Сандро и Гены происходят вот на такой карте, которую я накидал в редакторе за 15 минут (все ссылки доступны в конце статьи):



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



Игра происходит по сети в режиме "человек против человека". Сандро выбрал Ubuntu Desktop 20.04 в качестве машины для запуска, а Гена решил не рисковать и придерживаться проверенного варианта с Windows 7. Сандро на правах более опытного пользователя создаёт конференцию и выступает в качестве сервера, а Гене лишь нужно подключиться к Сандро по сети.


За развёртывание, настройку стенда, установку VCMI и прогон всей игровой партии отвечает платформа Testo. Возможно, вы уже видели на Хабре краткое описание этой платформы, или примеры автотестов для антивируса Dr. Web (без малейшего доступа к исходникам). Помимо тестов, Testo можно применять и для автоматического развёртывания интересных виртуальных стендов (например, контроллер домена AD вместе с рабочей станцией).


Краткое описание Testo

Testo это новый фреймворк для системных (End-To-End) автотестов, который позволяет автоматизировать взаимодействие с виртуальными машинами с помощью скриптов на специальном, простом для понимания языке. Testo построен на распознавании объектов на экране с помощью нейросетей, поэтому для его работы не требуется устанавливать специальные агенты внутрь виртуальных машин.


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



Благодаря механизму кеширования в Testo, все эти тесты вовсе не нужно будет прогонять с нуля каждый раз. Поэтому самые долгие и неинтересные тесты *install и *configure (установка и первичная настройка ОС) прогонятся вовсе только один раз, после чего вечно будут закешированы. Разбирать эти тесты я не буду, но желающие смогут найти исходники этих тестов в ссылках в конце статьи.


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




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


Сам дом оказался тоже незапертым. Не то, чтобы это могло стать препятствием, конечно же. Но так эффект от бесшумного появления Сандро в комнате с кучей крестьян оказался максимально удовлетворительным для него. Двадцать с лишним пар глаз мгновенно уставились на него. Весёлые разговоры мгновенно смолкли, оставив лишь гнетущую тишину. Сандро любил гнетущую тишину как и всё гнетущее. Прошептав своим замогильным голосом Вы пойдёте со мной, Сандро развернулся и неспешно вышел обратно во двор. Он знал, что ни один тщедушный смертный не посмеет ему перечить, и что эти крестьяне теперь в его власти до самой своей смерти. Да и после смерти тоже. Особенно после смерти.


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


Установка VCMI


Похоже, дела у Сандро идут вполне неплохо.


Что ж, первые тесты прошли (где-то за кадром), пришло время заняться установкой VCMI. За это отвечают два теста: ubuntu_install_vcmi и win7_install_vcmi. В них происходит установка самого дистрибутива VCMI и копируются почти все необходимые файлы с оригинальными данными Героев 3 в папки VCMI (Чтобы VCMI мог их подхватить). Точнее, копируются все файлы, кроме папки Maps (в которой лежит только одна карта, скриншот которой вы видели выше). Эта папка будет скопирована чуть позже. Вот так выглядит этот процесс на примере Ubuntu:



Уважаемые читатели, автор в курсе про правило не выкладывать код скриншотами, но тесты написаны на языке Testo-lang, и Хабр не поддерживает подсветку этого языка. Я считаю, что лучше уж выложить скриншот, зато код не будет сливаться в одно месиво. Ссылки на все тесты доступны в конце статьи, вы всегда можете увидеть их текстовое представление там.

Пробежимся вкратце по тесту:


  1. Сам VCMI устанавливается из репозитория ppa:vcmi/ppa.
  2. Нужно один раз запустить VCMI Launcher, чтобы он создал свои служебные папки в ~/.local/share.
  3. Копируем с хоста папки Data, Mp3, Mods/vcmi в служебные папки VCMI. Папки Data и Mp3 содержат файлы с данными оригинальных Героев. Папка Mods/vcmi содержит мод vcmi, который позволяет устанавливать нормальное разрешение экрана.
  4. Копируем файл vcmi_settings.json с настройками VCMI, чтобы не нужно было прокликивать настройки с помощью мышки.

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


Для Windows процесс выглядит примерно так же (разве что дистрибутив копируется с хоста, а не из Интернета), останавливаться отдельно не буду.




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


Он сидел в комнате для совещаний, закрыв лицо руками, и не верил своим ушам. Помимо него в комнате находилось ещё двое: крестьянин из Вилларибо и единственный штатный монах во всём Балашове по имени Станислав. Станислав, в отличие от Гены, был отправлен в Балашов на пенсию, а не чтобы не отсвечивать. Навоевался монах в свои годы, и просто хотел на старости лет отдохнуть.


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


Откуда он тут взялся? это оказался единственный вопрос, который был способен сгенерировать Генин разум в этой ситуации.


Я не знаю, да это и неважно. Важно лишь то, что все крестьяне из обоих деревень скоро станут скелетами, если уже не стали. Это больше 400 скелетов. С этой армией он наверняка захочет захватить наш замок, Станислав был мрачен, но непоколебим.


И что нам теперь делать?


Нам нечем защищаться. У нас нет войск. Все, кто могут держать оружие, слишком далеко они не успеют вернуться в замок до атаки некроманта.


Так и как же быть?


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


И какие у нас варианты?


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


Так каков в итоге план?


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


Замыленные месяцем возлияний мозговые шестерёнки в голове у Гены наконец-то со скрипом прокрутились, и он с ужасом осознал: ему придётся изучать магию. МАГИЮ! Магию изучают ЗУБРИЛ! Сам же Гена всегда был свято уверен, что жизнь слишком коротка, чтобы тратить её на изучение каких-то ветхих непонятных книг.


Гена был потрясён и обескуражен до глубины души. В мгновение ока он забыл про некроманта, про орды скелетов и про залежи вина в погребах. Заикаясь, Гена выдавил:


Я, я не могу изучать м-магию. Мне нельзя магию! Мне нельзя доверять книгу заклинаний, я даже грифонов в таверне умудрился пропить! Нам лучше сдать замок и эвакуироваться!


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


Запуск сетевой игры


За запуск сетевой игры отвечает тест launch_game. Это первый тест, куда "сходятся" отдельные ветки Ubuntu и Windows 7. Это и понятно, ведь для запуска сетевой игры требуется наличие в тесте обоих машин (ранее мы могли всё делать по отдельности). Ubuntu (Сандро) выступает в качестве сервера, а Windows (Гена) в качестве клиента. Так что тест выглядит следующим образом:


1) Копируем папку Maps с хоста на Ubuntu и запускаем VCMI. Создаём новую конференцию для игры по сети.


Сниппет


2) Копируем папку Maps с хоста на Windows 7 и запускаем VCMI. Подключаемся к конференции по IP-адресу.


Сниппет


3) Каждый игрок выбирает себе начальный бонус, после чего игра запускается.


Сниппет


Можно заметить, что этот тест выглядит как простое сочетание кликов по тексту и по изображениям (все изображения я заранее вырезал и упаковал в файлы, путь к которым теперь указывается в действиях mouse click).


Но дальше начинается сама игра. И если вы думаете, что вся партия будет выглядеть как простой набор прокликиваний по куче картинок, то вы заблуждаетесь. Нет, здесь начинается самая интересная часть нашей статьи. Благодаря небольшому тюнингу языка и специальным нейросетям я смог научить платформу Testo неплохо ориентироваться в мире иконок и моделей Героев 3.




Просто поставь подпись в ведомости вот здесь, пробормотал Игорь, теперь давай сюда свою книгу, сейчас мы её разблокируем


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


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


В смысле могу?! Я же ничего не знаю! Я не умею в МАГИЮ!


Конечно можешь. Я же только что проштамповал книгу печатью базовой мудрости.


Гена просто молчал.


Ты что, вообще не знаешь, как работает магия?


Больше молчания.


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


По умолчанию в книгу можно проставлять только слабенькие заклинания, 1-2 уровней. Чтобы заносить заклинания помощнее, нужно разблокировать книгу специальными печатями. Станислав попросил меня проставить тебе печать базовой мудрости, так что вот теперь и заклинания третьего уровня можно заносить.


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


Гена никогда ещё не получал так много информации в такие сжатые сроки. Но он знал, что ради родного Балашова (и родных феечек) он должен стать лучше! Он должен попробовать вникнуть!


А пользоваться то этой книгой как?


Проще некуда. В бою открываешь книгу, и нажимаешь пальцем на нужное заклинание. Книга задаст тебе контрольный вопрос (какой-нибудь общеизвестный факт), на который надо ответить. Отвечаешь правильно заклинание срабатывает. Это стандартная практика аутентификации, чтобы книгой не могли пользоваться всякие малограмотные крестьяне, если вдруг они завладеют книгой.


На слове малограмотные Гена непроизвольно сглотнул слюну. Слово аутентификация он решил просто проигнорировать.


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


Гена сомневался, что он хоть один вопрос сможет осилить, но решил снова промолчать.


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


Гене нравилось слово шпаргалка, но не нравилось слово зубрить. Зубрить шпаргалки это же почти и есть Учиться! Но выбора у Гены не было, так что он спрятал в рюкзак свою книгу заклинаний и поплёлся проставлять оставшиеся печати.


Похождения на карте и не только



Начинается игра, и возникает вопрос: а как автоматизировать действия героев на карте? Сначала я пошёл довольно топорным путём: каждый пункт назначения Сандро и Гены был оформлен в виде изображения-шаблона, который затем передавался на вход действию mouse click img. То же самое касалось любых других действий: нажатие иконок, постройка зданий и так далее. То есть я применял довольно прямолинейный (и распространённый) способ управлять действиями на экране виртуалки поиск изображения по образцу.


А потом количество заготовок стало зашкаливать, да и не все они хорошо работали: например, при попытке кликнуть по нужной звёздной оси поиск образа не всегда отрабатывал как нужно, ведь в этом объекте присутствует анимация:



Так что я подумал, а почему бы не сделать отдельную нейросеть для работы исключительно с объектами в Героях 3? За счёт того, что этой нейросети придётся работать с ограниченным набором объектов, она сможет довольно качественно их распознавать, даже несмотря на анимацию, вариативность цветов и так далее.


А чтобы этой нейросетью можно было удобно пользоваться, я решил добавить новую возможность в язык Testo-lang. Теперь действия wait и mouse click помимо текста и картинок поддерживают возможность ожидания и кликов по объектам Героев!


Вот так выглядил один из фрагментов теста до преобразований:



А вот так после:



Стало ли удобнее? На первый взгляд может показаться, что разница минимальна, но это не так: за каждой картинкой в img скрывается ручная работа: требуется сделать скриншот виртуальной машины и вырезать оттуда требуемый шаблон. Поиск с помощью homm3 работает на основе нейросетей, так что никаких действий с моей стороны не требуется только пользуйся. К тому же нейросети берут на себя заботы по возможной анимированности искомых объектов и помогут ещё в ряде случаев (подробности будут дальше).


Уважаемые читатели! Функционал платформы Testo по поддержке работы с объектами Героев 3 я выполнил в режиме Proof Of Concept и исключительно just for fun. Этот функционал очень сильно недоделан и не подходит для полноценного использования с Героями 3. В конце статьи доступна экспериментальная сборка Testo, на которой вы сможете запустить примеры, но доделывать полноценный вариант я не решился из-за сомнительной ценности и больших трудозатрат.

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




Сандро скомандовал последней порции крестьян пройти обряд улучшения (как он это называл). Люди, один за одним, отправились в сторону преобразователя скелетов. В их глазах была вселенская тоска от понимания своей участи (в конце концов, все они видели, что происходит с их предшественниками), но никто не посмел возразить властному некроманту.


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


В мгновение ока Сандро превратился из остерегающегося патрулей скрытого агента в могущественного предводителя армии тьмы (роль, к которой он очень сильно привык). И он знал, куда он направит свою армию тьмы в первую очередь. Повинуясь беззвучной команде Сандро, орды скелетов направились в сторону виднеющегося вдали замка.


Обучение геройской сети


Итак, мы решили создать нейросеть, чтобы облегчить себе прокликивание различных объектов. С чего же нам начать? Создание любой нейросети начинается с данных для обучения. В нашем случае мы решаем задачу, которая называется object detection. Соответственно, для обучения нам понадобятся скриншоты Героев, для каждого скриншота должен быть список объектов, которые изображены на нём, их классы (например, "герой" или "город") и их координаты (так называемый bounding box, сокращённо bbox). Причем данных нам понадобится много: гигабайты, а ещё лучше десятки гигабайт. Как же нам создать такой большой датасет? Давайте рассмотрим основные варианты:


  1. Можно вручную наснимать 100 тысяч скриншотов Героев и так же вручную их разметить. По понятным причинам этот вариант отметаем сразу.
  2. Мы можем воспользоваться тем фактом, что VCMI движок перед отрисовкой очередного кадра и так знает, где какие объекты находятся. То есть теоретически мы можем подправить исходники VCMI таким образом, чтобы он нагенерил нам большое количество скриншотов и сохранил информацию об объектах куда-нибудь в файл, например. Исходники VCMI довольно хорошего качества, однако реализовать такой план не так-то просто, потому что логика отрисовки объектов тесно переплетена с механикой игры. Нельзя просто так взять и сказать "отрисуй мне вот эту карту". К сожалению, придётся отказаться от этого плана из-за слишком больших трудозатрат.
  3. Есть ещё один компромиссный вариант, который подходит для большинства задач класса object detection. Идея заключается в том, чтобы вручную разметить относительно небольшое количество скриншотов (допустим 100 штук), а затем разнообразить их путём отрисовки в случайных местах тех объектов, которые мы собирается потом детектить. Разумеется, дополнительно можно применить и другие способы искажения изображений: кадрирование, масштабирование, отражение слева-направо, изменения контраста и яркости, инверсия цветов и т.д. Этот вариант вполне рабочий, а главное его можно реализовать всего за пару дней.

Сказано сделано. Разметить 100 скриншотов не представляет никакой сложности, особенно, если использовать подходящий инструмент разметки. Можно использовать любой из свободных инструментов, например LabelImg. Но я использую собственный интрумент разметки на базе Electron и KonvaJS. Использование своего инструмента удобнее с той точки зрения, что его легче затачивать именно под решение своих задач.



Итак, с разметкой разобрались, теперь нужно определиться с тем, какие объекты мы будем рисовать поверх скриншотов. Я решил, что нейросети стоит применять только там, где плохо подходит стандартный и уже встроенный в Testo детект изображений (wait img). Поэтому для определения зоны ответственности для новой нейросети я руководствовался следующими принципами:


  1. В первую очередь, конечно, это анимированные объекты, потому что иначе действие wait img просто не будет работать. К таким объектам относятся, например, анимированные иконки построек в меню города.
  2. Объекты, у которых может быть разный фон (иначе понадобится вырезать новую картинку на каждый новый фон). Пример такого объекта иконка героя или любого другого объекта на карте.
  3. Объекты, у которых может быть много вариаций. Например кнопка ОК. У неё в игре есть где-то 5-6 разных вариантов отрисовки. Вырезать 5-6 картинок это не проблема, но каждый раз подбирать, какая же картинка лучше подходит в данном конкретном случае нет никакого желания.

Для того, чтобы извлечь оригиналы иконок объектов с альфа каналом я использовал проект lodextract. На вход ему подаются .lod файлы из оригинальной версии игры, а на выходе он выдаёт неимоверно большое количество .png картинок (десятки тысяч), например, вот таких:



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



Строго говоря, в качестве фона даже необязательно было брать скриншоты из игры, можно было взять любые другие изображения. Но, как мне кажется, если использовать скриншоты из игры результат получится лучше.


Процесс искажения изображений я разделяю на два этапа. Наиболее сложные операции, в том числе отрисовка объектов поверх скриншота, я делаю до обучения нейросети. Результат этого этапа сохраняется на диск, получается около 100Гб данных. Приятным бонусом в такой схеме является то, что можно любым удобным способом открывать файлы на диске и проверять, что данные сгенерировались правильно. Второй этам происходит "на лету" уже во время обучения сети. Сюда входят простые операции, такие как изменение контраста или инверсия цветов.


Таким образом мы плавно подошли к обучению нейросети. Для начала нам нужно определиться с её архитектурой. Я выбрал yolov3-tiny, но на самом деле это не так уж важно. Для нашей очень простой задачи, впринципе, подойдёт любая архитектура, выполняющая детект объектов. Гораздо бОльшее значение здесь имеет датасет. Нейросети имеют удивительное свойство "выдавать результат" несмотря на их небольшой размер или неоптимальную архитектуру. Главное не давать сети зацикливаться на косвенных признаках объектов. Например, нейросеть может подумать, что коричневые пиксели это учёный (scholar), если на скриншотах в датасете больше не встречается объектов коричневого цвета. Поэтому так важно иметь большой и качественный датасет. А архитектура нейросети это второстепенный вопрос.


Я не буду останавливаться детально на архитекруте Yolo, тем более, что есть подробная статья на эту тему. Обратим внимание только на входные и выходные данные.



На вход сети подаётся набор картинок, а на выходе для каждой картинки мы получаем по несколько тысяч векторов (для больших картинок, типа FullHD). Это одна из особенностей работы нейросетей: мы в какой-то степени использум брутфорс, нежели какую-ту изощренную логику. Каждый вектор состоит из следующих значений:


  • вероятность, что этот вектор описывает какой-то объект на картинке (objectness score). Для большинства векторов это значение будет около нуля.
  • координаты центра объекта (x, y), а также его ширина и высота (w, h)
  • набор значений, описывающих вероятности того, что этот объект принадлежит к каждому из классов (class scores)

То есть выход нейросети требует ещё некоторой постобработки. Нужно отфильтровать вектора с низким objectness score, а также объединить вектора, которые указывают на один и тот же объект. Эти операции не добавляются в модель нейросети, потому что они плохо подходят под архитектуру GPU (у нас получится размерность выходных данных всё время разная). К этому вопросу мы вернёмся чуть позже, а пока предлагаю мельком взглянуть на код нейросети, написанный с ипользованием фреймвока PyTorch:


Код модели
import torchimport torch.nn as nnimport torch.nn.init as initimport torch.nn.functional as Ffrom dataset import classes_namesdef Conv(in_ch, out_ch, kernel_size, activation='leaky'):    seq = nn.Sequential(        nn.Conv2d(in_ch, out_ch, kernel_size=kernel_size, padding=kernel_size//2),        nn.BatchNorm2d(out_ch),    )    if activation == 'leaky':        seq.add_module("2", nn.LeakyReLU(0.1))    elif activation == 'linear':        pass    else:        raise "Unknown activation"    return seqdef MaxPool(kernel_size, stride):    if kernel_size == 2 and stride == 1:        return nn.Sequential(            nn.ZeroPad2d((0, 1, 0, 1)),            nn.MaxPool2d(kernel_size, stride)        )    else:        return nn.MaxPool2d(kernel_size, stride)# Псевдо-слой сети, служит для конкатенации выходов из нескольких других слоёв class Route(nn.Module):    def __init__(self, layers_indexes):        super().__init__()        self.layers_indexes = layers_indexesclass Upsample(nn.Module):    def __init__(self, scale_factor, mode="nearest"):        super().__init__()        self.scale_factor = scale_factor        self.mode = mode    def forward(self, x):        x = F.interpolate(x, scale_factor=self.scale_factor, mode=self.mode)        return xnum_classes = len(classes_names)# Псевдо-слой сети, служит для нормализации выходов предыдущего слоя и # преобразования x,y,w,h в пикселиclass Yolo(nn.Module):    def __init__(self, anchors):        super().__init__()        self.anchors = anchors        self.mse_loss = nn.MSELoss()        self.bce_loss = nn.BCELoss()        self.obj_scale = 1        self.noobj_scale = 100    # Вычесление intersection over union двух bbox-ов    def bbox_wh_iou(self, w1, h1, w2, h2):        inter_area = torch.min(w1, w2) * torch.min(h1, h2)        union_area = w1 * h1 + w2 * h2 - inter_area        return inter_area / (union_area + 1e-16)    def forward(self, x, img_w, img_h):        B, C, H, W = x.shape        num_anchors = len(self.anchors)        prediction = x.view(B, num_anchors, num_classes + 5, H, W) \            .permute(0, 1, 3, 4, 2) \            .contiguous() # преобразуем к формату (B, num_anchors, H, W, num_classes + 5)        # размеры одной ячейки (ширина и высота) в пикселях        stride_x = img_w / W        stride_y = img_h / H        # номера столбцов и строк        grid_x = torch.arange(W).repeat(H, 1).view([1, 1, H, W]).to(x.device)        grid_y = torch.arange(H).repeat(W, 1).t().view([1, 1, H, W]).to(x.device)        # размемы якорей (ширина и высота) в пикселях         anchor_w = x.new_tensor([anchor[0] for anchor in self.anchors]).view((1, num_anchors, 1, 1))        anchor_h = x.new_tensor([anchor[1] for anchor in self.anchors]).view((1, num_anchors, 1, 1))        # преобразуем x,y,w,h в пиксели        pred_x = (prediction[..., 0].sigmoid() + grid_x) * stride_x        pred_y = (prediction[..., 1].sigmoid() + grid_y) * stride_y        pred_w = prediction[..., 2].exp() * anchor_w        pred_h = prediction[..., 3].exp() * anchor_h        # приводим вероятности к диапазону [0, 1]        pred_conf = prediction[..., 4].sigmoid()        pred_cls = prediction[..., 5:].sigmoid()        # из матрицы делаем список        # это позволит объединить вместе выходы Yolo разных размеров        return torch.cat(            (                pred_x.view(B, -1, 1),                pred_y.view(B, -1, 1),                pred_w.view(B, -1, 1),                pred_h.view(B, -1, 1),                pred_conf.view(B, -1, 1),                pred_cls.view(B, -1, num_classes),            ),            -1,        )class Model(nn.Module):    def __init__(self):        super().__init__()        # размеры якорей        anchors_1 = [(81,82), (135,169), (344,319)]        anchors_2 = [(10,14), (23,27), (37,58)]        # список всех слоёв сети        self.module_list = nn.ModuleList([            Conv(3, 16, 3),            MaxPool(2, 2),            Conv(16, 32, 3),            MaxPool(2, 2),            Conv(32, 64, 3),            MaxPool(2, 2),            Conv(64, 128, 3),            MaxPool(2, 2),            Conv(128, 256, 3),            MaxPool(2, 2),            Conv(256, 512, 3),            MaxPool(2, 1),            Conv(512, 1024, 3),            #############            Conv(1024, 256, 1),            Conv(256, 512, 3),            Conv(512, (num_classes + 5) * len(anchors_1), 1, activation='linear'),            Yolo(anchors_1),            Route([-4]),            Conv(256, 128, 1),            Upsample(2),            Route([-1, 8]),            Conv(128 + 256, 256, 3),            Conv(256, (num_classes + 5) * len(anchors_2), 1, activation='linear'),            Yolo(anchors_2)        ])    def forward(self, img):        layer_outputs = []        yolo_outputs = []        x = img        # просто применяем очередной слой к выходу от предыдущего слоя        # (или объединяем выходы нескольких слоёв в случае с Route)         for module in self.module_list:            if isinstance(module, Route):                x = torch.cat([layer_outputs[i] for i in module.layers_indexes], 1)            elif isinstance(module, Yolo):                x = module(x, img.shape[3], img.shape[2])                yolo_outputs.append(x)            else:                x = module(x)            layer_outputs.append(x)        return torch.cat(yolo_outputs, 1)

У нас получилась модель, которая на диске занимает примерно 35Мб. На самом деле это перебор для такой простой залачи, как детект объектов в Героях. Я уверен, что можно безболезненно уменьшить размер модели где-то до 5Мб, при этом не потеряв в точности детекта. Но на это нет времени, история Гены и Сандро ждёт своего продолжения. Двигаемся дальше.


Имея датасет и код нейросети, обучить её не составяет труда. Запускаем обучение и уходим погулять часа на 3.



На картинке выше изображен график функции ошибки. Как мы видим за 3 часа цикл обучения успел выполнить около 70 тысяч итераций и процесс обучения сошелся. Также мы видим, что примерно на 7 тысячах итераций случился какой-то непонятный всплеск. Это может свидетельствовать о том, что мы допустили какую-то ошибку при генерации датасета и вместо корректных обучающих данных подсунули нейросети какую-ту лажу. Здесь как раз может пригодится то, что все скриншоты имеются на диске, можно открыть проблемный и изучить его повнимательнее. Но, скорее всего, это просто оптимизатор по инерции вылетел из локального минимума, в целом это совершенно нормальное поведение.


Сеть обучена, замечательно. Однако возникает резонный вопрос, как это дело интегрировать в конечный продукт (если он не на питоне)? Самый простой, наверное, способ это слинковаться с libpytorch. Более того, мы могли бы вместо того, чтобы писать код нейросети на питоне написать его сразу на С++ и даже получить некоторый прирост производительности, благо PyTorch предоставляет C++ frontend. Однако, не очень то хочется тащить за собой весь PyTorch, ведь он даже в заархивированном виде весит целый гигабайт. Поэтому я использую OnnxRuntime. Он позволяет сократить размер дистрибутива в два раза, а также увеличить производительность работы нейросетей. Как следует из названия, этот проект позволяет загружать обученные модели нейросетей в формате onnx и запускать их, так что нам для начала нужно экспортировать нашу модель в этот формат:


model = Model()model.load_state_dict(torch.load("path_to_model.pt", map_location=torch.device('cpu')))model.eval()x = torch.randn(1, 3, 480, 640)output = model(x)torch.onnx.export(model, x, "model.onnx",    input_names=["input"],    output_names=["output"],    dynamic_axes={        'input': {            2: 'height',            3: 'width'        },        'output': {            2: 'height',            3: 'width'        }    },    opset_version=11)

Я указал здесь параметр dynamic_axes, это позволит подавать на вход сети картинки любого размера. Вообще, с экспортом в формат onnx нужно быть очень осторожным. Когда мы пишем код модели на питоне то мы используем привычные нам циклы, условия и переменные. А формат onnx описывает граф с вершинами и ребрами. Сконвертировать одно в другое это совершенно нетривиальная задача. Убедиться в том, что экспорт прошел успешно, можно с помощью просмотрщика формата onnx, например с помощью Netron. Но скорее всего, если что-то пойдёт не так, PyTorch выдаст предупреждение. Экспортировав модель в формат onnx, мы можем загрузить её из С++/C#/Java/NodeJS. Ниже пример для питона:


import onnxruntimeimport numpyort_session = onnxruntime.InferenceSession("model.onnx")x = numpy.rand(1, 3, 480, 640)ort_inputs = {"input": x}ort_outs = ort_session.run(None, ort_inputs)

Вот здесь как раз можно выполнить постобработку результатов работы нейросети. Векторы с низким objectness score просто отбрасываем, а к остальным применяем алгоритм Non-Maximum Suppression. Давайте, наконец, запустим нашу свежеобученную нейросеть и посмотрим, как она работает:



Я обучал нейросеть детектить только те объекты, которые мне нужны для теста, поэтому на скриншоте выше подсвечены не все объекты. Ура! Вроде всё работает, а значит мы можем автоматизировать похождения Гены и Сандро по карте:



Битва


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


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


Перед боем Станислав рассказал Гене, как нужно использовать магию: сначала необходимо притормозить скелетов с помощью замедления (и не забывать следить за тем, чтобы эффект заклинания не кончился), а затем использовать заклинание Уничтожить нежить до тех пор, пока Гене будет хватать знаний. Знаний у Гены конечно прибавилось после всех шпаргалок с ответами, которые он добывал всю неделю. К тому же перед боем он хлебнул воды из какого-то магического фонтана, после чего у него на удивление сильно обострилась память, так что ответы из шпаргалок прочно сидели у него в голове.


Но скелетов было так много, что даже обостренный ум Гены мог не выдержать. Всё могло решиться в самых мелочах.




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


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



Битва будет протекать по такому сценарию:


  1. Сандро будет атаковать на автопилоте.
  2. Гена кастует экспертное замедление и перемещает феечек наверх карты.
  3. Пока Сандро медленно продвигает своих скелетов вперёд, Гена кастует "Уничтожение нежити".
  4. Через 5 ходов Гена обновляет замедление и перемещает феечек в низ карты.
  5. Гена продолжает кастовать "Уничтожение нежити" пока у него не кончится мана или пока не кончатся скелеты.

Тест может закончиться тремя исходами:


  1. Бой заканчивается поражением Гены меньше, чем за 13 ходов провал.
  2. Бой длится 13 ходов и больше провал.
  3. Гена побеждает быстрее, чем за 13 ходов успех.

Вот так это выглядит в виде теста:



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


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



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


Сколько раз за один ход королевский грифон может ответить на атаку противника? гласила надпись на книге. Это был первый вопрос, которого не было в шпаргалках Гены.


За всё своё время рыцарства Гена так и не удосужился узнать хоть что-нибудь об этих ужасных страшных созданиях. Его познания о грифонах ограничивались тем, что если пропить их в таверне, тебя сошлют в Балашов.


Гена стал судорожно подбирать правильный ответ. Книга допускала 2 неверные попытки, после чего заклинание блокировалось на какое-то время. У Гены этого времени не было: оставшиеся скелеты уже ломились к нему на стену замка. От фей тоже было мало проку: они забились в угол и ждали, когда Гена, наконец, прочтёт завершающее заклинание.


Варианты 2 и 3 раза не понравились книге заклинаний.




Гена стоял со связанными руками перед преобразователем скелетов и думал, что же пошло не так. Как победа умудрилась выскользнуть из его рук? Если бы только он был немного умнее Если бы он хоть немного слушал на лекциях своих преподавателей, которые не раз и не два рассказывали про этих проклятых грифонов Было ли бы этого достаточно чтобы победить злого Сандро? Эх, чего уж теперь гадать, пора шагать в преобразователь.


А где же хеппи энд?


Действительно, не можем же мы завершить такую эпичную сагу на такой негативной ноте. Новый год же! Как мы увидели, Гена вполне себе уверенно шёл к успеху, пока у него неожиданно закончилась мана. Может быть, надо просто сделать Гену изначально несколько "умнее"? Так давайте! Откроем редактор карт и добавим Гене немного "знаний":



Ну а теперь я наконец то могу ответить на вопрос, почему же я в тестах копирую папку Maps не в тесте install_vcmi, а в тесте launch_game. Как только мы поменяли файл Villaribo_and_Villabadjo_lose.h3m, Testo сбросит кеш того теста, где этот файл задействован (т.е. launch_game). Потерявший кеш тест (и его потомки) запустится заново. Если бы я копировал Maps в тестах install_vcmi, то именно эти тесты потеряли бы кеш. А значит, пришлось бы заново прогонять установку vcmi и копирование других папок (Data, Mp3 и прочее).


Я просто построил дерево тестов таким образом, чтобы редактирование карты не запускало повторную установку vcmi.



А вот и хэппи энд подъехал! Оказывается, Гене всего лишь нужно было лучше слушать преподавателей в военном ВУЗе!


Заключение


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


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


Репозиторий со всеми необходимыми сценариями и артефактами (в том числе с экспериментальной сборкой Testo) можно найти вот тут:


https://github.com/testo-lang/testo-articles/tree/master/HOMM3

Подробнее..

Исследовательское тестирование пустая трата времени или мощный инструмент?

27.12.2020 12:16:52 | Автор: admin
Одни считают, что исследовательское тестирование более продуктивное, чем привычное нам тестирование по сценариям. Другие что это пустая трата времени и ресурсов. Так ли это на самом деле?




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


Три вида тестирования, которые не стоит путать


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

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

Исследовательское тестирование более формальная версия ad-hoc: тестирование, не требует написания тест-кейсов без необходимости, но подразумевает, что каждый последующий шаг(тест) выбирается на основании результата предыдущего шага(теста). А по Сэму Канеру, Testing Computer Software, исследовательское тестирование вдумчивый подход к ad-hoc тестированию.

Сценарное тестирование является классическим тестированием по предварительно написанным и задокументированным сценариям. Оно требует наибольшей степени формальности и детализации документирования, что затрачивает гораздо больше сил тестировщика и его времени, однако наиболее подходит для ведения отчетности, статистики и т.п.

Общие сведения


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

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



Рассмотрим преимущества и недостатки исследовательского тестирования


Преимущества исследовательского похода:


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


Недостатки исследовательского подхода:


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


В каких же случаях стоит применять исследовательское тестирование?


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

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

Небольшой стартап
Функционал нашего приложения не очень большой, поэтому можно смело использовать исследовательское тестирование.

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

В каких же случаях не стоит применять одно только исследовательское тестирование?


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

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

Резюме


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

Перевод Новые задачи из мира непрерывной доставки

30.12.2020 18:06:06 | Автор: admin

Для будущих студентов курса "QA Lead" и всех интересующихся подготовили перевод интересного материала.

Также приглашаем принять участие в открытом уроке по теме:
"Организация тестирования при различных методологиях разработки".


Непрерывная доставка и развертывание (с общей аббревиатурой CD) далеко не новые концепции. Десять лет назад Джез Хамбл и Дэвид Фарли опубликовали книгу Continuous Delivery. Патрик Дебуа в 2009 году организовал конференцию DevOpsDays и создал хэштег #DevOps (также пишется как devops, devOps или Devops).

Конечно, концепция CD для некоторых команд в новинку. По этой теме есть множество курсов и публикаций, помогающих организациям успешно внедрять CD. Как и в любой практике, когда вы разберетесь с основами, появятся проблемы, которые нужно решить. В январе 2020 года мне посчастливилось посетить новую конференцию DeliveryConf, на которой слушатели могут получить более глубокие технические знания о непрерывной интеграции (CI) и непрерывной доставке. Основываясь на том, что я узнала там, о чем могу рассказать из своего опыта и опыта сообщества, мне бы хотелось поделиться некоторыми задачами, с которыми сегодня могут столкнуться некоторые команды в вопросах CD.

Тестирование

Сессии DeliveryConf, на которых я была (или позже смотрела на YouTube-канале), помогли мне увидеть больше примеров практических кейсов, в которых технология непрерывной доставки меняет представление о тестировании.

Автоматизация сложных работ или тяжелого труда не новая идея, однако выступление Джессики Керр открыло мне новые перспективы. Ниже я приведу несколько примеров того, как можно использовать автоматизацию, чтобы освободить больше вашего времени для важных дел:

  • Автоматизация для сокращения времени на подтверждение или проверки доступности машины.

  • Автоматизация таких вещей, как процедуры, инструменты, получение учетных данных, процесс проверки PR.

  • Развертывание сред тестирования в облаке для каждого нового изменения.

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

Преобразование пайплайнов

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

Стив Перейра в своем выступлении рекомендовал составить карту потока создания ценности вашего пайплайна, чтобы выявить узкие места и сократить петли обратной связи. Его команда создала карту в Google Sheets, чтобы каждый член команды мог посмотреть на нее. Они оценили те шаги, которые выполняются вручную, автоматические шаги, время на передачу и время ожидания. Они выполняли один шаг в единицу времени. В конце концов, им удалось сократить время, необходимое для запуска пайплайна с четырех недель до четырех дней!

Вот пример того, как может выглядеть часть карты потока создания ценности пайплайна, включая время выполнения каждого шага и время ожидания между шагами:

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

Команды, которые уже какое-то время занимаются CI/CD скорее всего имеют legacy пайпланы те, которые уже существуют какое-то время. Обычно они плохо документированы, и никто не понимает их от начала до конца, они даже могут оказаться крайне нестабильными. На DeliveryConf Лаура Сантамария поделилась своими переживаниями об устаревании пайплайнов. Она также рекомендовала создать карту потока создания ценности для пайплайна с помощью любой документации, которую вы сможете найти, включая все, что записано в системе отслеживания инцидентов. Так можно лучше понять пайплайн и найти способы его стабилизации, улучшения и документирования.

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

Нетрадиционные программные приложения

Эмили Горченски рассказывала о непрерывной доставке в контексте машинного обучения (ML), о шаблонах и распространенных проблемах. Мои знания ML весьма поверхностны, однако мне было очень интересно. Я и не подозревала, что у моделей машинного обучения есть срок годности.

Эмили объясняла, что системы, управляемые данными, недетерминированы, что затрудняет их тестирование и совершенствование. У них сложный критерий приемки и они нелинейны, их можно расценивать как черный ящик. Даже небольшие изменения могут иметь непредсказуемый эффект в неожиданном месте. Границы определяются крайне тяжело. Она говорила следующее: Разработка, управляемая данными, это действительно сложно, и мы все сейчас разбираемся в этом недостаточно хорошо.. Если коротко, то ML и CD в связке все еще работают тяжко. Звучит немного обескураживающе, однако для работы над этим требуется больше экспериментов.

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

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

Меньше слов, больше дела

Самый сильный посыл DeliveryConf заключается в том, что нужно на самом деле двигаться к CD хотя бы маленькими шажками. Сосредоточьтесь на самом важном для ускорения работы. Экспериментируйте, визуализируйте эти эксперименты, измеряйте свой прогресс. CD можно внедрить даже в legacy-системы мы слышали историю о том, как кто-то внедрил CD в приложение по сборке VAX, которому уже 45 лет!

Меня поразил акцент на маленьких шагах и экспериментах. Те же мысли мы слышим на конференциях, посвящённых тестированию и Agile-разработке. DevOps это устранение силосов в организации. Что случится, если мы будем усердно работать над устранением силосов в сообществах? Мы сможем многому научиться друг у друга. Есть множество виртуальных конференций, посвящённых DevOps, например, DivOps, Failover Conf, и All Day DevOps. Используйте эти возможности для обучения!

Автоматизация не единственная важная часть CD. Организационная культура и взаимоотношения между людьми с различными навыками и ролями также важны. Опять же, это тот посыл, которым мы делимся в сообществах, посвященных тестированию и Agile, и он справедлив практически для любой деятельности, которая помогает решать проблемы наших клиентов с помощью программного продукта.

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


Узнать подробнее о курсе "QA Lead".

Записаться на вебинар по теме: "Организация тестирования при различных методологиях разработки".

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

Кстати, о "красивой упаковке" онлайн-сертификатов мырассказываем в этой статье.

ЗАБРАТЬ СКИДКУ

Подробнее..

Обзор рынка труда QAQC в Москве

07.01.2021 14:19:53 | Автор: admin
"Word cloud" на основе описаний вакансий из раздела "Тестирование" по Москве."Word cloud" на основе описаний вакансий из раздела "Тестирование" по Москве.

Я всегда с интересом читаю обзоры рынка труда, которые публикуются на Хабре. Но, после них у меня всегда оставалось чувство легкого голода: нехватало более подробного анализа по моему сегменту рынка и региону. Да и с регулярностью все было не то чтобы хорошо. Так пару лет назад, у меня появилась идея сделать что-то вроде дашборда по рынку труда QA специалистов Москвы на основе данных HH.ru. Результаты мне показались достаточно интересными, чтобы принести их сюда.

Начну с того, чего в этом отчете нет. Не буду отбирать хлеб у авторов с "Хабр Карьера" их опросы по зарплатам трудно превзойти по степени достоверности, поэтому в моих отчетах нет цифр по заработной плате. Также нет точности в абсолютных цифрах. Причины в том, что атрибуция вакансий на HH.ru сделана своеобразно и одна вакансия может публиковаться несколько раз под разными ID. С другой стороны, одно объявление может соответствовать нескольким открытым позициям в компании. Поэтому рассматривать абсолютные цифры следует с осторожностью. Но проводить сравнительный анализ эти данные все же позволяют. Для сбора вакансий использовалась открытая часть API HH.ru, которая отдает описание вакансий в формате JSON. Часть графиков построена на базе параметров переданных в JSON-формате, часть на основе анализа текстовых описаний вакансий. Наблюдение велось с марта 2019 по декабрь 2020 гг. в разделе "Тестирование" по г. Москве. Запрос был сужен до специалистов по тестированию, вакансии из этого раздела с другой специализацией отбрасывались.

Посмотрим как менялся совокупный спрос в этом году на фоне прошлого:

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

Популярность удаленного режима работы выросла за год вдвое. Причем, именно за счет доли вакансий с полным рабочим днем в офисе. Популярность "гибкого" и "сменного" графика в вакансиях практически не изменилась. Вероятно, доля "удаленки" будет еще выше, если мы не будем учитывать в 2020 году три месяца до объявления карантинных ограничений. Понятно, что после отмены ограничений произойдет частичный откат к работе в офисах. С другой стороны, полностью провернуть этот фарш обратно тоже не получиться. О степени "отката" можно будет судить, в лучшем случае, по итогам следующего года.

На HH.ru есть возможность указать и вид найма с выбором из списка: полная занятость, частичная, проектная или стажировка. Но, в сравнении с прошлым годом, по этим показателям изменения минимальные. Полная занятость по-прежнему превалирует с долей в 97%.

Требования к опыту кандидатов изменились мало, и это "дрейф" в ожидаемом направлении: категория "от 3 до 6 лет" отъела по проценту от категорий "без опыта" и "от года до трех". Разница на грани статистической ошибки и об устойчивости этой тенденции можно будет говорить по итогам следующих лет. Кроме, того я предполагаю, что в само изначальное распределение долей внесена систематическая ошибка, о чем я напишу ниже.

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

Тем не менее, из параметров вакансий, можно выделить косвенный показатель, который описывает изменения в зарплатной политике. Это доли вакансий с указанной и не указанной зарплатой и доли вакансий в которых диапазоны зарплат открыты вверх или вниз. Доля вакансий в которых не указан размер оплаты труда выросла на вполне заметные 5%, причем за счет вакансий с "закрытым" диапазоном и тех, где диапазон был открыт в сторону повышения. Доля вакансий с указанием только верхней границы изменилась незначительно. Мой вывод: если есть возможность сэкономить на фонде заработной платы в ситуации всеобщей неопределенности, то желающие это сделать обязательно найдутся. К счастью, волны массовых сокращений не случилось, если не считать отдельных случаев, как, например, с Wildberries. Но, зато часть столичных компаний обнаружила, что можно использовать рабочую силу из провинции без релокации, а "удаленка" у нас традиционно была поводом для дисконта в пользу работодателя.

Остальные графики в отчете относятся к техническим средствам тестирования и делались на основе частоты упоминаний этих средств в текстовых описаниях вакансий. Например, вполне ожидаемая четверка лидеров среди языков программирования (в порядке убывания): Java, Python, C#, JavaScript. Сюрпризом стал только взлет языка Kotlin c восьмого места в 2019 году, на пятое в 2020-ом. В целом, в этой группе отчетов я не нашел особых откровений, лидеры было вполне ожидаемы. Но, думаю, что для тех кто выбирает сейчас "ветвь развития" в профессии такие графики будут полезны.

Не буду занимать место под подробное описание чартов с инструментами: каждому будет интересен свой раздел. За подробностями лучше обратиться к полной версии отчета (осторожно, возможен хабра-эффект). Тем более, что она пересчитывается каждый день, на основе свежих вакансий. Если на графиках не нашлось места для вашего любимого языка, тестового фреймворка или другого инструмента тестирования пишите. Я готов расширить списки отслеживаемых инструментов, так же как и внести другие улучшения.

Подробнее..

Как стать тестировщиком или что я узнал за время становления на этот путь?

16.01.2021 16:12:10 | Автор: admin

Привет Хабр! В этот пост я хочу вынести опыт на тему начинания в сфере тестирования. Здесь не будут описаны техники и правила - это уже давно есть не только на Хабре и полно учебных курсов как платных, так и бесплатных.

Картинка взята с Я.ДзенКартинка взята с Я.Дзен

Я захотел стать тестировщиком

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

Я решил стать тестировщиком

Когда решились стать тестировщиком, то просто потребляйте информацию - можно читать книги и Хабр или смотреть видео. Неважно. Главное, узнать что такое тестирование как можно подробнее и научиться тестировать тот же карандаш. Несмотря на заезженность, на собеседовании может показать бэкграунд. Также, немаловажно это общаться с единомышленниками, так как при общении с сообществом узнаёшь много нового и структурируется ранее полученная информация.

Картинка взята с blog.noveogroup.comКартинка взята с blog.noveogroup.com

Правда, в просто потребляйте информацию есть огромная проблема. Она заключается в том, что многие ресурсы в каких-то вопросах дают разные ответы. Я бы посоветовал так - изучите информацию на основе какого-то авторитетного определённого ресурса и далее на собеседованиях говорите: согласно {наименование_ресурса} я понимаю так... и тому подобное. Авторитетным ресурсом можно использовать книгу или сайты testbase.ru, software-testing.ru, а если есть желание, то можете изучать с помощью силлабуса ISTQB.

Инструменты

С чего начать? С Linux? Или Java? А может Docker?

Меня взяли в опытную команду без знаний всего что выше. На момент собеседований я что-то понимал и мог отвечать, но в команду взяли из-за софт скиллов и амбиций. Грубо говоря, меня взяли лишь за то какой я человек, подойду ли по ценностям команде и имею ли представления о работе; остальное (хард скиллс) я смогу получить уже в команде. Команде/компании дешевле научить работе с инструментами, чем исправить характер человека.

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

Опыт

Если всё замечательно с софт скиллами, выполняйте любые задачи для набивания руки. Пишите тест-кейсы к сайту 2ГИС, пишите код для автотестов на главной Яндекса. Неважно где писать, важно чтоб хоть что-то было и это важно для джуна самого.Будут ли рассматривать ваши наработки? Скорее всего, нет.

Хочтите чтоб кто-то проверил? Попробуйте написать в телеграм чат QA_Junior.

Резюме

Фото прекрасного резюме взял с okiseleva.blogspot.comФото прекрасного резюме взял с okiseleva.blogspot.com

Главное, указать именно релевантный опыт, умение работать с инструментами, знание технологий и описать всё ёмко. Если 10 лет работали в продажах и 2 года водителем, то об этом стоит написать в резюме, чтоб указать что работал, а не сидел на печи: 12 лет не релевантного опыта.

Собеседование

Прежде, чем идти на собеседование, учтите несколько фактов:

  • Тестировщик - это лицо помогающее бизнесу сэкономить денежные средства. Да-да, тестировщик помогает заработать больше и снизить риски потери денег бизнесу;

  • Тестировщик не бог и у него нет никакого права решать не выпускать продукт - мы тестировщики и наша задача протестировать да рассказать о результатах тестирования людям выше;

  • Тестировщик не нашёл/нашёл мало багов - это ничего не говорит об опыте сотрудника или качестве проверки продукта, качестве самого продукта;

  • Тестирование - это информация. Не более.

Задача тестировщика предоставить информацию о соответствии критериям качества, о проблемах, о рисках, о способах сделать лучше (если есть такие идеи) или снять боль с команды/пользователей. Но ключевое слово: предоставить информацию - автор телеграм канала Shoo and Endless Agony в чате QA juniors

Ссылки

Для ознакомления с работой тестировщика можно прочитать эту статью. Мне статья понравилась;

Обзор развития карьеры

Если уже решили стать тестировщиком, то есть курс от mail.ru, а точнее от Алексея Петрова (pifagor_mc), очень понравилась подача материала и это первое что следует посмотреть для становления;

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

Что должен уметь начинающий специалист расписано здесь в разделе С чего начать.

Ольга Назина (Киселёва) представляет примеры хорошего резюме. Очень многое можно в её блоге почитать про тестирование в целом;

Арсений Батыров, который составил резюме из предыдущей ссылки, рассказывает про составление резюме.

*В данном предложении моё мнение - это изучить инструменты более важнее для перспективы трудоустройства, чем трата времени на изучение работы маршрутизации в сети интернет или чем отличается WWW от интернет и прочее. НО! Первое, замечательно будет знать будущему тестировщику веб-сайтов принципы клиент серверной архитектуры - любые знания будут иметь вес для специалиста. Второе, со временем специалист познает основы, допустим, что такое API и чем отличается от UI во время изучения Postman.

Подробнее..

Категории

Последние комментарии

© 2006-2021, personeltest.ru