Что такое BDD-тестирование? Фреймворк разработки, основанный на поведении.

⚡ Умное резюме

BDD-тестирование описывает поведение приложения простым языком, основанным на принципе «Дано, когда, тогда», что позволяет аналитикам, разработчикам и тестировщикам использовать единую спецификацию. В этом пошаговом руководстве этот подход применяется к тестированию REST API с помощью Behave. Python Структура проекта, включающая настройку, файлы функций, реализацию шагов, выполнение и составление отчетов.

  • 🗣️ Основной принцип: Разработка, основанная на поведении (Behavior-Driven Development), расширяет разработку, основанную на тестировании (Test-Driven Development), выражая каждый сценарий на естественном языке, который могут прочитать и утвердить люди, не являющиеся программистами.
  • 🧱 Структура сценария: Given устанавливает предварительные условия, When выполняет действие, Then утверждает результат, а And или But наследуют декоратор предыдущего шага.
  • 🌐 REST-покрытие: В примере демонстрируется полное CRUD-оперирование с использованием методов POST, GET, PUT и DELETE к общедоступному сервису jsonplaceholder posts.
  • 🐍 Настройка платформы: Behave устанавливается через pip на Python 3. Ожидается наличие каталога features, содержащего файлы функций, а также пакета steps, содержащего реализации.
  • 🔗 Привязка шагов: Декораторы, такие как @given, @when и @then, соответствуют тексту описания функции, а фигурные скобки-заполнители передают значения из сценария в функцию. Python функции.
  • ▶ ️ Исполнение: Функция форматирования выводит в консоли результат каждого шага, обозначенный цветом и определяющий, пройден ли тест или нет.
  • 📊 Отчетность: Инструмент форматирования allure-behave выводит результаты в машиночитаемом формате, которые командная строка Allure преобразует в HTML-отчет, доступный для просмотра.

Что такое BDD-тестирование?

Что такое BDD-тестирование (разработка, основанная на поведении)?

BDD (Разработка, основанная на поведении) Тестирование BDD — это метод гибкой разработки программного обеспечения, являющийся расширением TDD, то есть разработки через тестирование. В BDD тестовые примеры пишутся на естественном языке, который могут прочитать даже непрограммисты.

Как работает BDD-тестирование?

Предположим, вам поручено создать модуль для перевода средств в приложении интернет-банкинга.

Существует несколько способов это проверить:

  1. Перевод средств должен быть осуществлен, если на исходном счете достаточно средств.
  2. Перевод средств должен быть осуществлен, если данные о пункте назначения верны.
  3. Перевод средств должен состояться, если введенный пользователем пароль транзакции / код RSA / код аутентификации безопасности верен.
  4. Перевод средств должен осуществляться даже в праздничный день.
  5. Перевод средств должен произойти в будущую дату, установленную владельцем счета.

Сценарий тестирования Ситуация становится более сложной и запутанной по мере учета дополнительных функций, таких как перевод суммы X на интервал Y дней или месяцев, остановка.ping запланированный перевод, когда общая сумма достигнет Z, и так далее.

Как правило, разработчики склонны сначала разрабатывать функциональные возможности, а тестовый код писать позже. Как видно из приведенного выше примера, Тестовый кейс Разработка здесь — сложный процесс, поэтому разработчик будет откладывать её. Тестирование до момента выпуска, после чего проводится быстрое, но неэффективное тестирование.

Для решения этой проблемы была разработана концепция разработки, управляемой поведением (Behavior Driven Development, BDD). Она значительно упрощает весь процесс тестирования для разработчика.

В BDD все, что вы пишете, должно войти в Дано-Когда-Тогда Шаги. Рассмотрим тот же пример, что и выше, в рамках BDD:

Given that a fund transfer module in net banking application has been developed
And I am accessing it with proper authentication

When I shall transfer with enough balance in my source account
Or I shall transfer on a Bank Holiday
Or I shall transfer on a future date
And destination a/c details are correct
And transaction password / rsa code / security authentication for the transaction is correct
And press or click send button

Then amount must be transferred
And the event will be logged in log file

Эта форма проста в написании, чтении и понимании. Она охватывает все возможные тестовые случаи для модуля перевода средств и может быть быстро изменена для включения большего количества. Она также читается как постоянно обновляемая документация для модуля. Поскольку BDD вырос из TDD, стоит разобраться в различиях между ними, прежде чем переходить к инструментам.

BDD против TDD: ключевые различия

Оба подхода основаны на тестировании и сокращают циклы обратной связи. Различаются они в том, кто пишет тесты, какой язык они используют и какой уровень приложения они описывают.

