https://habr.com/ru/company/otus/blog/480186/- Блог компании OTUS. Онлайн-образование
- Python
- Тестирование веб-сервисов
Перевод статьи подготовлен специально для студентов курса «Python QA Engineer».
Мы живем в эпоху, когда программное обеспечение очень быстро отправляется на рынок. Из-за этого процесс разработки становится очень стрессовым. Высокие темпы внедрения ПО и быстрая доставка выглядят как хорошая составляющая бизнес-модели, однако здесь возникает вопрос о том, как поставлять программное обеспечение надлежащего качества.
Почему нужны автоматизированные тесты
У автоматизированного тестирования есть множество плюсов, вот три основных:
Переиспользование: Нет необходимости писать каждый раз новые скрипты, даже при выпуске новой версии операционной системы, если только в этом не появится острая необходимость.
Надежность: Люди склонны совершать ошибки, а машины совершают их с меньшей вероятностью. А еще они работают быстрее при выполнении повторяющихся шагов/тестов, которые необходимо выполнять постоянно.
Работа 24/7: Вы можете запустить тестирование в любое время суток даже удаленно. Если запустить тестирование ночью, то оно будет выполняться даже пока вы спите.
Развитый полнофункциональный инструмент тестирования pytest на Python
В настоящее время существует множество фреймворков и инструментов для тестирования. Встречаются разные разновидности фреймворков, например, управляемые данными, управляемые ключевыми словами, гибридные, BDD и т.д. Вы можете выбрать тот, который лучше всего будет отвечать вашим требованиям.
Должен сказать, что Python и
pytest
занимают огромную нишу в этом вопросе. Python и связанные с ним инструменты широко используются, вероятно потому, что они более доступны для людей, имеющих мало опыта программирования по сравнению с другими языками.
Фреймворк
pytest
позволяет легко писать небольшие тесты, но также масштабируется для поддержки сложного функционального тестирования приложений и библиотек.
Несколько ключевых особенностей
pytest
:
- Автоматическое обнаружение тестовых модулей и функций;
- Эффективный CLI для улучшения контроля над тем, что вы хотите запустить или пропустить;
- Большая сторонняя экосистема плагинов;
- Фикстуры – разные типы, разные области применения;
- Работа с традиционной структурой модульного тестирования.
Автоматическое и конфигурируемое обнаружение тестов
По умолчанию
pytest
ожидает найти тесты в тех модулях Python, имена которых начинаются на
test_
или кончаются на
_test.py
. Также по умолчанию он ожидает, что имена тестовых функций будут начинаться с префикса
test_
. Однако этот протокол обнаружения тестов можно изменить, добавив собственную конфигурацию в один из файлов конфигурации
pytest
.
# content of pytest.ini
# Example 1: have pytest look for "check" instead of "test"
# can also be defined in tox.ini or setup.cfg file, although the section
# name in setup.cfg files should be "tool:pytest"
[pytest]
python_files = check_*.py
python_classes = Check
python_functions = *_check
Давайте посмотрим на очень простую тестовую функцию:
class CheckClass(object):
def one_check(self):
x = "this"
assert 'h' in x
def two_check(self):
x = "hello"
assert hasattr(x, 'check')
Вы что-нибудь заметили? Нет никаких
assertEqual
или
assertDictEqual
, просто доступный и понятный
assert
. Нет необходимости импортировать эти функции, чтобы просто сравнить два объекта. Assert – это то, что уже есть в Python и нет необходимости изобретать колесо.
Шаблонный код? Не переживайте, фикстуры спешат на помощь!
Посмотрите на тестовые функции, которые тестируют базовые операции в программе Wallet:
// test_wallet.py
from wallet import Wallet
def test_default_initial_amount():
wallet = Wallet()
assert wallet.balance == 0
wallet.close()
def test_setting_initial_amount():
wallet = Wallet(initial_amount=100)
assert wallet.balance == 100
wallet.close()
def test_wallet_add_cash():
wallet = Wallet(initial_amount=10)
wallet.add_cash(amount=90)
assert wallet.balance == 100
wallet.close()
def test_wallet_spend_cash():
wallet = Wallet(initial_amount=20)
wallet.spend_cash(amount=10)
assert wallet.balance == 10
wallet.close()
Кхм, интересно! Заметили? Здесь очень много шаблонного кода. Еще одна вещь, которую стоит заметить, заключается в том, что этот тест делает кое-что еще помимо тестирования функциональной части, например, создает Wallet и закрывает его с помощью
wallet.close()
.
Теперь давайте посмотрим на то, как можно избавиться от шаблонного кода с помощью фикстур
pytest
.
import pytest
from _pytest.fixtures import SubRequest
from wallet import Wallet
#==================== fixtures
@pytest.fixture
def wallet(request: SubRequest):
param = getattr(request, ‘param’, None)
if param:
prepared_wallet = Wallet(initial_amount=param[0])
else:
prepared_wallet = Wallet()
yield prepared_wallet
prepared_wallet.close()
#==================== tests
def test_default_initial_amount(wallet):
assert wallet.balance == 0
@pytest.mark.parametrize(‘wallet’, [(100,)], indirect=True)
def test_setting_initial_amount(wallet):
assert wallet.balance == 100
@pytest.mark.parametrize(‘wallet’, [(10,)], indirect=True)
def test_wallet_add_cash(wallet):
wallet.add_cash(amount=90)
assert wallet.balance == 100
@pytest.mark.parametrize(‘wallet’, [(20,)], indirect=True)
def test_wallet_spend_cash(wallet):
wallet.spend_cash(amount=10)
assert wallet.balance == 10
Красиво, не правда ли? Тестовые функции теперь компактные и делают ровно то, что должны делать. Настройка, инстанциирование и закрытие Wallet осуществляется с помощью фикстуры
wallet
. Фикстуры не только помогают писать переиспользуемый код, но и добавляют концепцию разделения данных. Если вы посмотрите внимательнее, то amount в
wallet
– это часть тестовых данных, предоставленная извне тестовой логикой, а не жестко закрепленная внутри функции.
@pytest.mark.parametrize(‘wallet’, [(10,)], indirect=True)
В более контролируемой среде у вас может быть файл с тестовыми данными, например
test-data.ini
в вашем репозитории или оболочке, которая сможет его прочитать, при этом ваша тестовая функция может вызывать различные оболочки для чтения тестовых данных.
Рекомендуется, однако, поместить все ваши фикстуры в специальный файл
conftest.py
. Это специальный файл в pytest, который позволяет тесту обнаруживать глобальные фикстуры.
Но у меня есть тестовые случаи, которые я хочу выполнять на различных наборах данных!
Не беспокойтесь, у
pytest
есть классная функция для параметризации вашей фикстуры. Давайте посмотрим на примере.
Предположим, что у вашего продукта CLI – интерфейс, который управляется локально. Кроме того, у вашего продукта есть множество параметров по умолчанию, которые устанавливаются при запуске, и вы хотите проверить все значения этих самых параметров.
Вы могли подумать о написании отдельного тест-кейса для каждого из этих параметров, но с
pytest
все намного проще!
@pytest.mark.parametrize(“setting_name, setting_value”, [(‘qdb_mem_usage’, ‘low’),
(‘report_crashes’, ‘yes’),
(‘stop_download_on_hang’, ‘no’),
(‘stop_download_on_disconnect’, ‘no’),
(‘reduce_connections_on_congestion’, ‘no’),
(‘global.max_web_users’, ‘1024’),
(‘global.max_downloads’, ‘5’),
(‘use_kernel_congestion_detection’, ‘no’),
(‘log_type’, ‘normal’),
(‘no_signature_check’, ‘no’),
(‘disable_xmlrpc’, ‘no’),
(‘disable_ntp’, ‘yes’),
(‘ssl_mode’, ‘tls_1_2’),])def test_settings_defaults(self, setting_name, setting_value):
assert product_shell.run_command(setting_name) == \
self.”The current value for \’{0}\’ is \’{1}\’.”.format(setting_name, setting_value), \
‘The {} default should be {}’.format(preference_name, preference_value)
Круто, не правда ли? Вы только что написали 13 тест-кейсов (каждый устанавливает разное значение
setting_value
), а в будущем, если вы добавите новый параметр в свой продукт, то все, что вам нужно будет сделать – это добавить еще один кортеж.
Как pytest интегрируется с тестированием пользовательского интерфейса с Selenium и тестами для API?
Что ж, у вашего продукта может быть несколько интерфейсов. CLI – как мы говорили выше. Аналогично GUI и API. Перед развертыванием вашего программного продукта важно протестировать их все. В корпоративном программном обеспечении, где несколько компонентов взаимосвязаны и зависят друг от друга, изменение в одной части может повлиять на все остальные.
Помните о том, что
pytest
– это просто фреймворк для облегчения тестирования, а не конкретный тип тестирования. То есть вы можете создавать тесты для GUI с помощью Selenium или, например, тесты для API с библиотекой
requests
из Python и запускать их с
pytest
.
Например, на высоком уровне это может быть проверка структуры репозитория.
Как вы видите на изображении выше, он дает хорошую возможность разделять компоненты:
apiobjects: хорошее место для создания оболочек для вызова конечных точек API. У вас может быть
BaseAPIObject
и производный класс, отвечающий вашим требованиям.
helpers: Можете добавить сюда свои вспомогательные методы.
lib: файлы библиотек, которые могут использоваться различными компонентами, например, вашими фикстурами в
conftest
,
pageobjects
и т.д.
pageobjects: шаблон архитектуры
PageObjects
можно использовать для создания классов различных страниц GUI. Мы используем
Webium, который является библиотекой реализаций шаблонов Page Object для Python.
suites: вы можете написать свои наборы pylint-проверок для кода, они помогут получить большую уверенность в качестве вашего кода.
tests: вы можете каталогизировать тесты на основе ваших предпочтений. Это позволит легко управлять и обозревать ваши тесты.
Я привел это просто для справки, структура репозитория и зависимости могут быть организованы с учетом ваших личных потребностей.
У меня много тест-кейсов, и я хочу, чтобы они выполнялись параллельно
У вас может быть множество тест-кейсов в вашем наборе, и случается, что нужно запустить их параллельно и сократить общее время выполнения тестирования.
Pytest предлагает удивительный плагин для параллельного запуска тестов, который называется
pytest-xdist
, который добавляет к базовому pytest несколько уникальных режимов выполнения. Установите этот плагин с помощью
pip
.
pip install pytest-xdist
Давайте посмотрим, как он работает на примере.
У меня есть репозиторий автоматизированного тестирования CloudApp для моих GUI тестов на Selenium. Помимо этого, он постоянно растет и пополняется новыми тестами и теперь в нем лежит сотня тестов. То, что я хочу сделать – это запустить их параллельно и сократить общее время выполнения тестирования.
В терминале просто введите
pytest
в корневой папке проекта/папке с тестами. Это позволит выполнить все тесты.
pytest -s -v -n=2
pytest-xdist
запустит все тесты параллельно!
Таким способом вы также сможете параллельно запустить несколько браузеров.
Отчеты
Pytest поставляется со встроенной поддержкой создания файлов с результатами тестирования, которые можно открыть с помощью Jenkins, Bamboo или других серверов непрерывной интеграции. Используйте следующее:
pytest test/file/path — junitxml=path
Это поможет сгенерировать отличный XML-файл, который можно открыть множеством парсеров.
Заключение
Популярность Pytest растет с каждым годом. Кроме того, у него есть мощная поддержка сообщества, что позволяет получить доступ к множеству расширений, например
pytest-django, который поможет писать тесты для веб-приложений на Django. Помните, что pytest поддерживает выполнение тест-кейсов
unittest
, поэтому если вы используете
unittest
, pytest стоит рассмотреть более детально.
Источники
На этом все. До встречи на
курсе!