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

Блог компании testo lang

Настраиваем и автоматизируем развёртывание Active Directory

03.11.2020 08:13:00 | Автор: admin


В этой статье я бы хотел предложить вам пошаговый туториал по развёртыванию контроллера домена Active Directory на Windows Server 2016 (с графической оболочкой), а также по вводу рабочей станции в получившийся домен. Чем этот туториал может выделиться на фоне других:


  1. Вместо простого "Далее, Далее, кликаем сюда, вбиваем это" я постарался дать внятное объяснение каждому шагу, каждой настройке, которую предстоит выполнить. Помимо основных понятий Active Directory, DNS и DHCP вы также сможете найти много интересной информации по всем галочкам, которые вы часто видели, но не задумывались об их назначении.
  2. В конце статьи я предложу способ автоматизировать развёртывание получившегося стенда полностью с нуля, имея на компьютере только iso-образы ОС Windows 7 и Windows Server 2016. И никакого PowerShell. Всего одной командой.

Статья предполагает наличие у читателя лишь самых начальных знаний об устройстве сетей (на уровне "Что такое IP-адрес и DNS-адрес").


Заинтересовало что-то из вышеперечисленного? Тогда погнали.


Туториал будет происходить не в вакууме, а на конкретном виртуальном стенде, состоящим из двух виртуальных машин:



Начальное состояние стенда:


  1. На машине windows_server уже установлена ОС Windows Server 2016 Standard Evaluation (с GUI). Машина находится в состоянии "сразу после установки ОС". В процессе туториала на ней будут развернуты службы Active Directory (с доменом mydomain.com), DNS и DHCP.


  2. Машина workstation выполняет роль рабочей станции. На ней установлена ОС Windows 7. Машина находится в состоянии "сразу после установки ОС". В процессе туториала она будет подключена к домену mydomain.com.



Туториал построен следующим образом (если вам интересен только конкретный пункт смело кликайте прямо туда):


  1. Объясню, почему я выбрал именно такой стенд для туториала;
  2. Супер-краткое описание технологии Active Directory;
  3. Выполняется небольшая предварительная настройка windows_server;
  4. На windows_server производится включение необходимых компонентов;
  5. На windows_server происходит настройка контроллера домена AD (совместно с DNS);
  6. На windows_server происходит настройка сервера DHCP;
  7. На windows_server регистрируется новая учетная запись в AD;
  8. На workstation происходит подключение к домену.

В конце туториала вас ждет приятный бонус я покажу вам как можно развернуть у себя на компьютере весь этот работающий стенд одной единственной командой. Вам понадобится только наличие двух установочных iso-образов (windows 7 и windows server 2016), да небольшой скрипт, ссылку на который я вам дам в конце статьи.


Почему такой стенд?


Такой стенд, с моей точки зрения, отлично подходит для первого самостоятельного "прощупывания" технологии Active Directory. Он минималистичен (всего 2 виртуальные машины), занимает минимум ресурсов, но при этом изолирован и самодостаточен. Его можно развернуть даже на довольно средненьком компьютере и ноутбуке. При этом на стенде уже присутствуют основные сетевые службы (AD + DNS). DHCP хоть и необязателен для функционирования AD, всё равно был добавлен в стенд в ознакомительных целях.


Disclaimer

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


Туториал предполагает подробный разбор всех шагов по настройке, с пояснениями "что, зачем и почему". Туториал ориентирован на людей, не слишком знакомых с технологиями Active Directory, DNS и DHCP, которые хотели бы немного узнать о внутренней кухне администрирования сетей с Active Directory.


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


Что такое Active Directory


Active Directory это службы каталогов от компании Microsoft, как подсказывает нам Википедия. За этим сухим и невзрачным определением скрывается одна из важнейших технологий в администрировании сетей. Благодаря Active Directory администоратор сети получает очень удобное централизированное средство управления учетными записями пользователей, групповыми политиками (в т.ч. политиками безопасности) и объектами в сети (причём Active Directory без особых проблем справляется даже с гигантскими сетями). А благодаря встроенному механизму репликации, "положить" правильно настроенные сервисы AD не так-то просто. Ну и напоследок, благодаря Windows, настроить Active Directory можно буквально мышкой, так что даже совсем начинающие IT-шники смогут с этим справиться.


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


Начинаем


Вы установили Windows Server 2016 и (надеюсь) видите следующий экран:



Эта панель основное (графическое) средство администрирования Windows Server 2016. Здесь вы можете управлять компонентами и сервисами на вашем сервере (проще говоря, настраивать то, что умеет делать сервер). Эту же панель можно использовать и для базовых сетевых настроек Windows Server, для чего есть вкладка "Локальный сервер".


Базовые настройки Windows Server


Первое, что нужно сделать это поменять сетевое имя сервера.


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


Проблема в том, что по-умолчанию для Windows Server генерируется совершенно нечитаемое и неинформативное сетевое имя (я выделил его красным цветом на скриншоте).


Рабочии станции ещё могут позволить себе иметь нечитаемый Hostname, но никак не сервер. Поэтому я предлагаю поменять эту абракадабру его на что-то более разумное (например, на ADController), благо делается это быстро.


Смена сетевого имени

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



После смены имени машину нужно будет перезагрузить.


Теперь зададим статический IP-адрес для сервера. В принципе это делать не обязательно, раз мы всё равно собрались поднимать DHCP службу, но на самом деле это хорошая практика, когда все ключевые элементы корпоративной сети имеют фиксированные адреса. Открыть меню по настройке сетевого адаптера можно из вкладки "Локальный сервер", кликнув на текущие настройки Ethernet-адаптера (тоже выделены красным цветом).


Настройки IP для интерфейса windows_server


Включаем нужные компоненты


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


  • Доменные службы Active Directory;
  • DNS-сервер;
  • DHCP-сервер.

Пройдемся вкратце по каждому из них.


Доменные службы Active Directory


Эта роль фактически "включает" технологию Active Directory на сервере и делает его контроллером домена (под доменом в технологии AD понимается группа логически связанных объектов в сети). Благодаря этой роли администратор получает возможность управлять объектами в сети, а также хранить информацию о них в специальной распределенной базе данных.


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


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


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


С этим пунктом все более менее понятно, а зачем же нам включать дополнительно ещё DNS-сервер?


DNS-сервер


Обычно протокол DNS (Domain Name System) используется для обращения к узлам в сети не по их IP-адресу, а по доменному имени (строковый идентификатор), что, конечно, гораздо удобнее. Другими словами, DNS чаще всего используется для разрешения доменных имен.