Аспект TDD (разработка через тестирование) BDD (разработка, управляемая поведением)
Фокус Как работает модуль кода внутри. Как система ведет себя с точки зрения пользователя.
Язык Утверждения языка программирования Сценарии «Дано, когда, тогда» на естественном языке
Основной автор Разработчик Бизнес-аналитик, владелец продукта, тестировщик и разработчик вместе.
Readable непрограммистами Нет Да
Типичная область применения Уровень подразделения Функционал, API и уровень приемлемости
Общие инструменты JUnit, pytest, NUnit Вести себя, CucumberSpecFlow, JBehave

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

Что такое тестирование REST API?

Поскольку REST стал популярным стилем построения API, автоматизация тестовых сценариев для REST API наряду с тестовыми сценариями для пользовательского интерфейса стала одинаково важной. REST API тестирование Это включает тестирование операций CRUD (создание-чтение-обновление-удаление) с использованием методов POST, GET, PUT и DELETE соответственно.

Что такое поведение?

Поведение – одно из популярных Python Тестовые фреймворки BDD. Вот как работает Behave:

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

После того, как роли каждого файла определены, можно приступать к установке фреймворка.

Настройка платформы тестирования BDD Windows

Установка:

  • Скачайте и установите Python 3 из https://www.python.org/
  • Выполните следующую команду в командной строке, чтобы установить Behave.
  • pip install behave
  • ИДЕ: PyCharm Здесь используется Community Edition — https://www.jetbrains.com/pycharm/download/

Настройка проекта:

  • Создание нового проекта
  • Создайте следующую структуру каталогов.

Настройка проекта

На скриншоте выше показана структура, которую ожидает Behave: a функции каталог, содержащий файлы функций, и вложенный шага каталог, содержащий Python реализации. Behave обнаруживает оба варианта по имени, поэтому имена папок должны точно совпадать.

Функциональные файлы:

Теперь давайте создадим файл с описанием функций. Sample_REST_API_Testing.feature, при этом особенностью являются операции CRUD (операции чтения-выполнения) над сервисом 'posts'.

В этом примере используется https://jsonplaceholder.typicode.com/ Сервис posts sample REST, бесплатный фиктивный API, который принимает запросы на запись и возвращает реалистичные ответы без сохранения изменений.

Пример сценария POST

Scenario: POST post example -> creating a new post item using the 'posts' service
Given I set post posts API endpoint          -> prerequisite: sets the URL of the posts service
When I set HEADER param request content type as "application/json"
And set request body
And send POST HTTP request                    -> the actual test step
Then I receive valid HTTP response code 201
And Response body "POST" is non-empty       -> verification of the response body

Аналогичным образом, остальные сценарии можно записать следующим образом:

Настройка проекта

Sample_REST_API_Testing.feature

Feature: Test CRUD methods in Sample REST API testing framework

Background:
    Given I set sample REST API url

Scenario: POST post example
    Given I Set POST posts api endpoint
    When I Set HEADER param request content type as "application/json"
    And Set request Body
    And Send a POST HTTP request
    Then I receive valid HTTP response code 201
    And Response BODY "POST" is non-empty

Scenario: GET posts example
    Given I Set GET posts api endpoint "1"
    When I Set HEADER param request content type as "application/json"
    And Send GET HTTP request
    Then I receive valid HTTP response code 200 for "GET"
    And Response BODY "GET" is non-empty

Scenario: UPDATE posts example
    Given I Set PUT posts api endpoint for "1"
    When I Set Update request Body
    And Send PUT HTTP request
    Then I receive valid HTTP response code 200 for "PUT"
    And Response BODY "PUT" is non-empty

Scenario: DELETE posts example
    Given I Set DELETE posts api endpoint for "1"
    When I Send DELETE HTTP request
    Then I receive valid HTTP response code 200 for "DELETE"

Шаги реализации

Теперь, что касается этапов реализации функций, используемых в описанных выше сценариях, вы можете написать их реализацию на языке программирования. Python файлы находятся в каталоге «steps».

Фреймворк Behave определяет функцию шага, сопоставляя декораторы с предикатом файла объектов. Например, Данный Предикат в сценарии файла функций ищет ступенчатую функцию, несущую @given декоратор. Аналогичное сочетание происходит и для После появления и ЗатемВ случае операторов 'But' и 'And' функция шага использует тот же декоратор, что и предыдущий шаг. Например, если 'And' следует за оператором Given, то соответствующий декоратор функции шага — это @given.

Например, шаг When для POST-запроса можно реализовать следующим образом. Обратите внимание, как "application/json" Передаётся из файла объектов в заключенный в скобки заполнитель — это называется параметризацией.

