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

Karma

Внедрение CICD и DevOps в Enterprise (Ростелеком) часть 3

27.12.2020 16:14:22 | Автор: admin

Всем привет! Это третья, завершающая, часть нашего рассказа о том, как Ростелеком ИТ внедряет CI/CD & DevOps в энтерпрайзовый ИТ-ландшафт и тяжелые монолитные Legacy-системы. Первую часть про внедрение CI/CD в десятки проектных команд очень большой компании можно прочитать на Хабре по ссылке здесь. Вторую часть сугубо инженерную, с описанием прикладных подходов, инструментов и реализаций читайте тут.

Сегодня мы расскажем про процесс внедрения в рамках Karma Framework в круге. Поехали!

Круг DevOps катаем квадратное, таскаем круглое

Фреймворк сетапа команд и дальнейшей работы по внедрению CI/CD & DevOps в проектные команды Ростелеком ИТ

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

  • Партнерская модель (ИТ владеет бизнес-экспертизой и разделяет цели продукта, ИТ самостоятельно планирует развитие исходя из бизнес-задач);

  • Модель Драйвер (ИТ выступает инициатором и драйвером создания высокотехнологичных решений).

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

Итак, круги не являются формальной иерархической структурой. Каждый круг в Karma Framework должен сформулировать свое предназначение, отвечающее целям родительского круга. Для Круга DevOps оно звучит так: Развитие и внедрение технологий и инструментов в области DevOps для повышения эффективности и качества реализации цифровых продуктов.

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

  • Лидер круга драйвит тему и организует регулярные встречи в формате онлайн;

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

  • Эксперты круга помогают командам с обсуждением архитектурных вопросов;

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

Существует два типа этой роли: Делегат-тимлид/Руководитель проектов (РП) и Делегат-DevOps-специалист. Делегат-тимлид/РП отвечает за внедрение процессов в команде и имеет полномочия на такие изменения как, например, выделение ресурсов на реализацию Continuous Delivery (CI) или изменение архитектуры решения. Делегат-DevOps-специалист наиболее погружен в технические аспекты и напрямую работает с инструментами.

  • Наблюдатели приходят в Круг посмотреть и поинтересоваться. В формировании повестки встреч не участвуют, обязательства не принимают;

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