Но область применения протокола DNS не ограничивается только сопоставлением хостового имени и IP-адреса, что как раз подтверждает технология Active Directory. Дело в том, что Microsoft решила построить технологию Active Directory не с нуля, а на основе протокола DNS. В частности, протокол DNS используется при определении местонахождения всех ключевых сервисов Active Directory в сети. Другими словами, рабочая станция при подключении к контроллеру домена понимает, "куда" ей надо обращаться, именно с помощью протокола DNS.


Все DNS-записи (в том числе с информацией о сервисах Active Directory) хранятся на DNS-сервере, а это значит, что нам нужно заиметь свой собственный DNS-сервер! Вот только вопрос, откуда его взять? Есть два варианта:


  1. Использовать отдельную машину в роли DNS-сервера;
  2. Использовать саму машину windows_server в роли DNS-сервера.

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


Именно поэтому эту роль (DNS-сервера) тоже нужно добавить к ролям машины windows_server.


Кстати, если не добавить роль "DNS-сервер" сейчас, то в будущем у вас ещё будет такая возможность при конфигурировании контроллера домена AD.


DHCP-сервер


Протокол DHCP (Dynamic Host Configuration Protocol) нужен для автоматической выдачи сетевых настроек узлам в сети. Под сетевыми настройками понимается IP-адрес, адрес шлюза по-умолчанию, адрес DNS-сервера, и ещё ряд других настроек. Этот протокол чрезвычайно удобен при администрировании сетей, особенно больших.


В этом туториале я использую протокол DHCP чтобы рабочая станция workstation могла получить сетевые настройки (в частности, адрес DNS-сервера) без каких-либо действий с моей стороны.


Протокол DHCP не имеет никакого отношения к технологии Active Directory, и можно было бы обойтись вовсе без него (достаточно прописать все сетевые настройки на рабочей станции самостоятельно), но я решил включить этот протокол в данный туториал просто для общего ознакомления. К тому же, такая связка "Контроллер AD DNS-сервер DHCP-сервер" довольно часто встречается в реальной жизни, потому что это очень удобный набор сервисов.


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


Что ж, довольно теории, давайте лучше перейдём к включению этих самых ролей.


Мастер добавления ролей и компонентов


Возвращаемся на панель мониторинга (самый первый скриншот) и щелкаем на пункт "Добавить роли и компоненты". Вас поприветствует мастер добавления ролей и компонентов. Первый экран ("Перед началом работы") пропускаем, он совсем неинтересный, а вот дальше идёт экран "Выбор типа установки"


Выбор типа установки