# Decorator: the braced name captures a value from the feature file
@when(u'I Set HEADER param request content type as "{header_content_type}"')
def step_impl(context, header_content_type):
    # Step implementation: set the content type on the request header
    request_headers['Content-Type'] = header_content_type

Аналогичным образом, осуществляется реализация остальных шагов на данном этапе. Python файл будет выглядеть так:

Шаги реализации

sample_step_implementation.py

from behave import given, when, then, step
import requests

api_endpoints = {}
request_headers = {}
response_codes = {}
response_texts = {}
request_bodies = {}
api_url = None

@given(u'I set sample REST API url')
def step_impl(context):
    global api_url
    api_url = 'https://jsonplaceholder.typicode.com'

# START POST Scenario
@given(u'I Set POST posts api endpoint')
def step_impl(context):
    api_endpoints['POST_URL'] = api_url + '/posts'
    print('url :' + api_endpoints['POST_URL'])

@when(u'I Set HEADER param request content type as "{header_content_type}"')
def step_impl(context, header_content_type):
    request_headers['Content-Type'] = header_content_type

# "And" or "But" steps are renamed by behave to match their preceding step
@when(u'Set request Body')
def step_impl(context):
    request_bodies['POST'] = {"title": "foo", "body": "bar", "userId": "1"}

@when(u'Send POST HTTP request')
def step_impl(context):
    # send the request and save the response object
    response = requests.post(url=api_endpoints['POST_URL'],
                             json=request_bodies['POST'],
                             headers=request_headers)
    response_texts['POST'] = response.text
    print("post response :" + response.text)
    response_codes['POST'] = response.status_code

@then(u'I receive valid HTTP response code 201')
def step_impl(context):
    print('Post rep code ;' + str(response_codes['POST']))
    assert response_codes['POST'] == 201
# END POST Scenario

# START GET Scenario
@given(u'I Set GET posts api endpoint "{id}"')
def step_impl(context, id):
    api_endpoints['GET_URL'] = api_url + '/posts/' + id
    print('url :' + api_endpoints['GET_URL'])

@when(u'Send GET HTTP request')
def step_impl(context):
    response = requests.get(url=api_endpoints['GET_URL'], headers=request_headers)
    response_texts['GET'] = response.text
    response_codes['GET'] = response.status_code

@then(u'I receive valid HTTP response code 200 for "{request_name}"')
def step_impl(context, request_name):
    print('Get rep code for ' + request_name + ':' + str(response_codes[request_name]))
    assert response_codes[request_name] == 200

@then(u'Response BODY "{request_name}" is non-empty')
def step_impl(context, request_name):
    print('request_name: ' + request_name)
    print(response_texts)
    assert response_texts[request_name] is not None
# END GET Scenario

# START PUT/UPDATE
@given(u'I Set PUT posts api endpoint for "{id}"')
def step_impl(context, id):
    api_endpoints['PUT_URL'] = api_url + '/posts/' + id
    print('url :' + api_endpoints['PUT_URL'])

@when(u'I Set Update request Body')
def step_impl(context):
    request_bodies['PUT'] = {"title": "foo", "body": "bar",
                             "userId": "1", "id": "1"}

@when(u'Send PUT HTTP request')
def step_impl(context):
    response = requests.put(url=api_endpoints['PUT_URL'],
                            json=request_bodies['PUT'],
                            headers=request_headers)
    response_texts['PUT'] = response.text
    print("update response :" + response.text)
    response_codes['PUT'] = response.status_code
# END PUT/UPDATE

# START DELETE
@given(u'I Set DELETE posts api endpoint for "{id}"')
def step_impl(context, id):
    api_endpoints['DELETE_URL'] = api_url + '/posts/' + id
    print('url :' + api_endpoints['DELETE_URL'])

@when(u'I Send DELETE HTTP request')
def step_impl(context):
    response = requests.delete(url=api_endpoints['DELETE_URL'])
    response_texts['DELETE'] = response.text
    print("DELETE response :" + response.text)
    response_codes['DELETE'] = response.status_code
# END DELETE

⚠️ Примечание: Сравнение кодов состояния с == , а не is имеет значение. is Оператор проверяет идентичность объекта, а не его равенство, и Python 3.8 и последующие события вызывают SyntaxWarning: “is” with a literal для этого шаблона. Любое утверждение, записанное как assert code is 201 следует переписать как assert code == 201.

Запуск тестов

Разработка тестового скрипта завершена, давайте запустим тесты. Для запуска файла с описанием функций выполните следующую команду в командной строке:

:: Run a single feature file with the pretty formatter
behave -f pretty features\feature_files_folder\Sample_REST_API_Testing.feature

:: Run every feature in the project
behave

