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

ВступлениеПодготовка к запускуАрхитектура платформы 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. Установлен менеджер виртуальных машин virt-manager.
  3. На хостовой машине имеется доступ в Интернет
  4. Имеется установочный образ Ubuntu server 16.04 с расположением /opt/iso/ubuntu_server.iso. Местоположение и название установочного файла может быть другим, в этом случае нужно будет соответствующим образом поправить параметр ISO_DIR, передаваемый через командную строку во время запуска тестов.
  5. Имеется образ с гостевыми дополнениями Testo в одной папке с установочным образом Ubuntu.
  6. (Рекомендовано) Настроена подсветка синтаксиса Testo-lang в Sublime Text 3.
  7. (Рекомендовано) Проделаны шаги из восьмой части.

Вступление

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

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

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

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

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

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

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

test server_install_ubuntu {
    server {
        start
        wait "English"
        press Enter
        #Действия могут разделяться символом новой строки
        #или точкой с запятой
        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 "Select your timezone" timeout 2m; 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 "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 "Install the GRUB boot loader to the master boot record?" timeout 10m; press Enter
        wait "Installation complete" timeout 1m;

        unplug dvd; press Enter
        wait "${server_hostname} 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
    wait "English"
    press Enter
    #Действия могут разделяться символом новой строки
    #или точкой с запятой
    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 "Select your timezone" timeout 2m; 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 "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 "Install the GRUB boot loader to the master boot record?" timeout 10m; press Enter
    wait "Installation complete" timeout 1m;

    unplug dvd; press Enter
    wait "${hostname} 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}")
}

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

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

user$ sudo testo run hello_world.testo --stop_on_fail --param ISO_DIR /opt/iso
UP-TO-DATE TESTS:
server_install_ubuntu
server_install_guest_additions
server_unplug_nat
server_prepare
client_install_ubuntu
client_install_guest_additions
client_unplug_nat
client_prepare
test_ping
exchange_files_with_flash
PROCESSED TOTAL 10 TESTS IN 0h:0m:0s
UP-TO-DATE: 10
RUN SUCCESSFULLY: 0
FAILED: 0
user$

Что же мы наблюдаем? Мы видим, что все наши тесты остались закешированными, несмотря на то, что мы очень сильно (как кажется) изменили текст самых базовых тестов. Однако платформа 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}")
}

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

user$ sudo testo run hello_world.testo --stop_on_fail --param ISO_DIR /opt/iso
UP-TO-DATE TESTS:
server_install_ubuntu
server_install_guest_additions
server_unplug_nat
server_prepare
client_install_ubuntu
client_install_guest_additions
client_unplug_nat
client_prepare
test_ping
exchange_files_with_flash
PROCESSED TOTAL 10 TESTS IN 0h:0m:0s
UP-TO-DATE: 10
RUN SUCCESSFULLY: 0
FAILED: 0
user$

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

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

param guest_additions_pkg "testo-guest-additions*"

macro install_guest_additions(hostname, login, password="${default_password}") {
    plug dvd "${ISO_DIR}/testo-guest-additions.iso"

    type "sudo su"; press Enter;
    #Обратите внимание, обращаться к параметрам можно в любом участке строки
    wait "password for ${login}"; type "${password}"; press Enter
    wait "root@${hostname}"

    type "mount /dev/cdrom /media"; press Enter
    wait "mounting read-only"; type "dpkg -i /media/${guest_additions_pkg}"; press Enter;
    wait "Setting up testo-guest-additions"
    type "umount /media"; press Enter;
    #Дадим немного времени для команды umount
    sleep 2s
    unplug dvd
}

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

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

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

test server_install_guest_additions: server_install_ubuntu {
    server install_guest_additions("${server_hostname}", "${server_login}")
}

test client_install_guest_additions: client_install_ubuntu {
    client install_guest_additions("${client_hostname}", "${client_login}")
}

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

user$ sudo testo run hello_world.testo --stop_on_fail --param ISO_DIR /opt/iso
UP-TO-DATE TESTS:
server_install_ubuntu
server_install_guest_additions
server_unplug_nat
server_prepare
client_install_ubuntu
client_install_guest_additions
client_unplug_nat
client_prepare
test_ping
exchange_files_with_flash
PROCESSED TOTAL 10 TESTS IN 0h:0m:0s
UP-TO-DATE: 10
RUN SUCCESSFULLY: 0
FAILED: 0
user$

Макрос unplug nat

Следующий участок кода, который хотелось бы вынести в макрос - действия по отключению сетевого адаптера nat. Этот код идентичен как для client, так и для server. В первом приближении соответствующий макрос мог бы выглядеть так:

