Tox Ansible#

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

Ключевые особенности и достоинства#

Tox Ansible имеет следующие ключевые особенности и достоинства:

  • Поддержка мультиверсионного тестирования.

    Tox Ansible позволяет тестировать коллекции сразу на нескольких версиях Python и Ansible.

  • Интеграция с инструментами тестирования Python.

    Tox Ansible позволяет проводить следующие типы тестов:

    • sanity – проверка качества кода и соответствия стандартам;

    • unit – модульное тестирование;

    • integration – интеграционное тестирование.

    Для проведения тестирования используются следующие инструменты:

    • tox – управляет тестовыми средами;

    • ansible-test – проводит тесты типа sanity;

    • pytest – выполняет тесты типов unit и integration.

  • Локальное тестирование.

    Тестовые окружения сохраняются после завершения тестирования. Это сокращает время на развертывание окружений.

  • Простота управления тестовыми окружениями.

    Все виртуальные окружения создаются внутри каталога .tox/. Если нужно, эти окружения можно легко удалить или пересоздать без дополнительных настроек.

Установка#

Для установки Tox Ansible выполните следующие действия:

  1. Подключите репозиторий Astra Automation.

    Инструкция по подключению репозитория
    1. В каталоге /etc/apt/sources.list.d/ создайте файл astra-automation.list со ссылкой на репозиторий Astra Automation:

      deb https://dl.astralinux.ru/aa/aa-debs-for-alse-1.8 <version> main
      

      Вместо <version> необходимо подставить версию устанавливаемой платформы, например, 1.2.

      Доступные версии продукта опубликованы в таблице История обновлений.

    2. Обновите список доступных пакетов:

      sudo apt update
      
  2. Выполните команду:

    sudo apt-get install tox-ansible --yes
    

Установка при отсутствии доступа к интернету описана в документе Разработка контента и среды исполнения.

Принцип работы#

Работа с Tox Ansible начинается с создания в корневом каталоге проекта файла настроек tox-ansible.ini. В этом файле указываются следующие параметры:

  • тестовые окружения;

  • зависимости;

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

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

Параметры запуска Tox Ansible также можно задать с помощью аргументов командной строки. Настройки из аргументов командной строки имеют более высокий приоритет, чем указанные в файле tox-ansible.ini.

Параметры в файле настроек задаются как в следующем примере:

[tox]
envlist =
    unit-py37-ansible2.9,
    unit-py37-ansible2.10,
    integration-py37-ansible2.9,
    integration-py37-ansible2.10

[testenv]
basepython =
    py37: python3.7
deps =
    ansible2.9: ansible==2.9.*
    ansible2.10: ansible==2.10.*
commands = pytest tests

Подробное описание параметров файла настроек см. в справочнике.

При запуске Tox Ansible выполняет следующие действия:

  1. Определяет список тестовых окружений, указанных в файле настроек.

  2. Для каждого тестового окружения создает изолированное виртуальное окружение с соответствующей версией Python.

  3. Устанавливает зависимости, указанные для каждого тестового окружения.

  4. Запускает тесты внутри каждого виртуального окружения.

  5. Собирает и выводит результаты тестирования.

Примеры#

Для изучения работы Tox Ansible на практике в вашей системе должен быть предварительно установлен Python версий 3.11. При отсутствии в системе необходимой версии Python тесты для нее будут пропущены.

Базовый сценарий#

