Общие сведения

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

Обучающие материалы по Testo для Hyper-V

Часть 1. Самый первый тестЧасть 2. Устанавливаем Ubuntu ServerЧасть 3. Доступ в Интернет из виртуальной машиныЧасть 4. Гостевые дополненияЧасть 5. ПараметрыЧасть 6. КешированиеЧасть 7. Связываем две машины по сетиЧасть 8. ФлешкиЧасть 9. МакросыЧасть 10. Конструкция ifЧасть 11. No snapshotsЧасть 12. Управление мышкойЧасть 13. Импортирование жёстких дисковЧасть 14. JS-селекторыЧасть 15. Циклы

Обучающие материалы по Testo для QEMU

Часть 1. Самый первый тестЧасть 2. Устанавливаем Ubuntu ServerЧасть 3. Гостевые дополненияЧасть 4. ПараметрыЧасть 5. КешированиеЧасть 6. Доступ в Интернет из виртуальной машиныЧасть 7. Связываем две машины по сетиЧасть 8. ФлешкиЧасть 9. МакросыЧасть 10. Конструкция ifЧасть 11. No snapshotsЧасть 12. Управление мышкойЧасть 13. Импортирование жёстких дисковЧасть 14. JS-селекторыЧасть 15. ЦиклыЧасть 16. Макросы с объявлениями

Спецификация языка

Общая структура скриптовых файловБазовые конструкции языкаOбъявление виртуальной машиныОбъявление виртуального флеш-накопителяОбъявление виртуальной сетиПараметрыОбъявление тестовМакросыДействия с виртуальными машинамиДействия с мышкойПоиск изображений на экранеДействия с виртуальными флеш-накопителямиУсловияЦиклыСписок идентификаторов клавиш

Запросы на языке Javascript

Общая концепция построения JS-селекторовВстроенные глобальные функции JavascriptИсключенияКласс TextTensorКласс ImgTensorКласс Point

Часть 9. Макросы

С чем Вы познакомитесь

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

Начальные условия

  1. Платформа Testo установлена.
  2. Гипервизор Hyper-V установлен.
  3. Имеется установочный образ Ubuntu server 16.04 с расположением C:\iso\ubuntu_server.iso. Местоположение и название установочного файла может быть другим, в этом случае нужно будет соответствующим образом поправить параметр ISO_DIR, передаваемый через командную строку во время запуска тестов.
  4. Имеется образ с гостевыми дополнениями Testo для Hyper-V в одной папке с установочным образом Ubuntu.
  5. Хост имеет доступ в Интернет.
  6. (Рекомендовано) Настроена подсветка синтаксиса Testo-lang в Sublime Text 3.
  7. (Рекомендовано) Проделаны шаги из седьмой части.

Вступление

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

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

В языке Testo-lang макрос означает поименованный набор действий, команд или объявлений. При этом вызов макроса также является действием, командой или объявлением (в зависимости от типа макроса). Благодаря макросам можно объединять похожие участки тестовых сценариев и делать их более читаемыми и компактными. Макросы могут принимать аргументы (в том числе аргументы по умолчанию), к которым можно обращаться внутри макроса как к обычным параметрам.

И, конечно, в языке Testo-lang предусмотрена возможность разносить участки кода по разным файлам. Делается это с помощью директивы include, и мы сегодня с ней тоже познакомимся.

С чего начать?

В нашем тестовом сценарии мы делаем много одинаковых подготовительных действий для машин client и server: устанавливаем ОС Ubuntu Server, подгатавливаем её, устанавливаем гостевые дополнения, настраиваем сетевой интерфейс. Честно говоря, на текущий момент большая часть тестов это фактически копи-паста. Однако мы можем сделать наши подготовительные тесты гораздо компактнее, если прибегнем к помощи макросов.

Давайте начнем с установки ОС. Сейчас тест на установку ОС Ubuntu выглядит так:

test server_install_ubuntu {
    server {
        start
        # The actions can be separated with a newline
        # or a semicolon
        wait "Install Ubuntu Server"; press Enter;
        wait "Choose the language"; press Enter
        wait "Select your location"; press Enter
        wait "Detect keyboard layout?"; press Enter
        wait "Country of origin for the keyboard"; press Enter
        wait "Keyboard layout"; press Enter
        #wait "No network interfaces detected" timeout 5m; press Enter
        wait "Primary network interface"; press Enter
        wait "Hostname:" timeout 5m; press Backspace*36; type "${server_hostname}"; press Enter
        wait "Full name for the new user"; type "${server_login}"; press Enter
        wait "Username for your account"; press Enter
        wait "Choose a password for the new user"; type "${default_password}"; press Enter
        wait "Re-enter password to verify"; type "${default_password}"; press Enter
        wait "Use weak password?"; press Left, Enter
        wait "Encrypt your home directory?"; press Enter

        wait "Is this time zone correct?" timeout 2m; press Enter
        wait "Partitioning method"; press Enter
        wait "Select disk to partition"; press Enter
        wait "Write the changes to disks and configure LVM?"; press Left, Enter
        wait "Amount of volume group to use for guided partitioning"; press Enter
        wait "Force UEFI installation?"; press Left, Enter
        wait "Write the changes to disks?"; press Left, Enter
        wait "HTTP proxy information" timeout 3m; press Enter
        wait "How do you want to manage upgrades" timeout 6m; press Enter
        wait "Choose software to install"; press Enter
        wait "Installation complete" timeout 30m;

        unplug dvd; press Enter
        wait "login:" timeout 2m; type "${server_login}"; press Enter
        wait "Password:"; type "${default_password}"; press Enter
        wait "Welcome to Ubuntu"
    }
}

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

