Используем переменные окружения в тестировании

Переменные окружения - это такие переменные, которые определяются в операционной системе и доступны всем процессам, которые в этой операционной системе запускаются. Такие переменные часто используются для того чтобы передавать в приложение какую-либо информацию, специфичную для конкретной среды. Например, переменная JAVA_HOME обычно хранит в себе путь к дистрибутиву Java. Этот путь для разных компьютеров может отличаться, поэтому, если какой-либо программе, наприме, потребуется узнать, где хранятся утилиты, входящие в постаку Java SDK, эта программа извлечет значение из переменной JAVA_HOME, добавит к ней /bin и получит искомый путь. Кстати, все языки программирования имеют необходимый инструментарий для чтения значений переменных окружения. Давайте теперь взглянем на то, как работать с этими переменными нам (тестировщикам).

Манипулируем входными данными приложения

Представим, что мы тестируем приложение, использующее переменные окружения в своей бизнес-логике. Наше приложение - это, например, торговый бот, который ожидает, что адрес биржи, на которой он должен торговать, хранится в переменной EXCHANGE_ADDRESS. Подойдя к вопросу тестпланнига со стороны классической теории, мы подготовим набор тестовых данных, уичитывающих все аспекты алгоритма, используемого ботом для коннекции к бирже (не забыв также о негативных сценариях - например, намеренно невалидный синтаксис адреса, либо адрес, закрытый авторизационными механизмами, либо адрес, по которому клиент получает какой-либо код ошибки). Подойдем, и будем правы. Устанвливая раз за разом новые значения переменной и перезапуская приложение, мы покроем все запланированные сценарии. Сделать это, напрример, можно прописав соответствующие инструкции присваивания значений переменной и последующего запуска бота в исполняемом скриптовом файле (.bat- файлы для Windows или shell-скрипт для *nix-подобных систем). Но что, если мы захотим, чтобы у нас на машине работало бы два бота одновременно, и работали бы они с разными биржами?

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

Например, если вы откроете командную строку Windows и выполните команду set, вы увидите какой-то список переменных с некоторыми значениями. Откуда они взялись там? Их передал в процесс, отвечающий за командную строку, тот родительский процесс, из которого вы командную строку вызвали. И так далее каскадно.

env var example

Давайте проведем экперимент. Откроем командную строку (Windows) и выполним команду set. Веберем теперь каку-нибудь переменную в качестве подопытного экземпляра. В моем списке есть переменная PUBLIC. Я буду экспериментировать над ней. Для того чтобы запросить значние только одной моей переменной, выполним команду set PUBLIC. Отлично! Мы получили ее значение. Давайте изменим её (не бойтесь, она изменится только для текущего процесса, и, как мы далее увидим, для процессов, порожденных им). Выполняем set PUBLIC=%PUBLIC%-changed. Кстати оборачивание имени переменной в знаки % - это наиболее корректный способ получения значения переменной внутри вашего скрипта. Команду set PUBLIC можно смело заменить на echo %PUBLIC%. Результат будет отличаться только тем, что в первом случае вывод будет содержать пару имяпеременной-значение, а во втором - только значение.

Теперь давайте проверим значение переменной после изменения, тем более, что мы теперь знаем как это делается. Видно, что значение изменилось. Выполним команду start cmd.exe. Эта команда породит еще одно окно с командной строкой. Давайте проврим значение этой переменной здесь. Оказывается, оно соответствует тому, на что мы изменили оригинальное значение в предыдущем окне, т.е. окружение передалось от предка потомку.

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

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

Кстати, в ОС Linux (и в Unix), всё работает по точно такому же принципу. Отличаются только команды. Например, чтобы в скрипте, написанном для Linux сделать переменную доступной проессам-потомкам, неободимо выполнить команду export (например - export EXCHANGE_ADDRESS=http://blahblah.blah)

Реализуем средо-независимый набор автотестов

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

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

  • Домашний каталог - среднестатистические тесты продуцируют множество разноплановых артефактов. Логи, скриншоты, промежуточные данные и т.д. В коде тестов удобнее всего отталкиваться от некоего общего корневого каталога и все пути для таких артефактов указывать отностиельно него. Разработчики заранее не знают, где на сервере запуска тестов хранить такие данные вообще возможно. В целях безопасности, пользователь от имени которого запускается тест имеет обычно весьма ограниченные возможности (в том числе и касаемо привелегий чтения/записи). Поэтому процесс управления непрерывной интеграцией может через переменную окружения указать тестам, куда им разрешено писать данные.

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

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

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

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

Остались вопросы? Задавайте их тут. Я постараюсь дополнить статью опираясь на ваши замечания.