Инструменты совместной работы круга

  1. Выделенное пространство в корпоративной базе знаний (например, Confluence.

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

  2. Оперативный чат в мессенджере (у нас Телеграм)

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

  3. Встречи в формате онлайн-конференции

    Сейчас у нас Zoom, но мы планируем переехать в корпоративный TrueConf. Выделены 4 типа встреч:

    • Тактические встречи (1 раз в неделю), на которых происходит синхронизация статусов команд, обзор прогресса по ключевым задачам в соответствии с дорожными картами развития в командах. Еще на таких встречах обсуждаются новые ожидания, которые трансформируются в обязательства, а также формируется карта напряжений;

    • Техническое обсуждение/обмен опытом (по запросу) - проводятся заинтересованным кругом участников для решения конкретного технического вопроса или презентации какого-либо технического решения;

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

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

  4. Таблица (матрица) соответствия проектов основным стандартам CI/CD опросник, который заполняют команды

    Он позволяет оценить организационную и технологическую зрелость самого процесса разработки по основным направлениям и областям применения того или иного инструментария. Опросник проясняет:

    • Как именно происходит управление задачами и релизами;

    • Как организован обмен знаниями и документирование;

    • Какие инструменты CI/CD применяются;

    • Как контролируется качество кода;

    • Как мониторятся стенды и собираются метрики.

    • карта развития процессов разработки для каждой из команд.

    Каждый пункт опросника имеет вес 1 или 0 (иногда промежуточные 0.5), проект оценивается в баллах по сумме заполненных строк. Таким образом формируется общая оценка зрелости проекта. На основании таблицы формируется дорожная карта развития процессов разработки для каждой из команд.

Обязательства и продукция круга DevOps включают:

  • Формирование и встраивание фреймворка внедрения CI/CD в связке с Agile-фреймворками команд (проектов);

  • Создание, накопление, поддержание лучших практик, технологий, инструментов в области DevOps;

  • Методологическая поддержка проектов и команд в части DevOps и CI/CD;

  • Содействие обмену знаниями в командах;

  • Содействие повышению качества продуктов и сервисов;

  • Содействие повышению скорости поставки продуктов и сервисов;

  • Содействие в оптимизации процессов эксплуатации продуктов и сервисов.

Фреймворк CI/CD мы планируем привносить в команды, которые в рамках общей digital-трансформации переходят на гибкие методики. Как мы уже отмечали, мы не мыслим Agile без современных технологических инструментов управления разработкой и здесь CI/CD является основной конвейера. Но в кровавом энтерпрайзе, где многим legacy-проектам и решениям от 5 до 10 лет, а некоторым и больше, такие переходы даются не сразу и не так легко.

Общая тенденция такова команды сетапятся в Канбан (или SCRUM), в чем нам помогает очень крутой корпоративный тренер, а в качестве Sidecar к гибкой методологии мы проводим аудит состояния DevOps и CI/CD, даем команде рекомендации, делаем совместную дорожную карту и детальные планы изменений в соответствии реальными возможностями команды и архитектурой информационной системы проекта.

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

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

Конечно, DevOps не заканчивается там, где заканчивается Kanban доска. Команды вовлечены как в процесс релиза, так и в процесс эксплуатации продукта.

Фреймворк, разрабатываемый кругом позволяет на всех этапах технологического цикла выстроить эффективные процессы в команде, а также дает необходимые инструменты на каждом шаге. На рисунке ниже отображено соответствие этапов Kanban процесса, DevOps-практик и инструментов CI/CD.

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

  1. Что можно сделать быстро и без значительных затрат (организационные мероприятия, оптимизация инструментария, централизация документации и базы знаний, методологические подходы и т.п.). Примеры: создать базу знаний проекта в Confluence, ввести и описать стандарты написания кода, правила атрибуции задач, ввести регистрацию техдолга, проводить еженедельные архитектурные комитеты в проекте, описать правила выпуска релизов, проводить ретроспективу после выпуска новых релизов, создать чеклист для ввода в курс дела в команду новичков и так далее.

  2. Что можно сделать в разумный срок, за разумный бюджет (внесение небольших архитектурных изменений или новых практик и методик, приносящих ощутимый эффект) - в рамках имеющегося согласованного бюджета проекта. Примеры: внедрить логирование в Elastic Stack или Graylog, обеспечить покрытие всего кода Unit-тестами на определенный процент, внедрить регистрацию и сбор ошибок с продуктивной среды (например, с помощью Sentry), обеспечить автоматизацию UI-тестирования и т.п.

  3. Что требует дополнительного согласования бюджета из-за значительных изменений в коде или архитектуре решения. Согласование изменений с бизнесом, исходя из прогнозируемой выгоды и улучшений: ускорение цикла разработки, повышение качества, снижение затрат (стоимости владения). Примеры: переход на микросервисную и Cloud Native архитектуру, контейнеризация приложений и размещение в OpenShift-кластере, внедрение инструментария управления версионностью БД (LiquiBase), и т.п.

Новая команда, которая проходит сетап любого из Agile-фреймворков, получает в комплекте фреймворк запуска CI/CD. Команда выбирает из своего числа делегата, который вступает в круг DevOps.

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

На следующем этапе делегат заполняет матрицу соответствия своего проекта стандартам CI/CD, в которую входят показатели уровня текущих процессов и инструментов, используемых в команде. Мы получаем более-менее объективную оценку текущего уровня развития DevOps в команде, выраженную в баллах.

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

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

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

Условия работы в круге

Принципиальными моментами в работе круга DevOps является следующее:

  • Команды приходят в круг добровольно. Никакой обязаловки, никаких принудительных приводов и уговоров;

  • Задача круга как экспертного сообщества заработать репутацию (карму) таким образом, чтобы командам было интересно туда приходить. Примеры экспертных кругов с репутацией в Ростелекоме есть это Круг аналитиков или Круг тестировщиков. Круг DevOps еще совсем молодой, мы только начинаем формировать и сообщество, и репутацию и хотим, чтобы у нас было интересно и работа приносила реальную пользу проектам;

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

  • У круга нет формальных KPI. Его эффективность измеряется кармой. Карма это прежде всего оценка удовлетворенности работы кругом DevOps со стороны родительского круга Развитие цифровых технологий. Выполняем ли мы свое предназначение? Какой фидбек от бизнеса и команд? Не занимаемся ли ненужной фигней, которая никому не понятна и не приносит пользы?

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

Резюме

Экономия ресурсов и времени происходит на многих участках технологического цикла. На основе нашего опыта на десятках проектов мы в среднем получаем такие результаты:

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

  • UAT-тестирование на тяжелых проектах сокращается в среднем с 5 рабочих дней до 2-3 за счет внедрения автотестов (в масштабе 2-3-недельной итерации).

  • Подготовка релиза в среднем сокращается с 4-5 дней до 1-2 за итерацию.

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

  • На многих проектах вывод в продуктивную среду сокращается с 3-4 часов до десятков минут, делается автоматически и производится днем, что еще несколько лет назад для Ростелекома было просто немыслимым.

  • Регрессионное тестирование в среднем сокращается с 3-5 дней до 1-2 за итерацию, а количество ошибок и хотфиксов за счет автоматизации цикла сводится к минимуму.

  • Экономия по всему технологическому циклу может достигать 5 рабочих дней всей команды; на месячном релизном цикле это дает +25% производительности команды.

  • Не надо бояться разговаривать об изменениях практики разработки с бизнесом и смежными ИТ-службами.

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

Подробнее..

Тестирование KotlinJS фреймворки, корутины и все-все-все

17.01.2021 00:16:40 | Автор: admin

Kotlin - блестящий проект. Изначально задуманный как просто JVM-язык, впоследствии он получил поддержку компиляции под все мейнстримные платформы, среди которых -- JavaScript.

Вводная. У меня есть пет-проект -- сайт и API-платформа для комьюнити по игре Elite: Dangerous. Бэкенд - на Kotlin/JVM (Ktor+Hibernate), фронтенд - на Kotlin/JS (KVision+Fomantic UI). О пет-проекте я расскажу как-нибудь потом, а о фронте поподобрнее.

  • KVision - фронтэнд-фреймворк для Kotlin, объединяющий в себе идеи из различных десктопных фреймворков (от Swing и JavaFX до WinForms и Flutter) и синтаксические возможности Kotlin, например, DSL-билдеры.

  • Fomantic-UI - форк Semantic-UI, компонентного веб-фреймворка для HTML/JS, который можно сравнить с Bootstrap, только Fomantic будет поинтереснее.

Не так давно я загорелся мыслью связать эти два мира и написать библиотеку для KVision, которая бы, как минимум, облегчила написание KVision-страниц с Fomantic-элементами. И, как подобается open source проекту, я планировал покрыть библиотеку тестами. Вот об этом приключении и будет эта статья.

Код

В первую очередь определимся с задачей. Есть у нас на руках следующий код простой Fomantic-кнопки:

package com.github.kam1sh.kvision.fomanticimport pl.treksoft.kvision.html.*import pl.treksoft.kvision.panel.SimplePanelopen class FoButton(text: String) : Button(text = text, classes = setOf("ui", "button")) {    var primary: Boolean = false        set(value) {            if (value) addCssClass("primary") else removeCssClass("primary")            field = value        }}fun SimplePanel.foButton(    text: String,    init: (FoButton.() -> Unit)? = null): FoButton {    val btn = FoButton(text)    init?.invoke(btn)    add(btn)    return btn}    

И есть парочка тестов:

package com.github.kam1sh.kvision.fomanticimport kotlin.test.BeforeTestimport kotlin.test.Testimport kotlin.test.assertEqualsimport kotlinx.browser.documentimport kotlinx.coroutines.*import pl.treksoft.kvision.panel.ContainerTypeimport pl.treksoft.kvision.panel.Rootclass FoButtonTest {    lateinit var kvapp: Root    @BeforeTest    fun before() {        kvapp = Root("kvapp", containerType = ContainerType.NONE, addRow = false)            }    @Test    fun genericButtonTest() {        kvapp.foButton("Button")        assertEqualsHtml("""...""")    }    @Test    fun buttonPrimaryTest() {        val btn = kvapp.foButton("Button") { primary = true }        assertEqualsHtml("""...""")        btn.primary = false        assertEqualsHtml("""...""")    }}fun assertEqualsHtml(expected: String, message: String? = null) {    val actual = document.getElementById("kvapp")?.innerHTML    assertEquals(expected, actual, message)}

Другими словами: "вшиваем" KVision в div с id=kvapp, создаём кнопку и потом ассертим её HTML-код.

Вопрос. Откуда должен взяться первоначальный div? Можно просто добавить HTML-код через какой-нибудь document.body?.insertAdjacentHTML(...), но что, если нам очень-очень хочется добавить прямо свою страничку вроде такой?

<source lang="html"><!DOCTYPE html><html lang="en"><head>    <meta charset="UTF-8">    <script src="http://personeltest.ru/aways/cdn.jsdelivr.net/npm/jquery@3.3.1/dist/jquery.min.js"></script>    <link rel="stylesheet" type="text/css" href="http://personeltest.ru/aways/cdn.jsdelivr.net/npm/fomantic-ui@2.8.7/dist/semantic.min.css">    <script src="http://personeltest.ru/aways/cdn.jsdelivr.net/npm/fomantic-ui@2.8.7/dist/semantic.min.js"></script></head><body>    <main>        <div id="kvapp">        </div>    </main></body></html></source>

You've lost karma

Давайте сначала обратимся к разделу тестирования документации Kotlin/JS.
For browser projects, it downloads and installs the Karma test runner with other required dependencies; for Node.js projects, the Mocha test framework is used.
Ага. Karma и Mocha. Также говорится, что конфигурируется Karma через js-скрипты в папке karma.config.d.

После поиска по документации Karma по конфигам, приходим методом проб и ошибок к такому конфиг-скрипту:

// karma.config.d/page.jsconfig.set({  customContextFile: "../../../../../src/test/resources/test.html"})

Файл test.html, в моём случае, находится по пути src/test/resources/test.html. Из-за того, что Karma запускается в каталоге build/js/packages/kvision-fomantic-test/node_modules, нам для начала надо подняться на пять каталогов повыше.

Всё готово, верно? Запускаем ./gradlew browserTest, ииии... получаем Disconnected (0 times), because no message in 30000 ms.

Это всё потому, что наша HTML-страница несколько отличается от оригинальной, в которой выполняется особый JS-код. Оригинал можно посмотреть в build/js/node_modules/karma/static/context.html.

Копируем недостающий код в место сразу перед main-блоком:

<!-- The scripts need to be in the body DOM element, as some test running frameworks need the body     to have already been created so they can insert their magic into it. For example, if loaded     before body, Angular Scenario test framework fails to find the body and crashes and burns in     an epic manner. --><script src="context.js"></script><script type="text/javascript">    // Configure our Karma and set up bindings    %CLIENT_CONFIG%    window.__karma__.setupContext(window);    // All served files with the latest timestamps    %MAPPINGS%</script><!-- Dynamically replaced with <script> tags -->%SCRIPTS%<!-- Since %SCRIPTS% might include modules, the `loaded()` call needs to be in a module too.This ensures all the tests will have been declared before karma tries to run them. --><script type="module">    window.__karma__.loaded();</script><script nomodule>    window.__karma__.loaded();</script>

Запускаем, и... прикона, работает.

Корутины мои корутины

Но это всё более-менее просто. А если у нас есть корутины? Типа, HTTP-клиент Ktor или просто нам надо добавить задержку. Вот в Python мы бы просто повесили модификатор async, поставили к pytest плагин pytest-async, и всё.

Попробуем навесить suspend на функцию теста.

> Task :compileTestKotlinJs FAILED

e: ...src/test/kotlin/com/github/kam1sh/kvision/fomantic/FoButtonTest.kt: (44, 5): Unsupported [suspend test functions]

- Gradle

Хоба. Низзя. Тикет ещё не закрыт.

Ладно, тогда выполним весь код теста в runBlocking {}. Однако...

runBlocking эксклюзивен для Kotlin/JVM.

Это проблема, тоже есть тикет, правда, закрытый, мол, это by design. В качестве решения предлагается использовать GlobalScope.promise, и в принципе с ним можно написать runBlocking в одну строку:

fun runBlocking(block: suspend (scope: CoroutineScope) -> Unit) = GlobalScope.promise { block(this) }

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

config.set({  client: {    mocha: {      timeout: 9000    }  }})

Но вечно поднимать таймауты не получится. Это просто workaround.

Mocha, из которой, на самом деле, и запускается код тестов, имеет два решения:

  • Делать функцию тестов, которые принимают done-колбэк, и дёргать колбэк по завершении теста.

  • Делать функцию теста такой, чтобы она возвращала Promise.

К сожалению, если вы попробуете добавить колбэк в объявление параметров метода, то ничего хорошего вы не получите. Точнее говоря, такой коллбэк в kotlin-test-js не поддерживается.
Второе, увы и ах, тоже не сделать. Ну, тест может возвращать Promise, но в Mocha он как бы не будет передан.

Что нам, собственно, остаётся? Как связать два мира -- мир корутин и мир тестирования?

Встречайте Волшебника из Канзаса - Kotest. Его разработчики всё за нас решили.

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

// build.gradle.ktstestImplementation("io.kotest:kotest-assertions-core-js:4.3.2")testImplementation("io.kotest:kotest-framework-api-js:4.3.2")testImplementation("io.kotest:kotest-framework-engine:4.3.2")
class FoButtonTest : FunSpec({    var kvapp: Root? = null    beforeEach {        kvapp = Root("kvapp", containerType = ContainerType.NONE, addRow = false)    }    test("generic button") {        kvapp!!.foButton("Button")        assertEqualsHtml("""...""")    }    test("primary button") {        val btn = kvapp!!.foButton("Button") { primary = true }        assertEqualsHtml("""...""")        btn.primary = false        delay(200)        assertEqualsHtml("""...""")    }})

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

Если внимательно присмотреться, то можно заметить, что во втором тесте есть delay() из корутин и при этом нигде нет модификатора suspend.

А он есть:

Это пока что всё, что я могу рассказать про kotest. Я ещё буду развивать библиотеку, и когда она будет готова, я анонсирую её выход в дискорде Fomantic и слаке Kotlin/kvision. И параллельно буду познавать kotest.

Если вам этого было мало, то приношу извинения: я хотел ещё здесь показать вывод ./gradlew --debug browserTest, но подготовка этого материала и так достаточно затянулась из-за появления личной жизни, так что если интересно -- созерцайте отладочные логи Gradle сами.

Ну и что? Ну и ничего. Кушайте Kotlin/JS, запивайте kotest-ом и берегите себя.

Подробнее..

Категории

Последние комментарии

  • Имя: Макс
    24.08.2022 | 11:28
    Я разраб в IT компании, работаю на арбитражную команду. Мы работаем с приламы и сайтами, при работе замечаются постоянные баны и лаги. Пацаны посоветовали сервис по анализу исходного кода,https://app Подробнее..
  • Имя: 9055410337
    20.08.2022 | 17:41
    поможем пишите в телеграм Подробнее..
  • Имя: sabbat
    17.08.2022 | 20:42
    Охренеть.. это просто шикарная статья, феноменально круто. Большое спасибо за разбор! Надеюсь как-нибудь с тобой связаться для обсуждений чего-либо) Подробнее..
  • Имя: Мария
    09.08.2022 | 14:44
    Добрый день. Если обладаете такой информацией, то подскажите, пожалуйста, где можно найти много-много материала по Yggdrasil и его уязвимостях для написания диплома? Благодарю. Подробнее..
© 2006-2024, personeltest.ru