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

Mpp

Архитектурный шаблон MVI в Kotlin Multiplatform. Часть 3 тестирование

27.08.2020 16:05:58 | Автор: admin


Эта статья является заключительной в серии о применении архитектурного шаблона MVI в Kotlin Multiplatform. В предыдущих двух частях (часть 1 и часть 2) мы вспомнили, что такое MVI, создали общий модуль Kittens для загрузки изображений котиков и интегрировали его в iOS- и Android-приложения.

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

Обновлённый пример проекта доступен на нашем GitHub.

Пролог


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

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

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

Indeed, the ratio of time spent reading versus writing is well over 10 to 1. We are constantly reading old code as part of the effort to write new code. ...[Therefore,] making it easy to read makes it easier to write. Robert C. Martin, Clean Code: A Handbook of Agile Software Craftsmanship

Kotlin Multiplatform расширяет возможности тестирования. Эта технология добавляет одну важную особенность: каждый тест автоматически выполняется на всех поддерживаемых платформах. Если поддерживаются, например, только Android и iOS, то количество тестов можно умножить на два. И если в какой-то момент добавляется поддержка ещё одной платформы, то она автоматически становится покрытой тестами.

Тестирование на всех поддерживаемых платформах важно, потому что могут быть различия в поведении кода. Например, у Kotlin/Native особенная модель памяти, Kotlin/JS тоже иногда даёт неожиданные результаты.

Прежде чем идти дальше, стоит упомянуть о некоторых ограничениях тестирования в Kotlin Multiplatform. Самое большое из них это отсутствие какой-либо библиотеки моков для Kotlin/Native и Kotlin/JS. Это может показаться большим недостатком, но я лично считаю это преимуществом. Мне довольно трудно давалось тестирование в Kotlin Multiplatform: приходилось создавать интерфейсы для каждой зависимости и писать их тестовые реализации (fakes). На это уходило много времени, но в какой-то момент я понял, что трата времени на абстракции это инвестиция, которая приводит к более чистому коду.

Я также заметил, что последующие модификации такого кода требуют меньше времени. Почему так? Потому что взаимодействие класса с его зависимостями не прибито гвоздями (моками). В большинстве случаев достаточно просто обновить их тестовые реализации. Нет необходимости углубляться в каждый тестовый метод, чтобы обновить моки. В результате я перестал использовать библиотеки моков даже в стандартной Android-разработке. Я рекомендую прочитать следующую статью: "Mocking is not practical Use fakes" (автор Pravin Sonawane).

План


Давайте вспомним, что у нас есть в модуле Kittens и что нам стоит протестировать.

  • KittenStore основной компонент модуля. Его реализация KittenStoreImpl содержит бОльшую часть бизнес-логики. Это первое, что мы собираемся протестировать.
  • KittenComponent фасад модуля и точка интеграции всех внутренних компонентов. Мы покроем этот компонент интеграционными тестами.
  • KittenView публичный интерфейс, представляющий UI, зависимость KittenComponent.
  • KittenDataSource внутренний интерфейс для доступа к Сети, который имеет платформенно-зависимые реализации для iOS и Android.

Для лучшего понимания структуры модуля приведу его UML-диаграмму:



План следующий:

  • Тестирование KittenStore
    • Создание тестовой реализации KittenStore.Parser
    • Создание тестовой реализации KittenStore.Network
    • Написание модульных тестов для KittenStoreImpl

  • Тестирование KittenComponent
    • Создание тестовой реализации KittenDataSource
    • Создание тестовой реализации KittenView
    • Написание интеграционных тестов для KittenComponent

  • Запуск тестов
  • Выводы


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


Интерфейс KittenStore имеет свой класс реализации KittenStoreImpl. Именно его мы и собираемся тестировать. Он имеет две зависимости (внутренние интерфейсы), определённые прямо в самом классе. Начнём с написания тестовых реализаций для них.

Тестовая реализация KittenStore.Parser


Этот компонент отвечает за сетевые запросы. Вот как выглядит его интерфейс:


TestKittenStoreNetwork имеет хранилище строк (как и настоящий сервер) и может их генерировать. По каждому запросу текущий список строк кодируется в одну строку. Если свойство images равно нулю, то Maybe просто завершится, что должно рассматриваться как ошибка.

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

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

Тестовая реализация KittenStore.Parser


Этот компонент отвечает за разбор ответов от сервера. Вот его интерфейс:


Как и в случае с Network, используется TestScheduler для замораживания подписчиков и проверки их совместимости с моделью памяти Kotlin/Native. Ошибки обработки ответов моделируются, если входная строка пуста.

Модульные тесты для KittenStoreImpl


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

Первый шаг создать экземпляры наших тестовых реализаций:


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


Этапы теста:

  • сгенерировать исходные изображения;
  • создать экземпляр KittenStoreImpl;
  • сгенерировать новые изображения;
  • отправить Intent.Reload;
  • убедиться, что состояние содержит новые изображения.