Результаты выполнения теста отображаются следующим образом:

Запуск тестов

Отображение отчета на консоли

Вывод в консоль удобен на этапе разработки, но заинтересованные стороны обычно предпочитают читаемый отчет, который предоставляет Allure.

Согласно отчетам

Сначала установите программу форматирования Allure Behave и инструмент командной строки Allure. Программа форматирования представляет собой... Python пакет; инструмент командной строки загружается отдельно, описание см. в соответствующем разделе. Документация отчета Allure.

:: 1. Install the formatter
pip install allure-behave

:: 2. Run the tests and write raw results to a folder
behave -f allure_behave.formatter:AllureFormatter -o reports/allure-results features/

:: 3. Render and open the HTML report
allure serve reports/allure-results

Это позволяет сформировать отчет о результатах тестирования в наглядном и информативном формате, подобном этому:

Согласно отчетам

Отчет об испытаниях в формате HTML

Отчет об испытаниях в формате HTML

Отчет о тестировании, отображающий результат отдельного сценария

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

Лучшие практики написания функциональных файлов Behave

Эффективность набора инструментов Behave быстро снижается, когда сценарии описывают клики и полезную нагрузку вместо поведения. Приведенные ниже методы позволяют сохранять читаемость файлов с описанием функций для заинтересованных сторон из бизнеса, одновременно обеспечивая...ping Python Этот слой удобен для сопровождения инженерами.

  1. Описывайте поведение, а не его реализацию. Пишите «При условии, что у клиента достаточный баланс», а не «При условии, что значение в столбце «Баланс» равно 500». В файле описания функций указано, что делает система; в файле описания шага указано, как это проверяется.
  2. Для каждого сценария используйте одно действие. Сценарий, в котором указывается код состояния, тело ответа и строка в базе данных, на самом деле представляет собой три сценария. Разделение их позволяет напрямую указывать на причину сбоев.
  3. Перевести общие настройки в фоновый режим. В приведенном выше примере используется Background: Given I set sample REST API url так что каждый сценарий наследует базовый. URL не повторяя его.
  4. Вместо дублирования используйте параметры. Заполнители с скобками, такие как "{request_name}" Пусть одна функция шага обслуживает утверждения GET, PUT и DELETE, поэтому в примере требуется гораздо меньше определений шагов, чем в сценариях.
  5. Используйте структуру сценария для отображения различных вариантов данных. Когда необходимо доказать одинаковое поведение для нескольких входных данных, Examples: Таблица понятнее, чем скопированные сценарии.
  6. Избегайте зависимостей между сценариями. Behave не гарантирует, что сценарий POST будет выполняться раньше сценария GET в каждой конфигурации. Каждый сценарий должен создавать необходимое ему состояние.
  7. Замените глобальные переменные на уровне модуля на контекст. В приведенном примере для краткости конечные точки и ответы хранятся в словарях модулей. В производственных средах их следует хранить в Behave. context Таким образом, состояние объекта корректно сбрасывается между сценариями.
  8. Пометьте сценарии для выборочных запусков. Теги, такие как @smoke or @regression позволять behave --tags=@smokeчто обеспечивает быструю обратную связь в процессе обработки данных.

Применение этих принципов с самого первого файла описания функций позволяет сохранить ценность набора инструментов BDD далеко за пределами первоначальных примеров CRUD. Команды, расширяющие этот подход, часто сочетают его с Cucumber для проектов на JVM или Postman для ознакомительных проверок API.

Часто задаваемые вопросы (FAQ)

Оба приложения читают файлы функций Gherkin. Cucumber привязывает шаги к Java, Руби или JavaScript, в то время как Behave привязывает их к PythonВыберите тот, который соответствует вашему стеку приложений, чтобы определения шагов повторно использовали существующие тестовые утилиты.

Да. Функция Behave возвращает ненулевой код завершения при возникновении любой ошибки, поэтому JenkinsСборка может автоматически завершиться с ошибкой, например, с помощью GitLab CI или GitHub Actions. Опубликуйте папку с результатами Allure в качестве артефакта сборки для составления отчетов.

Да. AI Помощники составляют сценарии "Дано, когда, тогда" на основе пользовательских историй или спецификации OpenAPI. RevПросмотрите результаты для отсутствующих отрицательных случаев и бизнес-правил, поскольку сгенерированные сценарии часто охватывают только счастливый сценарий.

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

Получите токен один раз в хуке before_all файла environment.py, сохраните его в объекте контекста и добавьте в заголовок Authorization на каждом этапе запроса. Храните учетные данные в переменных окружения, никогда не в файлах функций.

Подведем итог этой публикации следующим образом: