Американская история разработчика из России

Привет, хабр. В преддверии старта курса «Python QA Engineer» подготовили для вас еще один интересный перевод.


Руководство, описанное в этой статье, поможет вам в тестировании веб-интерфейсов. Мы создадим простое надежное решение для тестирования веб-интерфейса с помощью Python, pytest и Selenium WebDriver. Мы рассмотрим стратегии построения хороших тестов и паттерны написания правильных автоматизированных тестов. Конечно же, разработанный проект по тестированию сможет послужить хорошей основой для создания собственных тест-кейсов.

Какой браузер?

Тест с поиском в DuckDuckGo из одной из предыдущих глав работает просто отлично… но только в Chrome. Взглянем на фикстуру browser еще разок:

@pytest.fixture def browser():   driver = Chrome()   driver.implicitly_wait(10)   yield driver   driver.quit()

Тип драйвера и время ожидания заданы жестко. Для доказательства концепции это, может, и хорошо, но тесты на продакшене нужно уметь конфигурировать в рантайме. Тесты для веб-интерфейсов должны работать в любом браузере. Значения таймаута по умолчанию должны регулироваться на случай, если одни среды работают медленнее, чем другие. Конфиденциальные данные, такие как имена пользователей и пароли, также никогда не должны появляться в исходном коде. Как же работать с такими тестовыми данными?

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

Источники входных данных

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

  • Аргументы командной строки;
  • Переменные среды;
  • Свойства системы;
  • Файлы конфигурации;
  • Запросы к API.

К сожалению, большинство платформ для тестирования не поддерживают чтение данных из аргументов командной строки. Переменными среды и свойствами системы сложно управлять и их потенциально опасно обрабатывать. API сервисов – это отличный способ потреблять входные данные, особенно получать секреты (например, пароли) от службы управления ключами, такой как, например, AWS KMS или Azure Key Vault. Однако платить за такой функционал может быть недопустимо, а писать самостоятельно – неразумно. В таком случае лучшим вариантом будут конфигурационные файлы.

Config-файл – это обычный файл, который содержит данные конфигурации. Автоматизированное тестирование может считывать его при запуске тестов и использовать входные значения для управления тестами. Например, в config-файле может быть указан тип браузера, который используется в качестве фикстуры browser в нашем примере проекта. Как правило, файлы конфигурации стандартного формата, например, JSON, YAML или INI. Также они должны быть плоскими, чтобы их можно было легко отличить от других файлов.

Наш config-файл

Давайте напишем файл конфигурации для нашего проекта по тестированию. Мы воспользуемся форматом JSON, поскольку он прост в использовании, популярен и в нем четко выделена иерархия. К тому же, модуль json – это стандартная библиотека Python, которая с легкостью конвертирует файлы JSON в словари. Создайте новый файл с именем tests/config.json и добавьте следующий код:

{   "browser": "chrome",   "wait_time": 10 }

JSON использует пары ключ-значение. Как мы уже говорили, в нашем проекте есть два значения конфигурации: выбор браузера и время ожидания. Здесь «browser» – это строка, а «wait_time» – целое число.

Чтение config-файла с pytest

Фикстуры – это лучший способ читать файлы конфигурации с помощью pytest. С их помощью можно читать config-файлы перед началом тестов, а затем вставлять значения в тесты или даже другие фикстуры. Добавьте следующую фикстуру в tests/test_web.py:

import json  @pytest.fixture(scope='session') def config():   with open('tests/config.json') as config_file:     data = json.load(config_file)   return data

Фикстура config читает и парсит файл tests/config.json в словарь с помощью модуля json. Жестко заданные пути к файлам – довольно распространенная практика. На самом же деле многие инструменты и системы автоматизации будут проверять наличие файлов в нескольких директориях или по шаблонам именования. Область действия фикстуры установлена в «session», поэтому фикстура запустится один раз за тестовую сессию. Нет необходимости читать один и тот же файл конфигурации каждый раз в новом тесте – это неэффективно!

Входные данные конфигурации нужны при инициализации WebDriver. Обновите фикстуру browser следующим образом:

@pytest.fixture def browser(config):   if config['browser'] == 'chrome':     driver = Chrome()   else:     raise Exception(f'"{config["browser"]}" is not a supported browser')    driver.implicitly_wait(config['wait_time'])   yield driver   driver.quit()

Фикстура browser теперь будет иметь зависимость от фикстуры config. Даже если config запустится один раз за тестовую сессию, browser все равно будет вызываться перед каждым тестом. Теперь у browser есть цепочка if-else, чтобы определить, какой тип WebDriver использовать. На данный момент поддерживается только Chrome, но скоро мы добавим еще несколько типов. Если браузер не определится, выпадет исключение. Неявное время ожидания также будет брать свое значение из файла конфигурации.

Поскольку browser все еще возвращает экземпляр WebDriver, тесты, которые его используют не нужно рефакторить! Давайте запустим тесты, чтобы удостовериться, что config-файл работает:

$ pipenv run python -m pytest tests/test_web.py  ============================= test session starts ============================== platform darwin -- Python 3.7.3, pytest-4.5.0, py-1.8.0, pluggy-0.12.0 rootdir: /Users/andylpk247/Programming/automation-panda/python-webui-testing collected 1 item                                                                 tests/test_web.py .                                                      [100%]  =========================== 1 passed in 5.00 seconds ===========================

Добавляем новые браузеры

Теперь, когда у нашего проекта есть config-файл, его можно использовать, чтобы поменять браузер. Давайте запустим тест на Mozilla Firefox вместо Google Chrome. Для этого загрузите и установите последнюю версию Firefox, а затем загрузит последнюю версию geckodriver (драйвер для Firefox). Убедитесь, что geckodriver также есть в system path.

Обновите код фикстуры browser для работы с Firefox:

from selenium.webdriver import Chrome, Firefox  @pytest.fixture def browser(config):   if config['browser'] == 'chrome':     driver = Chrome()   elif config['browser'] == 'firefox':     driver = Firefox()   else:     raise Exception(f'"{config["browser"]}" is not a supported browser')    driver.implicitly_wait(config['wait_time'])   yield driver   driver.quit()

Затем добавьте в config-файл опцию «firefox»:

{   "browser": "firefox",   "wait_time": 10 }

А теперь перезапустите тест, и увидите окно Firefox вместо Chrome!

Валидация

Несмотря на то, что config-файл работает, в логике его обработки есть существенный недостаток: данные не проверяются перед запуском тестов. Фикстура browser вызовет исключение, если браузер будет выбран некорректно, но произойдет это для каждого теста. Будет гораздо эффективнее, если исключение такого типа будет выпадать один раз за тестовую сессию. Помимо этого, тестирование упадет, если в config-файле будут отсутствовать ключи «browser» или «wait_time». Давайте это исправим.

Добавьте новую фикстуру для валидации выбора браузера:

@pytest.fixture(scope='session') def config_browser(config):   if 'browser' not in config:     raise Exception('The config file does not contain "browser"')   elif config['browser'] not in ['chrome', 'firefox']:     raise Exception(f'"{config["browser"]}" is not a supported browser')   return config['browser']

Фикстура config_browser зависит от фикстуры config. Также, как и у config, у нее scope = «session». Мы получим исключение, если в файле конфигурации не будет ключа «browser» или если выбранный браузер не поддерживается. Наконец, она возвращает выбранный браузер, чтобы тесты и другие фикстуры могли спокойно получить доступ к этому значению.

Дальше следующая фикстура для валидации времени ожидания:

@pytest.fixture(scope='session') def config_wait_time(config):   return config['wait_time'] if 'wait_time' in config else 10

Если в config-файле указано время ожидания, то фикстура config_wait_time вернет его. В противном случае, она вернет значение в 10 секунд по умолчанию.
Обновите фикстуру browser еще раз, чтобы использовать новые фикстуры для валидации:

@pytest.fixture def browser(config_browser, config_wait_time):   if config_browser == 'chrome':     driver = Chrome()   elif config_browser == 'firefox':     driver = Firefox()   else:     raise Exception(f'"{config_browser}" is not a supported browser')    driver.implicitly_wait(config_wait_time)   yield driver   driver.quit()

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

Запустите тест и убедитесь, что все работает:

$ pipenv run python -m pytest tests/test_web.py  ============================= test session starts ============================== platform darwin -- Python 3.7.3, pytest-4.5.0, py-1.8.0, pluggy-0.12.0 rootdir: /Users/andylpk247/Programming/automation-panda/python-webui-testing collected 1 item                                                                 tests/test_web.py .                                                      [100%]  =========================== 1 passed in 4.58 seconds ===========================

И это круто! Однако, чтобы валидация прошла более реалистично, нужно быть хитрыми. Давайте поменяем значение «browser» на «safari» — неподдерживаемый браузер.

$ pipenv run python -m pytest tests/test_web.py  ============================= test session starts ============================== platform darwin -- Python 3.7.3, pytest-4.5.0, py-1.8.0, pluggy-0.12.0 rootdir: /Users/andylpk247/Programming/automation-panda/python-webui-testing collected 1 item                                                                 tests/test_web.py E                                                      [100%]  ==================================== ERRORS ==================================== ________________ ERROR at setup of test_basic_duckduckgo_search ________________  config = {'browser': 'safari', 'wait_time': 10}      @pytest.fixture(scope='session')     def config_browser(config):       # Validate and return the browser choice from the config data       if 'browser' not in config:         raise Exception('The config file does not contain "browser"')       elif config['browser'] not in SUPPORTED_BROWSERS: >       raise Exception(f'"{config["browser"]}" is not a supported browser') E       Exception: "safari" is not a supported browser  tests/conftest.py:30: Exception =========================== 1 error in 0.09 seconds ============================

