Как проще вынести данные для авторизации?

Всем привет, думаю вопрос тривиальный, но решения не нашёл. Только запускаем автотесты на своём проекте и столкнулись со следующей сложностью. В каждом тесте в фикстуре указываем пару логин пароль и адрес тестового окружения (или прода). Запускаем пока что локально, как разберёмся с авторизацией, то думаю следующая сложность будет, это запускать тесты в дженкинсе.
Сам вопрос, как правильно организовать запуск тестов с разными логин/паролями и адресом тестового окружения. Т.е. например у нас сейчас 50 тестов, 20 с одними авторизационными данными, 20 с другими и 10 с третьими. И ещё какая-то часть этих данных запускается исключительно на тестовом окружении. Т.е. нам не нужно запускать 20 тестов под одним пользователем, потом эти же 20 тестов под другим пользователем. И вот если захотелось проверить на другом тестовом окружении или пользователя поменять, приходится в ручную переписывать данные в 30 тестах. Как можно это сделать проще?
Была мысль убрать логин/пароль в базовый класс, чтобы хотя бы надо было только в одном месте менять данные, но тогда не получится запустить все тесты, так как повторюсь, часть запускается на разных окружениях и под разными пользователями.

Привет, я бы разделил проблему на две части:

  1. Вынесение конфигурации из тестов
  2. Хранение секретов локально и в Дженкинс

Возможно, кто-то предложит вариант получше, но на проектах где я был делали так:
В самих тестах не должно быть захардкоженных конфигурационных значений (адресов окружений, логинов, паролей), главная причина, поясняющая почему это bad practice - отображена в самом вопросе - с каждым захардкоженным значением где-то начинает плакать один автоматизатор, а в антарктиде откалывается огромный такой кусок ледника и летит прямиком в океан, повышая его уровень на 10^-12 миллиметров.

Можно сделать классы:
ConfigurationManager с методом .get_config_for(env), который забирает данные для нужного энва в порядке приоритетности источников
CredentialManager с методами типа .get_credentials_for(username) -> UserCredentials, которые позволяют получить данные нужного тестового юзера через указание параметра

При этом ConfigurationManager и CredentialManager должны быть статичными или с помощью средств языка инициализироваться до начала тестов, забирая данные в такой очередности по принципу “если нашел значение - юзаю его и дальше не лезу”, это позволяет определить приоритетность источников:

  1. Параметры из локального конфигурационного файла (это может быть YAML или JSON, которыми можно оверрайдить что-то для дебаггинга или хранить стабильные значения, которые не меняются от запуска к запуску, но при этом являются частью конфигурации (напр. настройки RetryCount, разные timeouts, и т.п.)
  2. Параметры, переданные непосредственно тестраннеру при запуске тестов (считаем это императивом, т.к. если кто-то специально передал их тестраннеру любым способом - значит человек знает, что делает, кушаем их в первую очередь)
  3. Параметры из {ENV}, которые могут быть определены Дженкинс джобой или локально (желательно не комбинировать с вариантом №1, потому что рано или поздно можно запутаться: Вась, а ты не помнишь что мы передаем напрямую раннеру, а что определяем как ENV-переменные?)

Как дальше запускать тесты (20 с одними данными, 20 с другими, 10 с третьими)
Разделить на Suites, для каждого из них сделать некий BeforeSuite (или его логический аналог в py.test) который просит нужный конфиг/креды у ConfigManager и CredentialsManager и уже их использовать в тестовых методах.

Если хочется просто конфиг и креды для какого-то энва в одну строку - можно в BeforeSuite вызывать метод объекта с конфигурацией типа “дай как мне конфиг для энва с названием test1 прошу по-братски”, а класс-конфигуратор по нему уже вернет объект с нужными адресами, паролями и явками.

Ниже суржикопсевдокод, где намешаны пайтон и джава, просьба строго не судить - хотел проиллюстрировать передаваемые/возвращаемые типы:

@BeforeSuite
def this_method_is_executed_once_before_suite(EnvName env_name) {
EnvironmentConfig config = ConfigurationManager.get_config_for_env(env_name);
UserCredentials creds = CredentialManager.get_creds_for(config.get_username())
connection = Connection.new(config.get_env_url(), creds.get_username(), creds.get_password());
}
@AfterSuite
def this_method_is_executed_once_after_suite(EnvName env_name) {
     connection.close();
}

@Test
def my_amazing_test() {
#Using existing connection to perform test
#Making assertions
}

@Test
def another_test() {
#Using the same existing opened connection (browser) to perform test
#Making assertions
}
.

По поводу хранения секретов
Чтобы не хранить секреты в репозитории (пусть даже он и закрыт), особенно если каждый тестировщик/дев имеет своих ламповых тестовых юзеров, обычно файл с конфигурацией где логины/пароли пушат в репозиторий как темплейт, где есть ключи, но нет значений (YAML или JSON, только не CSV, пожалуйста) - напр. user_credentials.yaml
Дальше человек клонит репозиторий, и указывает гиту не трекать изменения по файлу - потом туда забивает свои собственные значения.

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

В рамках Дженкинса есть возможность хранить секреты и дать юзерам подставлять нужные для запуска джобы с помощью Credential Manager, (есть инфа что это не супербезопасно, но для тривиального случая сойдет

4 симпатии

Спасибо за такой развёрнутый ответ, а почему рекомендуете выносить в YAML или JSON?

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

Ах да, еще они легко трекаются системами контроля версий, в отличии от того же CSV