Давайте объявим макрос install_ubuntu (на том же уровне, что и объявление тестов, сущностей и параметров).

macro install_ubuntu(hostname, login, password) {
    start
    # The actions can be separated with a newline
    # or a semicolon
    wait "Install Ubuntu Server"; press Enter;
    wait "Choose the language"; press Enter
    wait "Select your location"; press Enter
    wait "Detect keyboard layout?"; press Enter
    wait "Country of origin for the keyboard"; press Enter
    wait "Keyboard layout"; press Enter
    #wait "No network interfaces detected" timeout 5m; press Enter
    wait "Primary network interface"; press Enter
    wait "Hostname:" timeout 5m; press Backspace*36; type "${hostname}"; press Enter
    wait "Full name for the new user"; type "${login}"; press Enter
    wait "Username for your account"; press Enter
    wait "Choose a password for the new user"; type "${password}"; press Enter
    wait "Re-enter password to verify"; type "${password}"; press Enter
    wait "Use weak password?"; press Left, Enter
    wait "Encrypt your home directory?"; press Enter

    wait "Is this time zone correct?" timeout 2m; press Enter
    wait "Partitioning method"; press Enter
    wait "Select disk to partition"; press Enter
    wait "Write the changes to disks and configure LVM?"; press Left, Enter
    wait "Amount of volume group to use for guided partitioning"; press Enter
    wait "Force UEFI installation?"; press Left, Enter
    wait "Write the changes to disks?"; press Left, Enter
    wait "HTTP proxy information" timeout 3m; press Enter
    wait "How do you want to manage upgrades" timeout 6m; press Enter
    wait "Choose software to install"; press Enter
    wait "Installation complete" timeout 30m;

    unplug dvd; press Enter
    wait "login:" timeout 2m; type "${login}"; press Enter
    wait "Password:"; type "${password}"; press Enter
    wait "Welcome to Ubuntu"
}

В тело макроса мы положим все необходимые для установки Ubuntu Server действия. Вы можете заметить, что внутри макроса не фигурирует никаких виртуальных машин. В сущности, наш новый макрос представляет собой все действия по установке ОС, но без "прикрепления" к конкретной виртуальной машине.

У нашего макроса три аргумента: hostname, login и password. Внутри макроса происходит обращение к значениям этих аргументов в действиях type: type "${hostname}", type "${login}" и type "${password}". Конкретное значение этих аргументов, конечно же, будет отличаться в разных вызовах макроса.

Давайте теперь попробуем преобразовать наши тесты server_install_ubuntu и client_install_ubuntu:

test server_install_ubuntu {
    server install_ubuntu("${server_hostname}", "${server_login}", "${default_password}")
}

test client_install_ubuntu {
    client install_ubuntu("${client_hostname}", "${client_login}", "${default_password}")
}

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

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

C:\Users\Testo> testo run macros.testo --stop_on_fail --param ISO_DIR C:\iso
UP-TO-DATE TESTS:
server_install_ubuntu
server_prepare
server_install_guest_additions
server_setup_nic
client_install_ubuntu
client_prepare
client_install_guest_additions
client_setup_nic
test_ping
PROCESSED TOTAL 9 TESTS IN 0h:0m:0s
UP-TO-DATE: 9
RUN SUCCESSFULLY: 0
FAILED: 0
C:\Users\Testo>

Что же мы наблюдаем? Мы видим, что все наши тесты остались закешированными, несмотря на то, что мы очень сильно (как кажется) изменили текст самых базовых тестов. Однако платформа Testo при рассчёте контрольных сумм тестовых сценариев при встрече с вызовом макроса "раскрывает" его в полноценный набор действий и принимает во внимание только содержимое макроса. Т.к. содержимое тестов реально не изменилось (оно лишь перекочевало в макрос), то и контрольная сумма остаётся целостной, а значит кеш остаётся действительным.

Наши тесты стали гораздо компактнее, но, как это ни странно, их можно сделать ещё компактнее. Для этого давайте заметим, что в обоих тестах мы передаём в качестве пароля значение ${default_password} - нас такое значение абсолютно устраивает в обоих виртуальных машинах. В этом случае мы можем сделать аргумент password в макросе install_ubuntu аргументом по умолчанию.