Типичный сценарий применения Tox Ansible содержит следующие шаги:

  1. Создайте коллекцию my_collection:

    ansible-galaxy collection init my_namespace.my_collection
    
  2. Перейдите в каталог roles/:

    cd my_namespace/my_collection/roles/
    
  3. Создайте роль test_role:

    ansible-galaxy init test_role
    
  4. Добавьте в файл main.yml следующие данные:

    ---
    - name: Message Hello Role
      ansible.builtin.debug:
        msg: "Hello collection"
    
  5. Перейдите в каталог my_collection/:

    cd ..
    
  6. В файле /my_namespace/my_collectionmeta/runtime.yml раскомментируйте строку:

    requires_ansible: '>=2.9.10'
    
  7. В каталоге my_collection/ создайте сценарий hello_collection.yml со следующим содержимым:

    ---
    - name: Test Role
      hosts: localhost
      roles:
        - test_role
    
  8. Создайте файл tests/test_sanity.py c тестом типа sanity

    test_sanity.py#
    import ansible_runner
    
    
    def test_hello_collection():
        result = ansible_runner.run(private_data_dir=".", playbook="hello_collection.yml")
    
        assert result.rc == 0, f"Сценарий завершился с ошибкой: {result.rc}"
    
        # Проверка наличия "Hello collection" в stdout
        assert any(
            "Hello collection" in event.get("stdout", "") for event in result.events
        ), "Сообщение 'Hello collection' не найдено в выводе"
    
  9. Создайте файл tests/integration/test_integration.py c тестом типа integration:

    test_integration.py#
    import subprocess
    import unittest
    
    
    class TestHelloCollectionIntegration(unittest.TestCase):
        def test_playbook_integration(self):
            """Проверка интеграции сценария и роли."""
    
            playbook_path = "<path_to_hello_collection.yml>"
    
            # Выполнение сценария через ansible-playbook
            result = subprocess.run(
                ["ansible-playbook", playbook_path],
                capture_output=True,
                text=True,
                check=True,
            )
    
            # Проверка кода завершения
            self.assertEqual(
                result.returncode, 0, f"Сценарий завершился с ошибкой: {result.stderr}"
            )
    
            # Проверка, что в выводе содержится ожидаемое сообщение
            self.assertIn(
                "Hello collection",
                result.stdout,
                "Сообщение 'Hello collection' не найдено в выводе.",
            )
    
    
    if __name__ == "__main__":
        unittest.main()
    

    Здесь <path_to_hello_collection.yml> – путь к сценарию hello_collection.yml, созданному ранее.

  10. Создайте файл tests/unit/test_unit.py c тестом типа unit:

    test_unit.py#
    import unittest
    
    
    def hello_message():
        return "Hello collection"
    
    
    class TestHelloFunction(unittest.TestCase):
        def test_hello_message(self):
            """Проверка, что функция возвращает правильное сообщение."""
    
            self.assertEqual(hello_message(), "Hello collection")
    
    
    if __name__ == "__main__":
        unittest.main()
    
  11. Создайте файл tox-ansible.ini со следующим содержимым:

    [tox]
    envlist = sanity-py3.11-ansible2.9, unit-py3.11-ansible2.9, integration-py3.11-ansible2.9
    skipsdist = true
    
    [testenv:sanity-py3.11-ansible2.9]
    basepython = python3.11
    deps =
       ansible==2.9
       pytest
       ansible-runner
    commands =
       pytest tests/test_sanity.py
    allowlist_externals =
       ansible-playbook
       mkdir
       bash
    
    [testenv:unit-py3.11-ansible2.9]
    basepython = python3.11
    deps =
       ansible==2.9
       pytest
       ansible-runner
    commands =
       pytest tests/unit/test_unit.py
    allowlist_externals =
       ansible-playbook
       mkdir
       bash
    
    [testenv:integration-py3.11-ansible2.9]
    basepython = python3.11
    deps =
       ansible==2.9
       pytest
       ansible-runner
    commands =
       pytest tests/integration/test_integration.py
    allowlist_externals =
       ansible-playbook
       mkdir
       bash
    
  12. Просмотрите список всех окружений, определенных в файле настроек:

    tox list --conf tox-ansible.ini
    
    Пример вывода
    default environments:
    sanity-py3.11-ansible2.9      -> [no description]
    unit-py3.11-ansible2.9        -> [no description]
    integration-py3.11-ansible2.9 -> [no description]
    
  13. Запустите утилиту:

    • Для всех типов тестов:

      tox -r --conf tox-ansible.ini
      
    • C фильтром по типу теста:

      tox -f unit -r --conf tox-ansible.ini
      

    Дождитесь завершения выполнения тестирования, это может занять некоторое время. Тестирование считается успешным, если для всех тестовых сред указано значение OK.

    Пример части вывода
    sanity-py3.11-ansible2.9: OK (14.09=setup[12.32]+cmd[1.77] seconds)
    unit-py3.11-ansible2.9: OK (10.98=setup[10.80]+cmd[0.18] seconds)
    integration-py3.11-ansible2.9: OK (13.33=setup[11.71]+cmd[1.62] seconds)
    congratulations :) (38.47 seconds)
    

Передача аргументов#

Tox Ansible автоматически запускает ansible-test для тестов типа sanity или pytest для тестов типа unit и integration. Однако иногда нужно настроить запуск этих тестов, добавив дополнительные аргументы.

Чтобы передать аргументы для ansible-test, используйте следующую команду:

tox -f sanity --ansible --conf tox-ansible.ini -- --test validate-modules -vvv

Здесь:

  • -f – фильтр тестов. В этом примере используется фильтр типа sanity.

  • --ansible – аргумент, активирующий поддержку Ansible.

  • -- – разделитель. Все, что идет после разделителя, передается как аргументы для ansible-test.

  • --test – название теста который необходимо запустить.

  • -vvv – повышение детализации вывода.

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

tox -e unit-py3.11-2.14 --ansible --conf tox-ansible.ini -- --junit-xml=tests/output/junit/unit.xml

Здесь:

  • -e – окружение для тестов:

    • unit – тип тестов.

    • py3.11 – версия Python.

    • 2.14 – версия Ansible.

  • -- – разделитель. Все, что идет после разделителя, передается как аргументы для pytest.

  • --junit-xml=tests/output/junit/unit.xml – путь для сохранения отчета о тестировании в формате XML.