Автоматизированное тестирование с Pytest

Автор оригинала: Harshil Modi
  • Перевод
Перевод статьи подготовлен специально для студентов курса «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 стоит рассмотреть более детально.

Источники



На этом все. До встречи на курсе!
OTUS. Онлайн-образование
420,97
Цифровые навыки от ведущих экспертов
Поделиться публикацией

Похожие публикации

Комментарии 2

    –3
    Это только мне кажется, что:

    • в примере без фикстур можно или нужно использовать традиционные setUp и tearDown.
    • Фикстуры это лишняя абстракция, которая только загромождает голову и код.

    Глядя на тесты, мне хочется видеть как работает код без того, чтобы лазить в 10 разных мест. В последнюю очередь я думаю о том, чтобы код метода текста содержал на две строки меньше.
    Или я один такой и неправ?
      0
      в примере без фикстур

      Фикстуры — это аналог setUp и tearDown. Поэтому там специально приведен пример без использования любого способа инициализации начальных данных.


      Фикстуры это лишняя абстракция

      Это еще один способ провести инициализацию. Здесь пример достаточно тривиален и действительно разницы особо не видно.


      Но в сложных случаях фикстуры могут быть удобнее, чем setUp и tearDown. Но в pytest можно использовать оба подхода, где какой удобнее будет. В этом еще один плюс этого фреймворка.


      Глядя на тесты, мне хочется видеть как работает код без того, чтобы лазить в 10 разных мест.

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

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

    Самое читаемое