Разделы портала

Онлайн-тренинги

.
Прелести Pytest: Pytest-BDD
06.04.2020 00:00

Автор: Джош Грант (Josh Grant)
Оригинал статьи
Перевод: Ольга Алифанова

Продолжая исследовать Pytest и его замечательные возможности, хочу рассказать о плагине pytest, который по сути представляет из себя полноценный инструмент тест-автоматизации. Это pytest-bdd.

Pytest-bdd – это вариация более распространенных фреймворков вроде Cucumber или Behave, который сам по себе – по сути Python Cucumber. BDD – популярный подход, распространенный среди команд – как работающих с автоматизированным тестированием впервые, так и опытных, ищущих зрелую методологию для внедрения. Ключевая особенность BDD – это создание фича-файлов с применением специального неспециализированного синтаксиса, Gherkin. Это позволяет описывать фичи вначале в таком формате:

Фича: добавление товаров в корзину.

Feature: Adding Items to Shopping Cart

Сценарий: добавить товар в пустую корзину.

Scenario: Add Item to Empty Cart

Если: я на странице товаров

Given: I am on the items list page

Когда: я добавляю один товар в мою корзину.

When: I add one item to my cart

Тогда: я должен увидеть один товар в корзине.

Then: I should see one item in my cart

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

Давайте посмотрим, как с этим справляется pytest-bdd. Начнем с фича-файла с использованием Gherkin. Не отходя от тематики онлайн-магазина, назовем фичу store_login.feature. Она будет описывать, как работают два специфических потока авторизации, валидный и невалидный. Она выглядит так:

Фича: авторизация в мой магазин.

Feature: Login to My Store

Основные потоки авторизации в my-store.ca

Basic login flows to my-store.ca



Сценарий: валидная авторизация.

Scenario: Valid Login

Если: я на странице авторизации

Given I visit the login page

Когда: я авторизуюсь с валидными данными.

When I login with valid credentials

Тогда: я должен перейти в каталог.

Then I should be on the inventory page



Сценарий: невалидная авторизация.

Scenario: Invalid Login

Если: я на странице авторизации

Given I visit the login page

Когда: я авторизуюсь с невалидными данными.

When I login with invalid credentials

Тогда: я должен получить сообщение об ошибке.

Then I should see an error message

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

Теперь давайте посмотрим на внедрение этих шагов. Тест-разработчик теперь может создать файл test_store_login.py, который может выглядеть так:

import pytest
from pytest_bdd import scenario, given, when, then
from selenium import webdriver
 
 
@pytest.fixture
def browser(request):
br = webdriver.Firefox()
 
yield br
 
br.quit()
 
 
@scenario("store_login.feature", "Valid Login")
def test_valid_login():
pass
 
@scenario("store_login.feature", "Valid Login")
def test_invalid_login():
pass
 
@given("I visit the login page")
def visit_page(browser):
browser.get("https://my-store.ca")
 
@when("I login with valid credentials")
def login_valid(browser):
browser.find_element_by_id("user-name").send_keys('valid_user')
browser.find_element_by_id("password").send_keys('good_password')
browser.find_element_by_class_name("btn_action").click()
 
@when("I login with invalid credentials")
def login_valid(browser):
browser.find_element_by_id("user-name").send_keys('nope')
browser.find_element_by_id("password").send_keys('still_nope')
browser.find_element_by_class_name("btn_action").click()
 
@then("I should be on the inventory page")
def on_inventory_page(browser):
assert 'inventory' in browser.current_url
 
@then("I should see an error message")
def on_inventory_page(browser):
assert browser.find_element_by_class_name('error-button').present
 

Это демонстрирует, как, продолжая пользоваться известными конструкциями Pytest (фикстурами, приписыванием test_ к файлам для исполнения), мы можем воспользоваться BDD и шагами Gherkin. Тут можно провести дополнительную работу, например, использовать паттерн page object или совместить шаги, но я хочу сконцентрироваться на BDD-аспекте этого сценария.

Для выполнения этого теста мы можем вызвать

pytest test_store_login.py

и увидеть результат. Pytest-bdd также сгенерирует ряд полезных отчетов в стиле Cucumber в дополнение к стандартной отчетности Pytest. Доступны также замещения и регулярные выражения в шагах Gherkin.

Наконец-то BDD и Pytest вместе:

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

И тут-то pytest-bdd предстает во всем блеске. Так как pytest-bdd использует pytest, то возможно:

  • Писать тест-фреймворки, смешивающие BDD и обычные тесты Pytest, и
  • Удалить использование BDD при рефакторинге в обычные тесты Pytest.

Допустим, что мы хотим убрать инструментарий BDD и пользоваться функциональностью pytest. Мы можем переписать тест на валидную авторизацию, а невалидная будет продолжать использовать pytest-bdd. После некоторой копипасты файл test_store_login.py будет выглядеть как-то так:

import pytest
from pytest_bdd import scenario, given, when, then
from selenium import webdriver
 
 
@pytest.fixture
def browser(request):
br = webdriver.Firefox()
 
yield br
 
br.quit()
 
 
def test_valid_login(browser):
browser.get("https://my-store.ca")
 
browser.find_element_by_id("user-name").send_keys('valid_user')
browser.find_element_by_id("password").send_keys('good_password')
browser.find_element_by_class_name("btn_action").click()
 
assert 'inventory' in browser.current_url
 
@scenario("store_login.feature", "Valid Login")
def test_invalid_login():
pass
 
@given("I visit the login page")
def visit_page(browser):
browser.get("https://my-store.ca")
 
 
@when("I login with invalid credentials")
def login_valid(browser):
browser.find_element_by_id("user-name").send_keys('nope')
browser.find_element_by_id("password").send_keys('still_nope')
browser.find_element_by_class_name("btn_action").click()
 
@then("I should see an error message")
def on_inventory_page(browser):
assert browser.find_element_by_class_name('error-button').present

При использовании той же команды, что и ранее, pytest test_store_login.py, оба теста будут запущены, отчетность Pytest будет такой же, как прежде, и для невалидной авторизации будет доступна дополнительная отчетность pytest-bdd. Если все пойдет хорошо, тот же процесс можно применить к тесту на невалидную авторизацию. Заметьте, что нам не нужно манипулировать фича-файлом: его можно сохранить или убрать, и на выполнение тестов он не повлияет.

Pytest-bdd – полезный плагин для старта работы с BDD и Python, и при этом он не ограничивает команду использованием только BDD-подходов к автоматизации. Это киллер-фича, которой нет у множества BDD-фреймворков, и это еще одна отличная причина использовать Pytest, неважно, с pytest-bdd или без него.