macro unplug_nat(hostname, login, password="${default_password}") {
    shutdown
    unplug nic nat
    start

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

...

test server_unplug_nat: server_install_guest_additions {
    server unplug_nat("${server_hostname}", "${server_login}")
}

test client_unplug_nat: client_install_guest_additions {
    client unplug_nat("${client_hostname}", "${client_login}")
}

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

Для этого надо параметризировать имя сетевого интерфейса в команде unplug nic. Язык Testo-lang позволяет опционально использовать строки вместо обычных токенов в некоторых командах. Например, в языке Testo-lang действие unplug nic nat равносильно действию unplug nic "nat". Формат использования строк вместо обычных токенов хорош тем, что строки можно параметризировать, поэтому нам никто не мешает написать действие unplug nic "${nic_name}". Таким образом можно макрос unplug_nat расширить до макроса unplug_nic, и картина в целом будет выглядеть следующим образом:

macro unplug_nic(hostname, login, nic_name, password="${default_password}") {
    shutdown
    unplug nic "${nic_name}"
    start

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

...

test server_unplug_nat: server_install_guest_additions {
    server unplug_nic("${server_hostname}", "${server_login}", "nat")
}

test client_unplug_nat: client_install_guest_additions {
    client unplug_nic("${client_hostname}", "${client_login}", "nat")
}

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

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

Макрос process_flash

Теперь пришло время заняться упрощением работы с флешками.

Алгоритм работы с флешками в наших тестах выглядит следующим образом:

  1. Подключить флешку к виртуальной машине (plug flash exchange_flash);
  2. Подмонтировать флешку в файловую систему (exec bash "mount /dev/sdb1 /media");
  3. Выполнить какую-то баш-команду (копирование файлов);
  4. Отмонтировать флешку (exec bash "umount /media");
  5. Вытащить флешку из виртуальной машины (unplug flash exchange_flash).

Все эти действия можно инкапсулировать в макрос, если воспользоваться возможностью задавать имена флешек в команде (un)plug flash в виде строк (как и имя сетевого адаптера в (un)plug nic).

macro process_flash(flash_name, command) {
    plug flash "${flash_name}"
    sleep 5s
    exec bash "mount /dev/sdb1 /media"
    exec bash "${command}"
    exec bash "umount /media"
    unplug flash "${flash_name}"
}

В этом макросе в качестве аргументов мы передаём имя флешки, а также bash-команду, которую необходимо выполнить после подмонтирования флешки. Обратите внимание на передачу имени флешки в качестве строки в действиях plug flash "${flash_name}" и unplug flash "${flash_name}".

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

test client_prepare: client_unplug_nat {
    client {
        process_flash("exchange_flash", "cp /media/rename_net.sh /opt/rename_net.sh")

        exec bash """
            chmod +x /opt/rename_net.sh
            /opt/rename_net.sh 52:54:00:00:00:aa server_side
            ip a a 192.168.1.2/24 dev server_side
            ip l s server_side up
            ip ad
        """
    }
}

...

test exchange_files_with_flash: client_prepare, server_prepare {
    client {
        #Создаём файл, который нужно будет передать на сервер
        exec bash "echo \"Hello from client!\" > /tmp/copy_me_to_server.txt"
        process_flash("exchange_flash", "cp /tmp/copy_me_to_server.txt /media")
    }

    server {
        process_flash("exchange_flash", "cp /media/copy_me_to_server.txt /tmp")
        exec bash "cat /tmp/copy_me_to_server.txt"
    }
}

Макрос с командами

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

Давайте взглянем ещё раз на тест exchange_files_with_flash. Сейчас в рамках этого теста происходит копирование файла с одной машины на другую с помощью флешки. Однако, стоит задуматься: ведь действия при копировании файлов между двумя машинами довольно похожи, в независимости от самих машин (по крайней мере, если обе машины имеют ОС Linux на борту и в них установлены гостевые дополнения). Что если мы хотим инкапсулировать действия, необходимые для передачи файлов между машинами? Заметим, что для этого потребуются действия не с одной, а с двумя машинами сразу, поэтому обычный макрос из действий тут не подойдёт. Поэтому здесь нам потребуется создать макрос с командами:

macro copy_file_with_flash(vm_from, vm_to, copy_flash, file_from, file_to) {
    "${vm_from}" process_flash("${copy_flash}", "cp ${file_from} /media/$(basename ${file_from})")
    "${vm_to}" process_flash("${copy_flash}", "cp /media/$(basename ${file_from}) ${file_to}")
}

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

  • vm_from - имя виртуальной машины, откуда следует скопировать файл.
  • vm_to - имя виртуальной машины, куда следует скопировать файл.
  • copy_flash - имя виртуальной флешки, которая будет использоваться для переноса файлов между машинами.
  • file_from - путь к копируемому файлу на машине vm_from.
  • file_to - путь назначения файла на машине vm_to.

Для составления макроса с командами мы воспользовались одной замечательной возможностью языка Testo-lang: имена виртуальных машин (как и флешек) в командах могут быть представлены не только идентификаторами (client, server), но и строками ("client", "server"). При этом строки нам пригодились только лишь потому, что в них можно использовать обращение к параметрам и к аргументам макросов.

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

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

Копируемый файл будет размещён в корне флешки copy_flash. Для того чтобы извлечь имя файла из file_from мы используем баш-конструкцию $(basename ${file_from}).

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

test exchange_files_with_flash: client_prepare, server_prepare {
    client exec bash "echo \"Hello from client!\" > /tmp/copy_me_to_server.txt"
    copy_file_with_flash("client", "server", "exchange_flash", "/tmp/copy_me_to_server.txt", "/tmp/copy_me_to_server.txt")
    server exec bash "cat /tmp/copy_me_to_server.txt"
}

Теперь тест состоит из трёх команд:

  1. В первой команде мы создаём файл, который нужно скопировать.
  2. Вторая команда - это вызов макроса copy_file_with_flash. Т.к. этот макрос содержит команды, то и вызывать такой макрос нужно как команду.
  3. Третья команда - это вывод скопированного файла на сервере после копирования.

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

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

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

Директива include

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

Вместо одного файла hello_world.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. Указание "конечного" файла с тестами: sudo testo run tests.testo --stop_on_fail --param ISO_DIR /opt/iso
  2. Указание целой папки с тестами: sudo testo run ./ --stop_on_fail --param ISO_DIR /opt/iso

Итоги

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

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