Вау! В ошибке было четко указано из-за чего она появилась. А теперь, что случится, если мы удалим выбор браузера из config-файла?

$ pipenv run python -m pytest tests/test_web.py  ============================= test session starts ============================== platform darwin -- Python 3.7.3, pytest-4.5.0, py-1.8.0, pluggy-0.12.0 rootdir: /Users/andylpk247/Programming/automation-panda/python-webui-testing collected 1 item                                                                 tests/test_web.py E                                                      [100%]  ==================================== ERRORS ==================================== ________________ ERROR at setup of test_basic_duckduckgo_search ________________  config = {'wait_time': 10}      @pytest.fixture(scope='session')     def config_browser(config):       # Validate and return the browser choice from the config data       if 'browser' not in config: >       raise Exception('The config file does not contain "browser"') E       Exception: The config file does not contain "browser"  tests/conftest.py:28: Exception =========================== 1 error in 0.10 seconds ============================

Отлично! Еще одно полезное сообщение об ошибке. Для последнего теста добавим выбор браузера, но уберем время ожидания:

$ pipenv run python -m pytest tests/test_web.py  ============================= test session starts ============================== platform darwin -- Python 3.7.3, pytest-4.5.0, py-1.8.0, pluggy-0.12.0 rootdir: /Users/andylpk247/Programming/automation-panda/python-webui-testing collected 1 item                                                                 tests/test_web.py .                                                      [100%]  =========================== 1 passed in 4.64 seconds ===========================

Тест должен отработать, поскольку время ожидания опционально. Что ж, изменения, которые мы внесли, пошли на пользу! Помните, что иногда вам нужно тестировать еще и свои тесты.

Итоговый тест

Есть еще две небольшие вещи, которые мы можем сделать, чтобы сделать код теста чище. Во-первых, давайте переместим наши веб-фикстуры в файл conftest.py, чтобы ими могли пользоваться все тесты, а не только тесты в tests/test_web.py. Во-вторых, давайте вытащим несколько буквенных значений в переменные модуля.

Создайте новый файл с именем tests/conftest.py со следующим кодом:

import json import pytest  from selenium.webdriver import Chrome, Firefox   CONFIG_PATH = 'tests/config.json' DEFAULT_WAIT_TIME = 10 SUPPORTED_BROWSERS = ['chrome', 'firefox']   @pytest.fixture(scope='session') def config():   # Read the JSON config file and returns it as a parsed dict   with open(CONFIG_PATH) as config_file:     data = json.load(config_file)   return data   @pytest.fixture(scope='session') def config_browser(config):   # Validate and return the browser choice from the config data   if 'browser' not in config:     raise Exception('The config file does not contain "browser"')   elif config['browser'] not in SUPPORTED_BROWSERS:     raise Exception(f'"{config["browser"]}" is not a supported browser')   return config['browser']   @pytest.fixture(scope='session') def config_wait_time(config):   # Validate and return the wait time from the config data   return config['wait_time'] if 'wait_time' in config else DEFAULT_WAIT_TIME   @pytest.fixture def browser(config_browser, config_wait_time):   # Initialize WebDriver   if config_browser == 'chrome':     driver = Chrome()   elif config_browser == 'firefox':     driver = Firefox()   else:     raise Exception(f'"{config_browser}" is not a supported browser')    # Wait implicitly for elements to be ready before attempting interactions   driver.implicitly_wait(config_wait_time)      # Return the driver object at the end of setup   yield driver      # For cleanup, quit the driver   driver.quit()

Полное содержание tests/test_web.py теперь должно быть проще и чище:

import pytest  from pages.result import DuckDuckGoResultPage from pages.search import DuckDuckGoSearchPage   def test_basic_duckduckgo_search(browser):   # Set up test case data   PHRASE = 'panda'    # Search for the phrase   search_page = DuckDuckGoSearchPage(browser)   search_page.load()   search_page.search(PHRASE)    # Verify that results appear   result_page = DuckDuckGoResultPage(browser)   assert result_page.link_div_count() > 0   assert result_page.phrase_result_count(PHRASE) > 0   assert result_page.search_input_value() == PHRASE

Ну вот, это уже в стиле Python!

Что дальше?

Итак, код примера нашего проекта по тестированию завершен. Вы можете использовать его в качестве базы для создания новых тестов. Финальный пример проекта вы также можете найти на GitHub. Однако то, что мы закончили писать код, не значит, что мы закончили обучение. В следующих статьях мы будем говорить о том, как вывести автоматизацию тестирования на Python на новый уровень!

FavoriteLoadingДобавить в избранное
Posted in Без рубрики

Добавить комментарий

Ваш e-mail не будет опубликован. Обязательные поля помечены *