И наконец давайте проверим следующий сценарий: когда в состоянии установлен флаг isLoading во время загрузки изображений.


Есть две зависимости: KittenDataSource и KittenView. Нам понадобятся тестовые реализации для них, прежде чем мы сможем начать тестирование.

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



Тестовая реализация KittenDataSource


Этот компонент отвечает за сетевые запросы. У него есть отдельные реализации для каждой платформы, и нам нужна ещё одна реализация для тестов. Вот как выглядит интерфейс KittenDataSource:


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

Для формирования JSON-массива используется библиотека kotlinx.serialization. Кстати, тестируемый KittenStoreParser использует её же для декодирования.

Тестовая реализация KittenView


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


Нам просто нужно запоминать последнюю принятую модель это позволит проверить правильность отображаемой модели. Мы также можем отправлять события от имени KittenView с помощью метода dispatch(Event), который объявлен в наследуемом классе AbstractMviView.

Интеграционные тесты для KittenComponent


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

Как и раньше, давайте начнём с создания экземпляров зависимостей и инициализации:


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


Этапы:

  • сгенерировать исходные ссылки на изображения;
  • создать и запустить KittenComponent;
  • сгенерировать новые ссылки;
  • отправить Event.RefreshTriggered от имени KittenView;
  • убедиться, что новые ссылки достигли TestKittenView.


Запуск тестов


Чтобы запустить все тесты, нам нужно выполнить следующую Gradle-задачу:

./gradlew :shared:kittens:build

Это скомпилирует модуль и запустит все тесты на всех поддерживаемых платформах: Android и iosx64.

А вот JaCoCo-отчёт о покрытии:



Заключение


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

  • KittenStoreImpl содержит бОльшую часть бизнес-логики;
  • KittenStoreNetwork отвечает за сетевые запросы высокого уровня;
  • KittenStoreParser отвечает за разбор сетевых ответов;
  • все преобразования и связи.

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

Такие тесты имеют следующие преимущества:

  • не используют платформенные API;
  • выполняются очень быстро;
  • надёжные (не мигают);
  • выполняются на всех поддерживаемых платформах.

Мы также смогли проверить код на совместимость со сложной моделью памяти Kotlin/Native. Это тоже очень важно из-за отсутствия безопасности во время сборки: код просто падает во время выполнения с исключениями, которые трудно отлаживать.

Надеюсь, это поможет вам в ваших проектах. Спасибо, что читали мои статьи! И не забудьте подписаться на меня в Twitter.



Бонусное упражнение


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

Рефакторинг KittenDataSource


В модуле существуют две реализации интерфейса KittenDataSource: одна для Android и одна для iOS. Я уже упоминал, что они отвечают за доступ к сети. Но на самом деле у них есть ещё одна функция: они генерируют URL-адрес для запроса на основе входных аргументов limit и page. В то же время у нас есть класс KittenStoreNetwork, который ничего не делает, кроме делегирования вызова в KittenDataSource.

Задание: переместить логику генерирования URL-запроса из KittenDataSourceImpl (на Android и iOS) в KittenStoreNetwork. Вам нужно изменить интерфейс KittenDataSource следующим образом:



Как только вы это сделаете, вам нужно будет обновить тесты. Единственный класс, к которому вам потребуется прикоснуться, это TestKittenDataSource.

Добавление постраничной загрузки


TheCatAPI поддерживает разбивку на страницы, поэтому мы можем добавить эту функцию для лучшего взаимодействия с пользователем. Вы можете начать с добавления нового события Event.EndReached для KittenView, после чего код перестанет компилироваться. Затем вам нужно будет добавить соответствующий Intent.LoadMore, преобразовать новый Event в Intent и обработать последний в KittenStoreImpl. Вам также потребуется изменить интерфейс KittenStoreImpl.Network следующим образом:



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

Подробнее..

Перевод Перспективные архитектуры для современных инфраструктур данных

04.05.2021 18:23:50 | Автор: admin

На сегодняшний день базы данных класса Massive Parallel Processing это отраслевой стандарт для хранения Больших Данных и решения разнообразных аналитических задач на их основе.

Сценарии использования mpp-баз разнообразны: они могут быть и рабочей лошадкой корпоративного BI, и инструментом централизации данных из сотен источников в одном DWH, и даже использоваться для горячего взаимодействия с ML-моделями, работающими в продуктивном окружении.

Данный класс технологий необходимый элемент в инструментарии современного Data Engineer.

На демо-занятии мы подробно разберем, что же такое mpp-БД, какие решения есть сегодня на рынке и даже углубимся в практический пример использования одной их самых инновационных mpp-систем на сегодня: ClickHouse.

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


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