param default_password "1111"
macro install_ubuntu(hostname, login, password = "${default_password}") {
    start
    wait "English"
    press Enter
    ...

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

В самих тестах теперь можно не указывать аргумент password при вызове макроса:

test server_install_ubuntu {
    server install_ubuntu("${server_hostname}", "${server_login}")
}

test client_install_ubuntu {
    client install_ubuntu("${client_hostname}", "${client_login}")
}

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

C:\Users\Testo> testo run macros.testo --stop_on_fail --param ISO_DIR C:\iso
UP-TO-DATE TESTS:
server_install_ubuntu
server_prepare
server_install_guest_additions
server_setup_nic
client_install_ubuntu
client_prepare
client_install_guest_additions
client_setup_nic
test_ping
PROCESSED TOTAL 9 TESTS IN 0h:0m:0s
UP-TO-DATE: 9
RUN SUCCESSFULLY: 0
FAILED: 0
C:\Users\Testo>

Подготовка ОС

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

macro prepare_ubuntu(hostname, login, password = "${default_password}") {
    # Enter sudo mode
    type "sudo su"; press Enter
    wait "password for ${login}"; type "${password}"; press Enter
    wait "root@${hostname}"

    # Reset the eth0 NIC to prevent any issues with it after the rollback
    type "dhclient -r eth0 && dhclient eth0 && echo Result is $?"; press Enter

    # Check that apt is OK
    type "clear && apt update && echo Result is $?"; press Enter
    wait "Result is 0"

    # Install linux-azure package
    type "clear && apt install -y linux-azure && echo Result is $?"; press Enter
    wait "Result is 0" timeout 15m      

    # Reboot and login
    type "reboot"; press Enter

    wait "login:" timeout 2m; type "${login}"; press Enter
    wait "Password:"; type "${password}"; press Enter
    wait "Welcome to Ubuntu"

    # Enter sudo once more
    type "sudo su"; press Enter;
    wait "password for ${login}"; type "${password}"; press Enter
    wait "root@${hostname}"

    # Load the hv_sock module
    type "clear && modprobe hv_sock && echo Result is $?"; press Enter;
    wait "Result is 0"

    type "clear && lsmod | grep hv"; press Enter
    wait "hv_sock"
}

Теперь тесты client_prepare и server_prepare тоже выглядят лаконично и компактно:

test client_prepare: client_install_ubuntu {
    client prepare_ubuntu("${client_hostname}", "${client_login}")
}

test server_prepare: server_install_ubuntu {
    server prepare_ubuntu("${server_hostname}", "${server_login}")
}

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

type "sudo su"; press Enter;
wait "password for ${login}"; type "${password}"; press Enter
wait "root@${hostname}"

Этот блок можно оформить в виде отдельного макроса:

macro enter_sudo(hostname, login, password) {
    type "sudo su"; press Enter;
    wait "password for ${login}"; type "${password}"; press Enter
    wait "root@${hostname}"
}

Теперь макрос prepare_ubuntu можно немного сократить:

macro prepare_ubuntu(hostname, login, password = "${default_password}") {
    # Enter sudo mode
    enter_sudo("${hostname}", "${login}", "${password}")

    # Reset the eth0 NIC to prevent any issues with it after the rollback
    type "dhclient -r eth0 && dhclient eth0 && echo Result is $?"; press Enter

    # Check that apt is OK
    type "clear && apt update && echo Result is $?"; press Enter
    wait "Result is 0"

    # Install linux-azure package
    type "clear && apt install -y linux-azure && echo Result is $?"; press Enter
    wait "Result is 0" timeout 15m      

    # Reboot and login
    type "reboot"; press Enter

    wait "login:" timeout 2m; type "${login}"; press Enter
    wait "Password:"; type "${password}"; press Enter
    wait "Welcome to Ubuntu"

    # Enter sudo once more
    enter_sudo("${hostname}", "${login}", "${password}")

    # Load the hv_sock module
    type "clear && modprobe hv_sock && echo Result is $?"; press Enter;
    wait "Result is 0"

    type "clear && lsmod | grep hv"; press Enter
    wait "hv_sock"
}

Обратите внимание, что при вызове макроса enter_sudo его аргументы вычисляются на основе аргументов, которые передаются в prepare_ubuntu. Также стоит помнить, что внутри макроса prepare_ubuntu можно вызывать только макросы с действиями (не с командами и не с объявлениями). Макрос enter_sudo содержит как раз действия, поэтому всё ОК.

Макрос enter_sudo является очень кратким и базовым макросом. Он может пригодиться и в будущем: в тестах или других макросах.

Проверяем, что тесты до сих пор закешированы:

C:\Users\Testo> testo run macros.testo --stop_on_fail --param ISO_DIR C:\iso
UP-TO-DATE TESTS:
server_install_ubuntu
server_prepare
server_install_guest_additions
server_setup_nic
client_install_ubuntu
client_prepare
client_install_guest_additions
client_setup_nic
test_ping
PROCESSED TOTAL 9 TESTS IN 0h:0m:0s
UP-TO-DATE: 9
RUN SUCCESSFULLY: 0
FAILED: 0
C:\Users\Testo>

Установка гостевых дополнений

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

param guest_additions_pkg "testo-guest-additions*"

macro install_guest_additions() {
    plug dvd "${ISO_DIR}\\testo-guest-additions-hyperv.iso"

    type "mount /dev/cdrom /media"; press Enter
    wait "mounting read-only"
    type "clear && dpkg -i /media/${guest_additions_pkg} && echo Result is $?"; press Enter;
    wait "Result is 0"
    type "clear && umount /media && echo Result is $?"; press Enter;
    wait "Result is 0"
    sleep 2s
    unplug dvd
}

Обратите внимание, что внутри этого макроса мы обращаемся к параметрам ISO_DIR и guest_additions_pkg, несмотря на то, что они не входят в список аргументов. Такая схема успешно работает благодаря алгоритму разрешения значений параметров при обращении к ним с помощью оператора ${}:

  1. Если поиск значения происходит внутри макроса, то сначала проверяется, входит ли искомое значение в список аргументов макроса. Если входит, то возвращается значение аргумента, и поиск значения на этом прекращается.
  2. Происходит поиск значений среди глобально объявленных параметров (в том числе параметров, объявленных с помощью аргумента --param). Если нужное значение найдено, то возвращается найденное значение, и поиск на этом завершается. В нашем макросе алгоритм завершит работу на этом шаге при обращении к ${ISO_DIR} и ${guest_additions_pkg}.
  3. Если на предыдущих шагах ничего не было найдено, то генерируется ошибка.

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

test server_install_guest_additions: server_install_ubuntu {
    server install_guest_additions()
}

test client_install_guest_additions: client_install_ubuntu {
    client install_guest_additions()
}

И вновь, если запустить тесты, мы увидим, что всё закешировано:

C:\Users\Testo> testo run macros.testo --stop_on_fail --param ISO_DIR C:\iso
UP-TO-DATE TESTS:
server_install_ubuntu
server_prepare
server_install_guest_additions
server_setup_nic
client_install_ubuntu
client_prepare
client_install_guest_additions
client_setup_nic
test_ping
PROCESSED TOTAL 9 TESTS IN 0h:0m:0s
UP-TO-DATE: 9
RUN SUCCESSFULLY: 0
FAILED: 0
C:\Users\Testo>

Макрос для выполнения баш-команд

Последнее, что мы улучшим с помощью макросов в этом гайде - это выполнение баш-команд. Вы могли заметить, что до момента установки гостевых дополнений, мы вынуждены были выполнять баш-команды с помощью связки действий clean; bash_command && echo "Result is $?"; wait "Result is 0".

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

macro exec_bash_command(command) {
    type "clear && ${command} && echo Result is $?"; press Enter
    wait "Result is 0"
}

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

macro prepare_ubuntu(hostname, login, password = "${default_password}") {
    # Enter sudo mode
    enter_sudo("${hostname}", "${login}", "${password}")

    # Reset the eth0 NIC to prevent any issues with it after the rollback
    exec_bash_command("dhclient -r eth0 && dhclient eth0")
    # Check that apt is OK
    exec_bash_command("apt update")
    # Install linux-azure package
    exec_bash_command("apt install -y linux-azure")

    # Reboot and login
    type "reboot"; press Enter

    wait "login:" timeout 2m; type "${login}"; press Enter
    wait "Password:"; type "${password}"; press Enter
    wait "Welcome to Ubuntu"

    # Enter sudo once more
    enter_sudo("${hostname}", "${login}", "${password}")

    # Load the hv_sock module
    exec_bash_command("modprobe hv_sock")

    type "clear && lsmod | grep hv"; press Enter
    wait "hv_sock"
}

Стало лаконичнее, не правда ли? Впрочем, пока что не всё так хорошо - вызов макроса exec_bash_command("apt install -y linux-azure") работать не будет. Причина в том, что внутри макроса действие wait "Result is 0" будет ждать появления надписи в течение одной минуты. При этом выполнение команды apt install -y linux-azure требует гораздо больше времени. Что же в таком случае делать?

Нам поможет возможность языка Testo-lang, которая позволяет использовать строки вместо некоторых конструкций языка. В частности, эта возможность Testo-lang позволяет указывать таймаут для действия wait качестве строки, а не в виде спецификатора:

wait "Hello world" timeout 5m
wait "Hello world" timeout "5m" #The same thing

"И как же это нам поможет?", - можете спросить вы. А помощь заключается в том, что внутри строк вы можете обращаться к параметрам!

wait "Hello world" timeout "${time_to_wait}"

Теперь мы можем доработать макрос exec_bash_command:

macro exec_bash_command(command, time_to_wait = "1m") {
    type "clear && ${command} && echo Result is $?"; press Enter
    wait "Result is 0" timeout "${time_to_wait}"
}

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

Поэтому итоговый вид макроса prepare_ubuntu будет таким:

macro prepare_ubuntu(hostname, login, password = "${default_password}") {
    # Enter sudo mode
    enter_sudo("${hostname}", "${login}", "${password}")

    # Reset the eth0 NIC to prevent any issues with it after the rollback
    exec_bash_command("dhclient -r eth0 && dhclient eth0")
    # Check that apt is OK
    exec_bash_command("apt update")
    # Install linux-azure package
    exec_bash_command("apt install -y linux-azure", "15m")

    # Reboot and login
    type "reboot"; press Enter

    wait "login:" timeout 2m; type "${login}"; press Enter
    wait "Password:"; type "${password}"; press Enter
    wait "Welcome to Ubuntu"

    # Enter sudo once more
    enter_sudo("${hostname}", "${login}", "${password}")

    # Load the hv_sock module
    exec_bash_command("modprobe hv_sock")

    type "clear && lsmod | grep hv"; press Enter
    wait "hv_sock"
}

Применим также макрос exec_bash_command в install_guest_additions:

macro install_guest_additions() {
    plug dvd "${ISO_DIR}\\testo-guest-additions-hyperv.iso"

    type "mount /dev/cdrom /media"; press Enter
    wait "mounting read-only"

    exec_bash_command("dpkg -i /media/${guest_additions_pkg}")
    exec_bash_command("umount /media")
    
    sleep 2s
    unplug dvd
}

Запустим тесты и в этот раз увидим, что кеш всё-таки потерялся:

C:\Users\Testo> testo run macros.testo --stop_on_fail --param ISO_DIR C:\iso
Because of the cache loss, Testo is scheduled to run the following tests:
- server_prepare
- server_install_guest_additions
- server_setup_nic
- client_prepare
- client_install_guest_additions
- client_setup_nic
- test_ping
Do you confirm running them? [y/N]: y
UP-TO-DATE TESTS:
server_install_ubuntu
client_install_ubuntu
TESTS TO RUN:
server_prepare
server_install_guest_additions
server_setup_nic
client_prepare
client_install_guest_additions
client_setup_nic
test_ping
[ 22%] Preparing the environment for test server_prepare
[ 22%] Restoring snapshot server_install_ubuntu for virtual machine server
[ 22%] Running test server_prepare
[ 22%] Calling macro prepare_ubuntu(hostname="server", login="server-login", password="1111") in virtual machine server
[ 22%] Calling macro enter_sudo(hostname="server", login="server-login", password="1111") in virtual machine server
[ 22%] Typing "sudo su" with interval 30ms in virtual machine server
[ 22%] Pressing key ENTER in virtual machine server
[ 22%] Waiting "password for server-login" for 1m with interval 1s in virtual machine server
[ 22%] Typing "1111" with interval 30ms in virtual machine server
[ 22%] Pressing key ENTER in virtual machine server
[ 22%] Waiting "root@server" for 1m with interval 1s in virtual machine server
[ 22%] Calling macro exec_bash_command(command="dhclient -r eth0 && dhclient eth0", time_to_wait="1m") in virtual machine server
[ 22%] Typing "clear && dhclient -r eth0 && dhclient eth0 && echo Result is $?" with interval 30ms in virtual machine server
[ 22%] Pressing key ENTER in virtual machine server
[ 22%] Waiting "Result is 0" for 1m with interval 1s in virtual machine server
[ 22%] Calling macro exec_bash_command(command="apt update", time_to_wait="1m") in virtual machine server
[ 22%] Typing "clear && apt update && echo Result is $?" with interval 30ms in virtual machine server
[ 22%] Pressing key ENTER in virtual machine server
[ 22%] Waiting "Result is 0" for 1m with interval 1s in virtual machine server
[ 22%] Calling macro exec_bash_command(command="apt install -y linux-azure", time_to_wait="15m") in virtual machine server
[ 22%] Typing "clear && apt install -y linux-azure && echo Result is $?" with interval 30ms in virtual machine server
[ 22%] Pressing key ENTER in virtual machine server
[ 22%] Waiting "Result is 0" for 15m with interval 1s in virtual machine server
[ 22%] Typing "reboot" with interval 30ms in virtual machine server
[ 22%] Pressing key ENTER in virtual machine server
[ 22%] Waiting "login:" for 2m with interval 1s in virtual machine server
[ 22%] Typing "server-login" with interval 30ms in virtual machine server
[ 22%] Pressing key ENTER in virtual machine server
[ 22%] Waiting "Password:" for 1m with interval 1s in virtual machine server
[ 22%] Typing "1111" with interval 30ms in virtual machine server
[ 22%] Pressing key ENTER in virtual machine server
[ 22%] Waiting "Welcome to Ubuntu" for 1m with interval 1s in virtual machine server
[ 22%] Calling macro enter_sudo(hostname="server", login="server-login", password="1111") in virtual machine server
[ 22%] Typing "sudo su" with interval 30ms in virtual machine server
[ 22%] Pressing key ENTER in virtual machine server
[ 22%] Waiting "password for server-login" for 1m with interval 1s in virtual machine server
[ 22%] Typing "1111" with interval 30ms in virtual machine server
[ 22%] Pressing key ENTER in virtual machine server
[ 22%] Waiting "root@server" for 1m with interval 1s in virtual machine server
[ 22%] Calling macro exec_bash_command(command="modprobe hv_sock", time_to_wait="1m") in virtual machine server
[ 22%] Typing "clear && modprobe hv_sock && echo Result is $?" with interval 30ms in virtual machine server
[ 22%] Pressing key ENTER in virtual machine server
[ 22%] Waiting "Result is 0" for 1m with interval 1s in virtual machine server
[ 22%] Typing "clear && lsmod | grep hv" with interval 30ms in virtual machine server
[ 22%] Pressing key ENTER in virtual machine server
[ 22%] Waiting "hv_sock" for 1m with interval 1s in virtual machine server
[ 22%] Taking snapshot server_prepare for virtual machine server
[ 33%] Test server_prepare PASSED in 0h:5m:50s
[ 33%] Preparing the environment for test server_install_guest_additions
[ 33%] Running test server_install_guest_additions
[ 33%] Calling macro install_guest_additions() in virtual machine server
[ 33%] Plugging dvd C:/iso/testo-guest-additions-hyperv.iso into virtual machine server
[ 33%] Typing "mount /dev/cdrom /media" with interval 30ms in virtual machine server
[ 33%] Pressing key ENTER in virtual machine server
[ 33%] Waiting "mounting read-only" for 1m with interval 1s in virtual machine server
[ 33%] Calling macro exec_bash_command(command="dpkg -i /media/testo-guest-additions.deb", time_to_wait="1m") in virtual machine server
[ 33%] Typing "clear && dpkg -i /media/testo-guest-additions.deb && echo Result is $?" with interval 30ms in virtual machine server
[ 33%] Pressing key ENTER in virtual machine server
[ 33%] Waiting "Result is 0" for 1m with interval 1s in virtual machine server
[ 33%] Calling macro exec_bash_command(command="umount /media", time_to_wait="1m") in virtual machine server
[ 33%] Typing "clear && umount /media && echo Result is $?" with interval 30ms in virtual machine server
[ 33%] Pressing key ENTER in virtual machine server
[ 33%] Waiting "Result is 0" for 1m with interval 1s in virtual machine server
[ 33%] Sleeping in virtual machine server for 2s
[ 33%] Unplugging dvd from virtual machine server
[ 33%] Taking snapshot server_install_guest_additions for virtual machine server
[ 44%] Test server_install_guest_additions PASSED in 0h:0m:16s
[ 44%] Preparing the environment for test server_setup_nic
[ 44%] Running test server_setup_nic
[ 44%] Copying C:/Users/Testo/rename_net.sh to virtual machine server to destination /opt/rename_net.sh with timeout 10m
[ 44%] Executing bash command in virtual machine server with timeout 10m
+ chmod +x /opt/rename_net.sh
+ /opt/rename_net.sh 52:54:00:00:00:bb client_side
Renaming success
+ ip a a 192.168.1.1/24 dev client_side
+ ip l s client_side up
+ ip ad
1: lo: <LOOPBACK,UP,LOWER_UP> mtu 65536 qdisc noqueue state UNKNOWN group default qlen
1000
link/loopback 00:00:00:00:00:00 brd 00:00:00:00:00:00
inet 127.0.0.1/8 scope host lo
valid_lft forever preferred_lft forever
inet6 ::1/128 scope host
valid_lft forever preferred_lft forever
2: eth0: <BROADCAST,MULTICAST,UP,
LOWER_UP> mtu 1500 qdisc mq state UP group default qlen 1000
link/ether 00:15:5d:01:25:15 brd ff:ff:ff:ff:ff:ff
inet 192.168.192.158/28 brd 192.168.192.159 scope global eth0
valid_lft forever preferred_lft forever
inet6 fe80::215:5dff:fe
01:2515/64 scope link
valid_lft forever preferred_lft forever
3: client_side: <BROADCAST,MULTICAST,UP,LOWER_UP> mtu 1500 qdisc mq state UP group default qlen 1000
link/ether 52:54:00:00:00:bb brd ff:ff:ff:ff:ff:ff
inet 192.168.1.1/24 scope
global client_side
valid_lft forever preferred_lft forever
inet6 fe80::5054:ff:fe00:bb/64 scope link tentative
valid_lft forever preferred_lft forever
[ 44%] Taking snapshot server_setup_nic for virtual machine server
[ 56%] Test server_setup_nic PASSED in 0h:0m:2s
[ 56%] Preparing the environment for test client_prepare
[ 56%] Restoring snapshot client_install_ubuntu for virtual machine client
[ 56%] Running test client_prepare
[ 56%] Calling macro prepare_ubuntu(hostname="client", login="client-login", password="1111") in virtual machine client
[ 56%] Calling macro enter_sudo(hostname="client", login="client-login", password="1111") in virtual machine client
[ 56%] Typing "sudo su" with interval 30ms in virtual machine client
[ 56%] Pressing key ENTER in virtual machine client
[ 56%] Waiting "password for client-login" for 1m with interval 1s in virtual machine client
[ 56%] Typing "1111" with interval 30ms in virtual machine client
[ 56%] Pressing key ENTER in virtual machine client
[ 56%] Waiting "root@client" for 1m with interval 1s in virtual machine client
[ 56%] Calling macro exec_bash_command(command="dhclient -r eth0 && dhclient eth0", time_to_wait="1m") in virtual machine client
[ 56%] Typing "clear && dhclient -r eth0 && dhclient eth0 && echo Result is $?" with interval 30ms in virtual machine client
[ 56%] Pressing key ENTER in virtual machine client
[ 56%] Waiting "Result is 0" for 1m with interval 1s in virtual machine client
[ 56%] Calling macro exec_bash_command(command="apt update", time_to_wait="1m") in virtual machine client
[ 56%] Typing "clear && apt update && echo Result is $?" with interval 30ms in virtual machine client
[ 56%] Pressing key ENTER in virtual machine client
[ 56%] Waiting "Result is 0" for 1m with interval 1s in virtual machine client
[ 56%] Calling macro exec_bash_command(command="apt install -y linux-azure", time_to_wait="15m") in virtual machine client
[ 56%] Typing "clear && apt install -y linux-azure && echo Result is $?" with interval 30ms in virtual machine client
[ 56%] Pressing key ENTER in virtual machine client
[ 56%] Waiting "Result is 0" for 15m with interval 1s in virtual machine client
[ 56%] Typing "reboot" with interval 30ms in virtual machine client
[ 56%] Pressing key ENTER in virtual machine client
[ 56%] Waiting "login:" for 2m with interval 1s in virtual machine client
[ 56%] Typing "client-login" with interval 30ms in virtual machine client
[ 56%] Pressing key ENTER in virtual machine client
[ 56%] Waiting "Password:" for 1m with interval 1s in virtual machine client
[ 56%] Typing "1111" with interval 30ms in virtual machine client
[ 56%] Pressing key ENTER in virtual machine client
[ 56%] Waiting "Welcome to Ubuntu" for 1m with interval 1s in virtual machine client
[ 56%] Calling macro enter_sudo(hostname="client", login="client-login", password="1111") in virtual machine client
[ 56%] Typing "sudo su" with interval 30ms in virtual machine client
[ 56%] Pressing key ENTER in virtual machine client
[ 56%] Waiting "password for client-login" for 1m with interval 1s in virtual machine client
[ 56%] Typing "1111" with interval 30ms in virtual machine client
[ 56%] Pressing key ENTER in virtual machine client
[ 56%] Waiting "root@client" for 1m with interval 1s in virtual machine client
[ 56%] Calling macro exec_bash_command(command="modprobe hv_sock", time_to_wait="1m") in virtual machine client
[ 56%] Typing "clear && modprobe hv_sock && echo Result is $?" with interval 30ms in virtual machine client
[ 56%] Pressing key ENTER in virtual machine client
[ 56%] Waiting "Result is 0" for 1m with interval 1s in virtual machine client
[ 56%] Typing "clear && lsmod | grep hv" with interval 30ms in virtual machine client
[ 56%] Pressing key ENTER in virtual machine client
[ 56%] Waiting "hv_sock" for 1m with interval 1s in virtual machine client
[ 56%] Taking snapshot client_prepare for virtual machine client
[ 67%] Test client_prepare PASSED in 0h:5m:48s
[ 67%] Preparing the environment for test client_install_guest_additions
[ 67%] Running test client_install_guest_additions
[ 67%] Calling macro install_guest_additions() in virtual machine client
[ 67%] Plugging dvd C:/iso/testo-guest-additions-hyperv.iso into virtual machine client
[ 67%] Typing "mount /dev/cdrom /media" with interval 30ms in virtual machine client
[ 67%] Pressing key ENTER in virtual machine client
[ 67%] Waiting "mounting read-only" for 1m with interval 1s in virtual machine client
[ 67%] Calling macro exec_bash_command(command="dpkg -i /media/testo-guest-additions.deb", time_to_wait="1m") in virtual machine client
[ 67%] Typing "clear && dpkg -i /media/testo-guest-additions.deb && echo Result is $?" with interval 30ms in virtual machine client
[ 67%] Pressing key ENTER in virtual machine client
[ 67%] Waiting "Result is 0" for 1m with interval 1s in virtual machine client
[ 67%] Calling macro exec_bash_command(command="umount /media", time_to_wait="1m") in virtual machine client
[ 67%] Typing "clear && umount /media && echo Result is $?" with interval 30ms in virtual machine client
[ 67%] Pressing key ENTER in virtual machine client
[ 67%] Waiting "Result is 0" for 1m with interval 1s in virtual machine client
[ 67%] Sleeping in virtual machine client for 2s
[ 67%] Unplugging dvd from virtual machine client
[ 67%] Taking snapshot client_install_guest_additions for virtual machine client
[ 78%] Test client_install_guest_additions PASSED in 0h:0m:17s
[ 78%] Preparing the environment for test client_setup_nic
[ 78%] Running test client_setup_nic
[ 78%] Copying C:/Users/Testo/rename_net.sh to virtual machine client to destination /opt/rename_net.sh with timeout 10m
[ 78%] Executing bash command in virtual machine client with timeout 10m
+ chmod +x /opt/rename_net.sh
+ /opt/rename_net.sh 52:54:00:00:00:aa server_side
Renaming success
+ ip a a 192.168.1.2/24 dev server_side
+ ip l s server_side up
+ ip ad
1: lo: <LOOPBACK,UP,LOWER_UP> mtu 65536 qdisc noqueue state UNKNOWN group default qlen
1000
link/loopback 00:00:00:00:00:00 brd 00:00:00:00:00:00
inet 127.0.0.1/8 scope host lo
valid_lft forever preferred_lft forever
inet6 ::1/128 scope host
valid_lft forever preferred_lft forever
2: eth0: <BROADCAST,MULTICAST,UP,
LOWER_UP> mtu 1500 qdisc mq state UP group default qlen 1000
link/ether 00:15:5d:01:25:16 brd ff:ff:ff:ff:ff:ff
inet 192.168.192.150/28 brd 192.168.192.159 scope global eth0
valid_lft forever preferred_lft forever
inet6 fe80::215:5dff:fe
01:2516/64 scope link
valid_lft forever preferred_lft forever
3: server_side: <BROADCAST,MULTICAST,UP,LOWER_UP> mtu 1500 qdisc mq state UP group default qlen 1000
link/ether 52:54:00:00:00:aa brd ff:ff:ff:ff:ff:ff
inet 192.168.1.2/24 scope
global server_side
valid_lft forever preferred_lft forever
inet6 fe80::5054:ff:fe00:aa/64 scope link tentative
valid_lft forever preferred_lft forever
[ 78%] Taking snapshot client_setup_nic for virtual machine client
[ 89%] Test client_setup_nic PASSED in 0h:0m:2s
[ 89%] Preparing the environment for test test_ping
[ 89%] Running test test_ping
[ 89%] Executing bash command in virtual machine client with timeout 10m
+ ping 192.168.1.2 -c5
PING 192.168.1.2 (192.168.1.2) 56(84) bytes of data.
64 bytes from 192.168.1.2: icmp_seq=1 ttl=64 time=0.022 ms
64 bytes from 192.168.1.2: icmp_seq=2 ttl=64 time=0.033 ms
64 bytes from 192.168.1.2: icmp_seq=3 ttl=64 time=0.032 ms
64
bytes from 192.168.1.2: icmp_seq=4 ttl=64 time=0.036 ms
64 bytes from 192.168.1.2: icmp_seq=5 ttl=64 time=0.028 ms

--- 192.168.1.2 ping statistics ---
5 packets transmitted, 5 received, 0% packet loss, time 4100ms
rtt min/avg/max/mdev = 0.022/0.030/0.036/
0.006 ms
[ 89%] Executing bash command in virtual machine server with timeout 10m
+ ping 192.168.1.1 -c5
PING 192.168.1.1 (192.168.1.1) 56(84) bytes of data.
64 bytes from 192.168.1.1: icmp_seq=1 ttl=64 time=0.014 ms
64 bytes from 192.168.1.1: icmp_seq=2 ttl=64 time=0.036 ms
64 bytes from 192.168.1.1: icmp_seq=3 ttl=64 time=0.029 ms
64
bytes from 192.168.1.1: icmp_seq=4 ttl=64 time=0.033 ms
64 bytes from 192.168.1.1: icmp_seq=5 ttl=64 time=0.031 ms

--- 192.168.1.1 ping statistics ---
5 packets transmitted, 5 received, 0% packet loss, time 4089ms
rtt min/avg/max/mdev = 0.014/0.028/0.036/
0.009 ms
[ 89%] Taking snapshot test_ping for virtual machine client
[ 89%] Taking snapshot test_ping for virtual machine server
[100%] Test test_ping PASSED in 0h:0m:14s
PROCESSED TOTAL 9 TESTS IN 0h:12m:32s
UP-TO-DATE: 2
RUN SUCCESSFULLY: 7
FAILED: 0
C:\Users\Testo>

Это связано с тем, что в самом начале макроса prepare_ubuntu было действие

type "dhclient -r eth0 && dhclient eth0 && echo Result is $?";

Которое после применения макроса exec_bash_command превратилось в действие

type "clear && dhclient -r eth0 && dhclient eth0 && echo Result is $?";

Т.к. действие поменялось, то Testo инвалидирует кеш всех тестов, где применяется макрос ubuntu_prepare, то есть тестов client_prepare и server_prepare. А также автоматически и всех их потомков.

Макросы с объявлениями

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

Директива include

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

Вместо одного файла macros.testo у нас появится несколько файлов: declarations.testo, macros.testo и tests.testo. В файл declarations.testo мы занесем все объявления виртуальных сущностей (machine, flash и network), а также параметры; в файле macros.testo оставим макросы, ф все тесты будут лежать в tests.testo. Конечно, надо понимать, что такое разделение достаточно условное, и что вы можете перемещать код из разных файлов так, как вам удобнее.

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

В нашем проекте файл declarations.testo не зависит ни от чего, поэтому он не нуждается в директиве include. Файл macros.testo же зависит от declarations.testo, потому что макросы используют параметры default_password и guest_additions_pkg, которые объявлены в declarations.testo. Для правильного функционирования подсчета контрольных сумм нам необходимо удостовериться, что эти параметры точно будут объявлены на момент объявления макросов. Поэтому в начале файла macros.testo необходимо добавить директиву include

include "declarations.testo"

macro install_ubuntu(hostname, login, password = "${default_password}") {
    ...

Файл с тестами tests.testo явно зависит как от declarations.testo, так и от macros.testo. Но т.к. declarations.testo уже включен в macros.testo, нам достаточно включить только macros.testo

include "macros.testo"

test server_install_ubuntu {
    server install_ubuntu("${server_hostname}", "${server_login}")
}
...

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

  1. Указание "конечного" файла с тестами: testo run tests.testo --stop_on_fail --param ISO_DIR C:\iso
  2. Указание целой папки с тестами: testo run .\ --stop_on_fail --param ISO_DIR C:\iso

Итоги

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

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