Нас устраивает значение по-умолчанию (Установка ролей или компонентов"), но интересен и второй пункт он позволяет задействовать ещё одну возможность Windows Server инфраструктуру виртуальных рабочих мест (Virtual Desktop Environment VDI). Эта интереснейшая технология позволяет, буквально, виртуализировать рабочее место. То есть для пользователя создаётся виртуальное рабочее место, к которому он может подключаться через тонкий клиент. Пользователь лишь видит картинку, тогда как само рабочее место может совершенно прозрачно работать где угодно.


Впрочем, технология VDI это отдельная большая тема, а в этом туториале надо сосредоточиться на контроллере AD, так что кликаем "Далее" и видим экран выбора целевого сервера.


Выбор целевого сервера


Мастер добавления ролей позволяет устанавливать роль не только на текущую машину, но вообще на любой добавленный сервер, и даже на виртуальный жёсткий диск. Да, если ваша Windows Server развернута на виртуальной машине (а это довольно частое явление), то вы можете администрировать эту виртуальную машину даже не запуская её! Понаблюдать за этим процессом можно, например, здесь.


Нам же такая экзотика ни к чему, так что просто выбираем единственный возможный сервер (обратите внимание, что он теперь называется ADController место непонятной абракадабры), жмём "Далее" и, наконец, попадаем на экран выбора ролей, которые нужно добавить.


Выбор добавляемых ролей


Выбираем три роли, о которых уже говорили ранее, и продолжаем.


Выбор компонентов


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


Согласно идеологии Microsoft, роль это набор программ, которые позволяют компьютеру предоставлять некоторые функции для пользователей в сети. Например, DNS, DHCP, контроллер домена AD это всё роли. А вот компоненты это набор программ, которые улучшают либо возможности ролей сервера, либо самого сервера.


При этом глядя на список "Компонентов" так сходу и не скажешь, что какие-то вещи в списке лишь "вспомогательные". Вот например, DHCP-сервер расценивается как роль, а WINS-сервер уже как компонент. А чем SMTP-сервер хуже DNS?


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


В любом случае, дополнительные компоненты нам не нужны, так что кликаем "Далее".


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


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


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


Остаётся лишь дождаться, когда заполнится progress-bar, и перейти к следующему пункту туториала настройке контроллера домена AD.


Настраиваем контроллер домена Active Directory


Все роли и компоненты успешно добавлены, о чём свидетельствует следующий экран:



Вот только AD на сервере всё еще не работает для этого его необходимо донастроить. Для этого нам настойчиво предлагают "Повысить роль этого сервера до уровня контроллера домена".


Погодите-ка, ЧТО?!


А чем же я занимался последние 15 минут? Я же добавлял роли, и судя по сообщению, они успешно добавились! И тут меня снова хотят заставить добавлять какие-то новые роли? В чем-то тут подвох.


Подвох тут действительно имеется, но вообще в не самом очевидном месте. Вот так выглядит предыдущий скриншот в английской версии Windows Server (картинка из интернета).


Английская версия скриншота


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


Что ж, тыкаем на предложение "Повысить роль этого сервера до уровня контроллера домена", и теперь нас привествует мастер настройки доменных служб Active Directory с предложением выбрать конфигурацию развёртывания.


Конфигурация развёртывания


Всего тут есть 3 варианта развития событий. Для того, чтобы выбрать правильный пункт, давайте сначала разберёмся, что эти пункты означают. В этом нам поможет вот такая картинка (картинка, если что, отсюда):



Технология Active Directory (как и DNS) подразумевает иерархическое построение имён на основе доменов. Домены могут выстраиваться в доменные деревья по принципу "родительско-дочерних" отношений. В основе дерева лежит так называемый корневой домен (на картинке выше это sources.com, xyz.com и abc.com). При этом домен может иметь сколько угодно потомков. Домен-потомок располагается в просранстве имён родителя и является его "поддоменом" (subdomain). У доменного имени домена-потомка есть дополнительный префикс относительно доменного имени родителя (rus.abc.com, eng.abc.com). Один корневой домен основывает только одно доменное дерево со своим независимым пространством имён.


Теперь представьте, что таких независимых деревьев может быть много в этом случае эти деревья образуют структуру, которая называется "лес". При этом в Active Directory доменные деревья не могут быть "сами по себе" они обязательно должны находиться в лесу (даже если лес будет состоять всего из одного-единственного домена). Первый домен, который добавляется в лес, называется корневым доменом леса (на рисунке выше это sources.com). Корневой домен леса используется для идентификации всего леса (то есть если корневой домен называется sources.com, то и весь лес называется sources.com).


Теперь возвращаемся к мастеру настройки доменных имен. На этом этапе мастер предлагает следующие варианты:


  1. Добавить контроллер домена в существующий домен (помните про резервирование контроллеров в домене, так ведь?). Этот вариант не для нас, ведь домена ещё никакого нет;
  2. Добавить новый домен в лес. Этого мы тоже сделать не можем, т.к. и леса у нас тоже никакого нет;
  3. Добавить новый лес. Это вариант как раз для нас. При этом нам тут же предлагают выбрать корневой домен для этого леса (первый домен, который будет создан в лесу).

Назовём корневой домен mydomain.com и кликнем "Далее"


Параметры контроллера домена


Рассмотрим возможные параметры:


  1. Режим работы леса и домена. Домены в одном лесе могут работать в разных режимах в зависимости от версии Windows Server на борту. Лес должен иметь режим не выше, чем самый "старый" домен в его составе. Т.к. мы планируем использовать только Windows Server 2016, то оставим этот режим и для леса и для домена;
  2. DNS-сервер. Если ранее Вы не активировали роль DNS-сервера в мастере добавления ролей, то можете сделать это сейчас (вам даже предложат такой вариант по-умолчанию);
  3. Должен ли контроллер домена выступать в роли Global Catalog-сервера;
  4. Включить ли режим базы данных Active Directory "только на чтение". Основная задача, которую преследует технология RODC возможность безопасной установки собственного контролера домена в удаленных филиалах и офисах, в которых сложно обеспечить физическую защиту сервера с ролью DC. Контроллер домена RODC содержит копию базы Active Directory, доступную только на чтение. Это означает, что никто, даже при получении физического доступа к такому контроллеру домена, не сможет изменить данные в AD (в том числе сбросить пароль администратора домена) (информация взята отсюда)

А вот пункт 3 рассмотрим поподробнее, он довольно интересный.


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


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


Что такое вообще "Глобальный каталог"? Согласно Miscrosoft это распределенное хранилище данных, которое хранит частичное представление обо всех AD-объектах в лесу. Это хранилище располагается на котроллерах домена, которые имеют дополнительную роль "Global Catalog Server" (Сервер глобального каталога). От обычного кнтроллера домена GC-сервер отличается в первую очередь тем, что помимо полной копии всех объектов в своем домене, хранит также частичную информацию обо всех объектах в других доменах леса.


Чего это позволяет достичь? Давайте представим, что рабочая станция запросила информацию об объекте из другого домена. Она обращается на GC-сервер с просьбой предоставить ей информацию об этом объекте. GC-сервер, в свою очередь, может:


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

Информация о том, какие атрибуты попадают в глобальный каталог, определена в Partial Attribute Set (PAS), который может настраивать администратор AD. Например, если администроатр понимает, что рабочие станции часто будут обращаться к атрибуту, который не содержится в глобальном каталоге, он может добавить туда этот атрибут. Тогда запросы рабочих станций при чтении этого атрибута будут выполняться значительно быстрее, т.к. уже ближайший GC-сервер сможет предоставить им всю нужную информацию.


Однако, если в лесе всего один домен (как у нас), то Глобальный каталог содержит полную копию объектов в домене и всё.


Что ж, возвращаемся к галочке GC, которую за нас уже проставил мастер настройки доменных служб. Если вы попробуете её отключить, то убедитесь, что отключить её нельзя. Это связано с тем, что каждый домен в AD должен иметь хотя бы один GC-сервер, и при добавлении первого контроллера в домен этот контроллер сразу помечается как GC-сервер.


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


  • база Active Directory повреждена и нуждается в исправлении;
  • требуется выполнить обслуживание базы данных AD (сжатие, анализ на наличие ошибок);
  • требуется восстановить резервную копию базы данных AD;
  • требуется сменить пароль администратора.

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


Фух, вроде разобрались. Давайте перейдем дальше на шаг, где нам предложат настроить делегирование DNS.


Делегирование DNS


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


Т.к. у нас всего одна зона DNS и DNS-сервер тоже один, то этот шаг нам необходимо пропустить и перейти к выбору NetBIOS-имени.


NetBIOS-имя


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


NetBIOS (Network Basic Input/Oputout) это ещё один способ разрешения имён узлов в сети (более древний и более примитивный, чем DNS). NetBIOS-имена не предполагают никакой иерархии, их длина ограничивается всего лишь 16 символами, и они применяются только для разрешения имён компьютеров в локальной сети. Когда мы в самом начале туториала выбрали сетевое имя ADController мы, на самом деле, задали именно NetBIOS-имя для сервера. Но теперь от нас снова требуют выбрать NetBIOS-имя (да ещё и другое, отличное от ADContoller). Не много ли NetBIOS-имён для одного компьютера?


Дело в том, что Microsoft пошла ещё дальше и ограничила длину NetBIOS-имен не 16 символами, а 15 символами. 16-ый символ при этом считается зарезервированным суффиксом, который может принимать фиксированные значения. В зависимости от значения 16-го байта получаются разные классы NetBIOS-имён. Например, если суффикс равен 00, то NetBIOS-имя относится к рабочей станции. Если суффикс равен 1С, то это имя относится к имени домена.


То есть, как вы понимаете, на первом шаге мы задавали NetBIOS-имя для компьютера Windows Server (с суффиком 00). А теперь задаём NetBIOS-имя домена mydomain.com (с суффиксом 1С).


Кстати, можете, ради интереса, отмотать туториал в самое начало и посчитать количество символов в "нечитаемом" автоматически сгенерированном сетевом имени windows_server. Будет как раз 15 символов (максимальная длина NetBIOS-имени).


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


Что ж, и с этим тоже разобрались. Оставляем NetBIOS-имя по-умолчанию и двигаемся дальше, к выбору места расположения базы данных AD. Можно оставить значение по-умолчанию, комментировать особо нечего.


Расположение базы данных


Все ваши настройки должны пройти предварительную проверку:


Проверка предварительных требований


Как только всё готово, жмите "Установить" и спокойно идёте пить чай, потому что после установки автоматически начнётся очень-очень долгая перезагрузка. Зато настройка контроллера домена AD на этом закончена, поздравляю!


Настройка DHCP-сервера


Пришло время заняться настройкой DHCP-сервера. Настройка глобально состоит из двух частей:


  1. Авторизация DHCP-сервера в домене AD. Не каждый DHCP-сервер может раздавать сетевые настройки в домене AD только авторизованные. Это сделано с целях безопасности, чтобы другие DHCP-серверы не могли "подсунуть" неправильные настройки компьютерам в домене;
  2. Настройка новой DHCP-области. Это уже непосредственно настройка самого DHCP-сервера, в ходе которой определяются какие сетевые настройки будут выдаваться компьютерам в сегменте сети.

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


Запуск авторизации DHCP-сервера




В открывшемся мастере настройки DHCP после установки пропускаем первый приветственный экран и переходим к экрану авторизации


Авторизация DHCP-сервера в домене


На выбор предлагаются три варианта:


  1. Использовать учётные администратора (по-умолчанию)
  2. Использовать учётные данные другого пользователя;
  3. Пропустить авторизацию AD.

По-умолчанию авторизовать DHCP-сервер в домене могут только члены группы EnterpriseAdmins, куда как раз и входит пользователь MYDOMAIN\Администратор. При желании можно потратить немного времени и делегировать эту возможность админам "помельче" (региональным администраторам), подчерпнуть больше информации по этой теме можно отсюда.


Итак, выбираем вариант по-умолчанию и завершаем первый этап настроки DHCP-сервера.


Завершение авторизации DHCP-сервера


Теперь переходим непосредственно к настройкам DHCP. Для этого на панели мониторинга кликаем вкладку "Средства" и выбираем пункт "DHCP"



В открывшемся окне с настройками DHCP нужно кликнуть правой кнопкой мышки на IPv4 и затем на пункт меню "Создать область". После этого откроется мастер создания новой области.


Открытие мастера создания новой области



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


Назовём DHCP-область SCOPE1 и перейдём дальше.


На следующем экране вам предложат выбрать диапазон адресов, которые будут выдаваться компьютерам в сети. Ранее я настраивал сетевой интерфейс на Windows Server, выдав ему адрес 192.168.1.1/24. Это статический адрес и он зарезервирован, его выдавать другим компьютерам нельзя.


Зато никто не мешает выдавать все остальные адреса в сети 192.168.1.0/24 так что задаём диапазон от 192.168.1.2 до 192.168.1.254 (192.168.1.255 это зарезервированный широковещательный адрес, его выдавать тоже нельзя).


Настройка диапазона адресов


В целом, никто не мешает вам как администратору выдавать меньше IP-адресов, чем доступно в адресации сети. Например, можно было бы выделить в сети всего 100 адресов для автоматической выдачи: 192.168.1.101-192.168.1.200.


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


Исключения в диапазоне и задержка DHCPOFFER


С исключениями всё более-менее понятно: если вы не хотите выдавать некоторые адреса в указанном ранее диапазоне, то вы можете указать эти адреса здесь в виде исключений. А что за задержка в DHCPOFFER такая?


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


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


Настройка времени аренды адресов


Протокол DHCP предполагает выделение адресов только на определённое время, после чего компьютеры должны продлять аренду. Здесь можно настроить это время (по-умолчанию 8 дней).


8 дней меня лично вполне устраивает, так что кликаем "Далее" и видим предложение настроить другие настройки, которые будут получать клиенты в сети (помимо IP-адреса). Соглашаемся.


Настроить дополнительные параметры


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


Шлюз по-умолчанию


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


А вот для понимания имени родительского домена, рассмотрим следующую ситуацию.


Допустим, есть домен mydomain.com и есть два компьютера в этом домене с именами comp1.mydomain.com и comp2.mydomain.com. Если comp1 хочет связаться с comp2, то он должен, по-хорошему, использовать следующую команду (обращение по Fully Qualified Domain Name FQDN):


ping comp2.mydomain.com

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


ping comp2

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


Процесс разрешения сетевых имён


  1. Поиск информации в hosts.txt или в кеше;
  2. Попытка найти имя через DNS;
  3. Попытка найти NetBIOS-имя в кеше;
  4. Попытка найти NetBIOS-имя через WINS-сервер;
  5. Попытка найти NetBIOS-имя путём отправки широковещательных пакетов в локальную сеть;
  6. Попытка найти NetBIOS-имя в LMHOSTS-файле.

Согласно алгоритму разрешения сетевых имен, сначала comp1 попробует найти информацию о comp2 в hosts.txt файле. Если этой информации там не окажется, то начинается процесс поиска узла через DNS. Вот только вопрос DNS-имена же находятся в каком-то домене, верно? Какое доменное имя нужно "пристыковать" к comp2 при выполнении пинга?


Вот тут в дело и вступает настройка DHCP, которая называется "имя родительсокго домена". Это как раз тот суффикс, который будет автоматически "пристыкован" к имени comp2 при выполнении DNS-разрешения имени. Так что если имя родительского домена равно "mydomain.com", то команда ping comp2 неявно преобразуется в ping comp2.mydomain.com.


Если же DNS-разрешение окажется неудачным, дальше начнутся попытки найти comp2 уже по NetBIOS-имени. Что такое WINS, и чем он отличается от Broadcast информация будет чуть дальше по тексту.


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


Настройки DNS


Теперь нас попросят указать настройки WINS-сервера. WINS (Windows Internet Name Service) сервер участвует в разрешении NetBIOS-имён в сети (прямо как DNS-сервер для DNS-имён). Вот только, в отличие от DNS, WINS-сервер не обязательно должен присутствовать в сети, чтобы разрешение NetBIOS-имён работало. Так зачем же он нужен тогда?


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


  1. При наличии большого количества NetBIOS-имён в сети широковещательный тафик может начать "зашумлять" канал;
  2. Широковещательные запросы не могут "выйти" за пределы текущей сети, поэтому узнать NetBIOS-имя из другой сети таким способом не выйдет.

Так вот, WINS-сервер позволяет решить обе этих проблемы. Этот сервер централизованно хранит NetBIOS-имена компьютеров, и обычные узлы в сети могут обращаться к нему для поиска IP-адреса интересующего их имени (как и для DNS). Такой подход, во-первых, резко уменьшает количество широковещательного трафика в сети, а, во-вторых, позволяет посылать NetBIOS-запросы в другие сети, а не только в текущую.


В нашей небольшой сети WINS-сервер нам ни к чему, поэтому просто пропускаем эту настройку и едем дальше.


WINS-серверы


В последнем шаге настройки вам предлагают сразу активировать настроенную область. Соглашаемся, и на этом заканчиваем настройку DHCP.


Активация области


Создаём нового пользователя в домене AD


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


Для этого возвращаемся на панель мониторинга, кликаем на "Средства" и затем на "Пользователи и Компьютеры Active Directory"



В открывшемся меню можно заметить созданный домен mydomain.com и его состав. Видно, что помимо пользователей в домене созданы папочки для Computers, Domain Controllers и других сущностей. Но нас сейчас интересуют пользователи, поэтому кликаем правой кнопкой мышки на папке Users и выбираем "Создать" -> "Пользователь"



После этого появляется диалоговое окно с преложением ввести данные нового пользователя. По старой доброй традиции назовём пользователя Foo Bar. Обратите внимание, что пользователь отображается лишь как "Объект" в Active Directory наравне с другими объектами.


Новый объект - Пользователь


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


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


Параметры нового пользователя


После этого останется лишь подтвердить создание нового пользователя.


Подтверждение создания нового пользователя


Ну что ж, вот, кажется, и всё! Осталось лишь проверить ввод рабочей станции в домен.


Ввод рабочей станции в домен


Переключаемся на вторую машину workstation под управлением Windows 7 и заходим в свойства системы. Сейчас видно, что рабочая станция находится в рабочей группе (не в домене). Кстати говоря, WORKGROUP это тоже NetBIOS-имя. Только в отличии от имени домена оно имеет суффикс 1E.



Щелкаем на кнопку "Изменить параметры", затем в появившемся окне ещё раз "Изменить...".


В окне изменения имени компьютера пишем, что он должен принадлежать домену mydomain.com.


Подключение к домену


Видим предупреждение о нестандартном имени компьютера (testo-ПК содержит кириллицу). Это связано с тем, что NetBIOS-имена не могут содеражать кириллицу. Но мы с вами настроили DNS-сервер (DNS настройки прилетели на рабочую станцию по DHCP), а DNS-механизм разрешения имён, как мы знаем, имеет приоритет перед NetBOIS. Так что в данном случае на работоспособность AD кириллица не влияет. Но на практике так делать не надо!


Нестандартное имя компьютера


Вводим логин-пароль от новой учетной записи FooBar и, наконец, видим заветное сообщение "Добро пожаловать в домен"


Авторизация в домене



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


Логин в AD


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


Новые свойства системы


Полное имя компьютера поменялось на testo-ПК.mydomain.com, а это значит, что мы успешно ввели рабочую станцию в домен mydomain.com.


Автоматизируем


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


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


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


Секция скрипта на языке Testo-lang


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



Всё, что Вам потребуется для создания собственного стенда с настроенной Active Directory это:


  1. Установочный iso-образ Windows Server 2016 русской версии;
  2. Установочный iso-образ Windows 7 (придётся поискать самим);
  3. Скрипты на языке Testo-lang;
  4. Установленная платформа Testo (бесплатно);
  5. Выполнить команду.

sudo testo run ./tests.testo --param ISO_DIR /path/to/your/iso/dir

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


Итоги


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


  • сайт платформы Тесто.
  • youtube-канал, где можно найти много примеров.
  • основная статья на хабре
  • статья, где я автоматизировал несколько системных тестов для Dr. Web
  • скрипты на языке Testo-lang для этого туториала
Подробнее..

Язык тестовых сценариев Testo Lang простая автоматизация сложных тестов

12.10.2020 08:12:38 | Автор: admin

Картинка для привлечения внимания


Если Вы разрабатываете более-менее сложный программный продукт, то Вам должна быть знакома ситуация, когда системные (end-to-end) тесты по тем или иным причинам автоматизировать не удаётся. На это могут быть разные причины, я приведу несколько примеров:


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

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


Если Вы ищете решение этой проблемы то прошу под кат.


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


кликнуть_мышкой_на_кнопке_с_надписью "Сохранить"напечатать_на_клавиатуре "Hello world"дождаться_надписи_на_экране "Готово"

При этом неважно, что именно вы тестируете: XAML-приложение, Qt-приложение, Electron-приложение, веб-страницу или вообще консольное приложение. Вы кликаете по экрану виртуальной машины и набираете текст на клавиатуре, а как приложение устроено внутри это Вас уже совершенно не волнует. Удобно? Конечно!


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


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


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


mouse click "Сохранить"type "Hello world"wait "Готово"

Это всё, что Вам надо написать на языке Testo Lang чтобы:


  1. Кликнуть на надпись на экране "Сохранить";
  2. Напечатать на клавиатуре "Hello world";
  3. Дождаться появления на экране строки "Готово".

Однако, мне бы не хотелось, чтобы у Вас сложилось ложное впечатление, будто бы Testo это какой-то аналог AutoIt или Sikuli. Нет, это не просто инструмент для автоматизации чего-то-там, это инструмент, заточенный именно под автоматизацию системных тестов. Testo берёт под свой контроль значительное количество задач, которые обычно выполняет тестировщик: определение того, какие тесты пора прогнать заново, подготовку виртуального стенда, составление отчёта о том, какие тесты свалились и в какой момент, и так далее.


Так, с этого момента поподробнее



Итак, мы в Вами говорим именно о системных (end-to-end) тестах. Системные тесты предполагают, что Вы тестируете программу не саму по себе в вакууме, а помещаете её в конкретное окружение и смотрите, как она с этим окружением справится. Под окружением может пониматься что угодно: и версия ОС, и наличие/отсутствие каких-то приложений/драйверов, и взаимодействие по сети, и соединение с Интернетом, и недостаток дискового пространства/оперативной памяти Да и много чего ещё.


Самый удобный способ создать такое окружение для программы это установить тестируемую программу внутрь виртуальной машины. Но виртуальную машину необходимо для начала создать и установить на неё операционную систему. Давайте посмотрим, как с этой задачей справляется платформа Testo. Мы предусмотрели специальные конструкции в языке тестовых сценариев Testo Lang, предназначенные для создания элементов виртуальной инфраструктуры Ваших стендов. Например, следующий сниппет объявляет "пустую" виртуалку:


machine my_super_vm {    ram: 2Gb    cpus: 2    iso: "ubuntu_server.iso"    disk main: {        size: 5Gb    }}

Эта конструкция создаёт виртуальную машину с 2Гб оперативной памяти, 2 ядрами процессора и 5Гб дискового пространства. При запуске такой виртуалки, начнётся процесс установки операционной системы из образа ubuntu_server.iso.


Это может быть несколько непривычно, но мы рассматриваем процесс установки операционной системы как ещё один тест, наравне с теми тестами, в которых проверяется собственно работоспособность Вашей программы. Это утверждение обретёт бОльший смысл, если мы на секунду представим, что мы разрабатываем не программу, а операционную систему. Может быть это какая-то специализированная система, например Alt Linux, а может быть мы разрабатываем игрушечную операционную систему just for fun. В любом случае, тестировать её как-то надо, а платформа Testo подходит для этой цели как нельзя лучше, потому что для неё нет никакой разницы, что мы тестируем: операционную систему или программу.


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


test my_super_test {    my_super_vm {        start        wait "Language"        press Enter        wait "Install Ubuntu Server"        press Enter        wait "Choose the language"        press Enter        # И так далее        ...    }}

Здесь мы видим новую конструкцию языка, которая объявляет тест my_super_test. В этом тесте учавствует всего одна виртуалка my_super_vm. Тест начинается с включения виртуальной машины. Затем мы дожидаемся, когда на экране появится надпись "Language" и нажимаем клавишу Enter. Собственно, весь тест будет заключаться в последовательности таких действий: ждём наступления события, затем печатаем что-то на клавиатуре.


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


machine my_super_vm {    ram: 2Gb    cpus: 2    disk main: {        source: "prepared_vm.qcow2"    }}

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


Подготовка виртуалки и установка ОС это конечно всё очень хорошо, но тесты, тесты-то на мою программу где? Хорошо, давайте представим, что мы хотим протестировать инсталлятор нашей супер-программы. Представим также, что мы уже вручную подготовили виртуальную машину с Windows 10 на борту. Для простоты примера предположим, что инсталлятор нашей супер-программы уже скопирован на рабочий стол этой виртуалки. Тогда автотест на установку программы будет выглядеть следующим образом:


machine my_win_vm {    ram: 2Gb    cpus: 2    disk main: {        source: "my_windows_10.qcow2"    }}test my_installer_test {    my_win_vm {        # Запустим виртуальную машину        start        # Дождёмся появления рабочего стола        wait "Корзина"        mouse dclick "my_super_installator"        wait "Добро пожаловать"        mouse click "Далее"        wait "Выберите путь установки"        mouse click "Продолжить"        wait "Успешно" timeout 10m        mouse click "Закрыть"    }}

Правда, просто? А мы только разогреваемся ...


Что за wait такой и как он работает?



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


  1. Воздействие на виртуальную машину (mouse move/click, type, press, start/stop, plug flash и много чего ещё);
  2. Анализ происходящего на экране (wait).

Так вот, это действие wait и является основным видом визуальных проверок в языке Testo Lang. Действие wait дожидается появления на экране определённых объектов и событий в течение заданного таймаута (по умолчанию одна минута). И если событие не наступило генерируется ошибка (прямо как человек, который ждёт надписи "Успешно" пока у него не кончится, наконец, терпение).


Если мы говорим про поиск текста на экране виртуалки (то бишь на скриншоте), то обычно для этих целей используют какую-нибудь OCR (Optical Character Recognition) систему (например, Tesseract). Однако, это не совсем верный подход. Дело в том, что OCR-системы строятся исходя из двух постулатов:


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

В случае автотестов мы имеем совершенно иную ситуацию:


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

В платформе Testo для реализации этой задачи мы использовали нейросети. На вход нейросетям мы подаём сразу две вещи: скриншот экрана виртуалки и искомый текст. От нейросети требуется сказать только, есть ли что-то похожее на изображении или нет. Например, если мы ищём слово "Hopa", то нам вполне подойдёт как слово, написанное кириллицей, так и слово, написанное латиницей, потому что выглядят они совершенно одинаково.


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


На одном wait далеко не уедешь


Писать длинные тесты с помощью wait + click довольно муторно, особенно, если нет автоматического рекодера тестов. Тесты на основе визуальных проверок это скорее крайний вариант, когда нет другой возможности протестировать приложение или настроить тестовое окружение. Обычно всё же существует возможность выполнить какие-то проверки путём запуска процессов на гостевой системе, например с помощью bash-скриптов.


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


test my_super_test  {        my_super_vm {            exec bash "echo Hello world from bash"            exec python """                print("Hello from python")            """        }}

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


На самом деле довольно многие тестовые сценарии выглядят следующим образом. Сначала с помощью wait + click на гостевую систему устанавливаются дополнения, а затем проверки сводятся к запуску процессов на виртуалке. Но при этом ничто не мешает в любой момент вернуться к визуальным проверкам. Тут всё зависит от Вас как Вам удобнее, так и делайте.


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


test copy_demo {        my_super_vm {            copyto "/file/on/host" "/file/on/guest"            copyfrom "/file/on/guest" "/file/on/host"        }}

Да зачем же придумывать целый язык?



Мне кажется, многие читатели сейчас думают: "Ребят, серьёзно? Целый язык? Зачем? Ну напишите Вы библиотеку для питона или для чего ещё. Все разумные люди так делают".


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


  1. Мы бы хотели, чтобы у нашего языка был минимальный порог вхождения для людей, не знакомых с программированием;
  2. Мы бы хотели избавиться от лишней мишуры, присущей языкам общего назначения, оставив только то, что требуется для автотестов;
  3. Некоторые фичи, которые мы запилили в Testo-lang, просто так не воткнешь в библиотеку для Python!

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


Допустим, у Вас есть такой тест:


test run_installator {        my_super_vm {            copyto "/path/on/host.msi" "C:\\Users\\Testo\\Desktop\\setup.msi"            mouse dclick "setup"            ...        }}

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


  1. Сам тест не менялся;
  2. Сборка Вашего инсталлятора не менялась.

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


Ух, круто. А что ещё умеет Testo?



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


# Сеть для соединения двух виртуальных машинnetwork net1 {    mode: "internal"}# Сеть для доступа в Итнернетnetwork internet {    mode: "nat"}machine my_super_client {    ...    nic server_side: {        attached_to: "net1"    }    nic internet: {        attached_to: "internet"    }}machine my_super_server {    ...    nic client_side: {        attached_to: "net1"    }}

Хотите добавить в стенд флешку? Нет проблем, пара строк и у Вас есть виртуальная флешка (можно даже скопировать на неё что-нибудь с хоста)


flash my_super_flash {    fs: ntfs    size: 2048Mb    #Папка с хоста, которую надо скопировать    folder: "/path/on/host"}

Хотите написать реально много тестов? Нет проблем, организуйте их в иерархию! Давайте для большей конкретики рассмотрим такой набор тестов:


  1. Установка ОС;
  2. Установка гостевых дополнений;
  3. Копирование и установка тестируемой программы на виртуалку;
  4. Тестирование фичи 1;
  5. Тестирование фичи 2.

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



На языке Testo Lang это будет выглядеть не сильно сложнее, чем на рисунке:


test install_os {    ...}test install_guest_additions: install_os {    ...}test install_app: install_guest_additions {    ...}test test_feature_1: install_app {    ...}test test_feature_2: install_app {    ...}

При первом запуске тестов, конечно, все тесты прогонятся от начала до конца. Допустим, при этом запуске не произошло никаких ошибок. В этом случае Testo запомнит, что все эти тесты завершились успешно и, соответственно, закешированы:



Если прямо сейчас заново запустить тесты, то они вовсе не прогонятся, так как ничего с момента последнего запуска и не поменялось. Но как только Вы соберёте новый билд своей тестирумой программы, Testo это отловит и инвалидирует кеш 3, 4 и 5 тестов:



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


Простой, но вполне реальный пример


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


Это приложение написано на С++ с использованием библиотеки ImGui, у него нет никаких хуков для автоматизации тестирования, но мы всё равно очень хотим на каждую сборку проверять, что оно успешно запускается на Windows 10 и высвечивает нам окошко с надписью "MySuperApp is working!".


Что ж, для начала нам понадобится виртуалка. Для этого примера мы создадим виртуалку на основе уже существующей, вручную подготовленной виртуалки, на которую мы заранее установили Windows 10:


machine my_vm {    cpus: 2    ram: 4Gb    disk main: {        source: "${QEMU_DISK_DIR}/win10.qcow2"    }}

А как нам скопировать на виртуалку сборку с нашей программой? Давайте для этого воспользуемся флешкой!


flash my_super_flash {    fs: "ntfs"    size: 16Mb    folder: "./dist"}

Сборку с нашей программой поместим в каталог "./dist", а Testo позаботится о том, чтобы она оказалась на флешке.


Ну а теперь пишем сам тест!


test launch_my_simple_app {    my_vm {        ...    }}

Так, стоп, а с чего начать? Да всё просто просто записывайте все действия, которые Вы бы проделывали вручную! Для начала виртуалку надо включить.


start

Окей, а дальше? Дальше надо дождаться появления рабочего стола, конечно же:


wait "Recycle Bin" timeout 10m


Вставляем флешку


plug flash my_super_flash


Кликаем по надписи "USB Drive (E:)"


mouse click "USB Drive (E:)"


Открываем файловый менеджер:


mouse click "Open folder to view files"


Дважды кликаем по нашему приложению:


mouse dclick "MySuperApp"


Как же нам понять, что наше приложение успешно запустилось? Ну, мы знаем, что наше приложение при запуске должно высвечивать надпись "hello world". Поэтому если такая надпись появилась на экране, то это с большой долей вероятности свидетельствует о том, что всё хорошо. Это и будет наша основная проверка в тесте:


wait "hello world"

В конце теста не забываем вытащить флешку, и в сумме у нас получается такой скрипт:


test launch_my_simple_app {    my_vm {        start        wait "Recycle Bin" timeout 10m        plug flash my_super_flash        mouse click "USB Drive (E:)"        mouse click "Open folder to view files"        mouse dclick "MySuperApp"        wait "hello world"        unplug flash my_super_flash    }}

Вот собственно и всё, наш первый тест готов. А как его запустить? Да тоже ничего сложного, главное указать путь QEMU_DISK_DIR:


sudo testo run my_script.testo --param QEMU_DISK_DIR /var/lib/libvirt/qemu/images

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



А теперь, напоследок, давайте попробуем симитировать ситуацию, когда в нашу программу закралась ошибка.


Например, по какому-то нелепому стечению обстоятельств, мы собрали MySuperApp с динамической линковкой стандартной библиотеки С++ вместо статической. Если программа собрана таким образом, то для её работы на гостевой системе должен быть установлен пакет Microsoft Visual C++ Redistributable. А мы разрабатываем standalone-приложение, которое не должно иметь никаких зависимостей. У разработчика на хостовой системе Microsoft Visual C++ Redistributable конечно же установлен, поэтому такую ошибку легко не заметить.


Итак, мы подкладываем в каталог ./dist новую сборку нашего приложения и запускаем тесты заново. Вот что мы увидим:



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



Тест свалился, ошибка выловлена!


Итоги


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


Скачать Testo абсолютно бесплатно без регистрации и СМС, а также ознакомиться с документацией можно у нас на сайте https://testo-lang.ru


Посмотреть больше примеров можно на youtube-канале https://www.youtube.com/channel/UC4voSBtFRjRE4V1gzMZoZuA

Подробнее..

Я автоматизировал тестирование Dr. Web. А сможете ли вы?

15.10.2020 14:05:55 | Автор: admin


Я никогда не пользовался Dr. Web. Я понятия не имею, как он устроен. Но это не помешало мне написать для него ряд автотестов (и лишь лень не позволила мне написать ещё сотню других):


  1. Тест на установку Dr. Web;
  2. Тест на ограничение доступа к съемным устройствам (флешкам);
  3. Тест на разграничение доступа к каталогу между программами;
  4. Тест на разграничение доступа к каталогу между пользователями системы (родительский контроль).

Такие и многие другие тесты можно клепать как горячие пирожки, и не только применительно к Dr. Web, и не только применительно к антивирусам. В этой статье я расскажу, как это сделать.


Подготовка


Для тестов нам понадобится виртуалка с Windows на борту. Я подготовил её вручную, выполнив на ней следующие манипуляции:


  1. Собственно, установил Windows 10 Pro x64;
  2. Во время установки создал основного пользователя "testo" в паролем "1111";
  3. Включил автологин для этого пользователя;

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



Здесь предполагается, что /path/to/win10.qcow2 это путь к диску той виртуалки, которую я подготовил вручную. На этом подготовка заканчивается, и начинается экшен.


Тест 1 Устанавливаем Dr. Web!


Для начала надо решить вопрос с переносом дистрибутива Dr. Web на виртуальную машину. Сделать это можно (например) с помощью флешки:



Всё, что нам надо сделать это положить установщик Dr. Web в папочку ${DR_WEB_DIR} (точное значение этого параметра мы будем задавать при запуске testo). А Testo само позаботится о том, чтобы этот инсталлятор оказался на флешке.


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



Скриншот на момент окончания сценария


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



Скриншот, на котором ещё происходит копирование файла


Всё, копирование успешно завершено! Теперь можно закрыть окно с флешкой и вытащить её:



Скриншот после закрытия проводника


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



Скриншот на момент окончания установки


Завершаем наш тест перезагрузкой. И в конце не забудем проверить, что после перезагрузки на рабочем столе появилась иконка с Dr. Web:



Скриншот после перезагрузки


Отличная работа! Мы автоматизировали установку антивируса Dr. Web! Давайте немного передохнём и посмотрим, как это выглядит в динамике:



Переходим к тестированию фич.


Тест 2 Ограничение доступа к флешкам


Первая фича по списку ограничение доступа к флешкам. Для этого спланируем довольно прямолинейный тест:


  1. Попробуем вставить флешку и создать там пустой файл должно получиться. Вытащим флешку;
  2. Включим блокировку съемных устройств в Dr. Web Security Center;
  3. Ещё раз вставим флешку и попробуем удалить созданный файл. Действие должно быть заблокировано.

Создадим себе новую флешку, вставим её в Windows и попробуем создать папку. Что может быть проще?



Скриншот на момент окончания сценария


Создаём новый текстовый файл через контекстное меню проводника:



Скриншот после переименования файла


Отключаем флешку, делаем это безопасно:



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



Скриншот с окном Security Center


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



Этот макрос нам ещё пригодится.


Первое, что мы сделаем, открыв центр безопасности Dr. Web включим возможность вносить изменения:



Теперь немного покликаем по менюшкам и зайдем в меню "Configure device access rules". В этом меню поставим галочку в пункте "Block removable media".



Скриншот с окном Devices and Personal Data


Попробуем открыть флешку теперь:



Скриншот с сообщение об ошибке


Вот так потихоньку-полегоньку мы и написали первый тест с тестированием вполне осязаемой фичи в Dr. Web. Настало время передохнуть и помедитировать, глядя на результаты наших трудов:




Тест 3 Разграничение доступа к каталогу между программами


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


  1. Установим на ОС стороннюю программу, для которой чуть позже добавим исключение при доступе к защищаемой папке. Сегодня сторонняя программа дня файловый менеджер FreeCommander;
  2. Создаем папку с файликом, которую будем защищать всеми силами;
  3. Откроем центр безопасности Dr. Web и включим там защиту этой папки;
  4. Настроим исключение для FreeCommander;
  5. Попробуем удалить файл из защищаемой папки обычным способом (через проводник Windows). Не должно получиться;
  6. Попробуем удалить файлик через FreeCommander. Должно получиться.

Ух, много работы. Быстрее начнём быстрее закончим.


Пункт первый, установка FreeCommander не сильно отличается от установки Dr.Web. Обычная рутина: вставили флешку, запустили инсталлятор и так далее. Пропустим это и перейдём сразу к интересному.


Если всё-таки интересно, как установить FreeCommander

Начнём с простого: создадим флешку, в которую поместим дистрибутив FreeCommander, а затем в тесте вставим флешку в ОС и откроем её:



Далее несколько не кликов чтобы запустить установку:



Установка не очень интересная, просто кликаем везде "Далее", а в конце не забываем отключить галочки с просмотром ReadMe и немедленным запуском FreeCommander



Заканчиваем тест, закрывая все окна и вытаскивая флешку



Готово!


Для работы с Dr. Web создадим новый тест dr_web_restrict_program, который будет полагаться на результат работы предыдущего теста win10_install_freecommander.


Начнём тест с создания папки Protected на рабочем столе:



Скриншот после создания папки


Заходим в папку Protected и создаём там файл my_file.txt, который будет играть роль защищаемого файла:



Ох, надо было бы тоже оформить это в виде макроса, ну да ладно ...


Скриншот после создания файла


Отлично, теперь надо включить защиту папки. Идём знакомой дорожкой и открываем Dr. Web, не забываем включить режим изменений. После чего переходим в меню "Data Loss Prevention".



Скриншот с окном Data Loss Prevention


Немного поработаем мышкой и добавим нашу папку Protected в список защищаемых:



Скриншот с мастером добавления защищаемой папки


Ну а теперь надо настроить исключение по доступу к папке для FreeCommander. Ещё немного работы мышкой:



Скриншот с добавленной программой-исключением


Теперь аккуратно закрываем все окна и пробуем удалить файл "my_file.txt" стандартным способом:



Скриншот с сообщением от Dr.Web


Но ничего не получилось значит Dr. Web действительно отработал! Половина теста позади, но нам ещё надо проверить, что будет работать исключение для FreeCommander. Для этого открываем FreeCommander и переходим в папку Protected:



Скриншот с окном FreeCommander


Ну и попробуем удалить файл my_file.txt:



Скриншот после удаления файла


Исключение для FreeCommander работает!


Отличная работа! Большой и сложный тесткейс и всё автоматизировано. Немного расслабона:




Тест 4 Родительский контроль


Этот последний на сегодня тесткейс мы построим следующим образом:


  1. Создадим нового пользователя MySuperUser;
  2. Залогинимся под этим пользователем;
  3. Создадим файл my_file.txt от имени нового пользователя;
  4. Откроем центр безопасности Dr. Web и включим родительский контроль для этого файла;
  5. В родительском контроле ограничим права пользователя MySuperUser на им же созданный файл;
  6. Попробуем прочитать и удалить файл my_file.txt от имени MySuperUser и посмотрим на результат.

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



Заключение


Исходники всех тестов Вы можете посмотреть здесь


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


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

Подробнее..

Гена против Сандро история автоматизации одной сетевой партии в Героях 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

Подробнее..

Категории

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

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