Многие из самых быстрорастущих инфраструктурных стартапов сегодня создают продукты для управления данными. Эти системы позволяют принимать решения на основе данных (аналитические системы) и управлять продуктами на основе данных, в том числе с помощью машинного обучения (оперативные системы). Они варьируются от конвейеров, по которым передаются данные, до решений для их хранения, SQL-движков, которые анализируют данные, дашбордов для мониторинга, которые упрощают понимание данных от библиотек машинного обучения и data science до автоматизированных конвейеров данных, каталогов данных и т.д.

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

Инфраструктура данных включает

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

Стремительный рост рынка инфраструктуры данных

Одной из основных причин, из-за которых был составлен этот доклад, является стремительный рост инфраструктуры данных за последние несколько лет. По данным Gartner, расходы на инфраструктуру данных достигли в 2019 году рекордного показателя в 66 миллиардов долларов, что составляет 24% и эта цифра растет всех расходов на программное обеспечение для инфраструктуры. По данным Pitchbook, 30 крупнейших стартапов по созданию инфраструктуры данных за последние 5 лет привлекли более 8 миллиардов долларов венчурного капитала на общую сумму 35 миллиардов долларов.

Венчурный капитал, привлеченный наиболее показательными стартапами в области инфраструктуры данных в 2015-2020 гг.

Примечание: Любые инвестиции или портфельные компании, упомянутые или описанные в этой презентации, не являются репрезентативными для всего объема инвестиций во все инвестиционные каналы, управляемые a16z, и нет никаких гарантий, что эти инвестиции будут прибыльными или что другие инвестиции, сделанные в будущем, будут иметь аналогичные характеристики или результаты. Список инвестиций, сделанных фондами под управлением a16z, доступен здесь: https://a16z.com/investments/.

Гонка за данными также отражается на рынке труда. Аналитики данных, инженеры по обработке данных и инженеры по машинному обучению возглавили список самых быстрорастущих специальностей Linkedin в 2019 году. По данным NewVantage Partners 60% компаний из списка Fortune 1000 имеют директоров по обработке и анализу данных, по сравнению с 12% в 2012 году, и согласно исследованию роста и прибыльности McKinsey эти компании значительно опережают своих коллег.

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

Унифицированная архитектура инфраструктуры данных

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

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

Результатом этих обсуждений стала следующая диаграмма эталонной архитектуры:

Unified Architecture for Data Infrastructure

Унифицированная архитектура для инфраструктуры данных

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

Столбцы диаграммы определены следующим образом:

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

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

Аналитика, AI/ML и грядущая конвергенция?

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

Вокруг этих вариантов использования выросли две параллельные экосистемы. Основу аналитической экосистемы составляют хранилища данных (data warehouse). Большинство хранилищ данных хранят данные в структурированном формате и предназначены для быстрого и простого получения выводов на основе обработки основных бизнес-метрик, обычно с помощью SQL (хотя Python становится все более популярным). Озеро данных (data lake) является основой оперативной экосистемы. Сохраняя данные в необработанном виде, он обеспечивает гибкость, масштабируемость и производительность, необходимые для специализированных приложений и более сложных задач обработки данных. Озера данных работают на широком спектре языков, включая Java/Scala, Python, R и SQL.

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

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

Архитектурные сдвиги

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

Новые возможности

Формируется набор новых возможностей обработки данных, которые нуждаются в новых наборах инструментов и базовых систем. Многие из этих трендов создают новые категории технологий (и рынки) с нуля.

Схемы построения современной инфраструктуры данных

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

Здесь мы предоставим обзор трех обобщенных схем. Мы начнем схемы современной бизнес-аналитики, которая фокусируется на облачных хранилищах данных и аналитических вариантах использования. Во второй схеме мы рассматриваем мультимодальную обработку данных, охватывая как аналитические, так и оперативные варианты использования, построенные на основе озера данных. В окончательной схеме мы подробно рассмотрим оперативные системы и новые компоненты AI и ML стека.

Три обобщенных схемы

Схема 1: современная бизнес-аналитика

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

Перейдите сюда, чтобы просмотреть версию в высоком разрешении.

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

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

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

Схема 2: мультимодальная обработка данных

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

Перейдите сюда, чтобы просмотреть версию в высоком разрешении.

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

Сценарии использования включают в себя как бизнес-аналитику, так и более продвинутые функции, включая оперативный AI/ML, аналитику, чувствительную к потоковой передаче / задержке, крупномасштабные преобразования данных и обработку различных типов данных (включая текст, изображения и видео) с использованием целого набора языков (Java/Scala, Python, SQL).

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

Схема 3: Искусственный интеллект и машинное обучение.

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

Перейдите сюда, чтобы просмотреть версию в высоком разрешении.

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

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

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

Смотря в будущее

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

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


Перевод подготовлен в рамках онлайн-курса "Data Engineer".

Смотреть вебинар Введение в MPP-базы данных на примере ClickHouse.

Подробнее..

Категории

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

  • Имя: Макс
    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