Що таке BDD-тестування? Фреймворк для поведінково-орієнтованої розробки

⚡ Розумний підсумок

Тестування BDD описує поведінку застосунку простою мовою «Дано-Коли-Тоді», щоб аналітики, розробники та тестувальники мали спільну специфікацію. У цьому покроковому посібнику цей підхід застосовується до тестування REST API за допомогою Behave, Python фреймворк, що охоплює налаштування, файли функцій, покрокову реалізацію, виконання та звітність.

  • 🗣️ Основний принцип: Розробка, орієнтована на поведінку, розширює розробку, орієнтовану на тестування, виражаючи кожен сценарій природною мовою, яку можуть прочитати та схвалити навіть непрограмісти.
  • 🧱 Структура сценарію: Вказано встановлює передумови, Коли виконується дія, а Потім стверджує результат, тоді як And або But успадковують декоратор попереднього кроку.
  • 🌐 Покриття REST: У цьому прикладі демонструється повна поведінка CRUD через POST, GET, PUT та DELETE для публічного сервісу публікацій jsonplaceholder.
  • 🐍 Налаштування фреймворку: Behave встановлюється через pip Python 3 та очікує каталог features, що містить файли функцій, а також пакет steps, що містить реалізації.
  • 🔗 Покрокове зв'язування: Декоратори, такі як @given, @when та @then, відповідають тексту функції, а заповнювачі у дужках передають значення зі сценарію в Python функції.
  • ▶ ゚ Виконання: Гарний форматтер друкує результат успішного або неуспішного виконання з кольоровим кодуванням для кожного кроку в консолі.
  • 📊 звітність: Форматувальник allure-behave записує машинно-читабельні результати, які командний рядок Allure відтворює як переглядаємий HTML-звіт.

Що таке BDD-тестування

Що таке BDD (Behavior Driven Development) тестування?

BDD (Behavior-driven development) тестування — це метод гнучкої розробки програмного забезпечення та розширення TDD, тобто розробки через тестування (TDD). У BDD тестові випадки пишуться природною мовою, яку можуть читати навіть непрограмісти.

Як працює тестування BDD?

Уявіть, що вам доручено створити модуль переказу коштів у застосунку Net Banking.

Існує кілька способів його протестувати:

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

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

Загальна тенденція розробників полягає в тому, щоб розробляти функції, а тестовий код писати пізніше. Як видно з вищезазначеного випадку, Тестовий випадок розробка тут складна, тому розробник відкладе Тестування до випуску, після чого проводиться швидке, але неефективне тестування.

Щоб подолати цю проблему, була розроблена розробка на основі поведінки (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 (Поведінковий розвиток)
Focus Як працює одиниця коду внутрішньо Як система поводиться з точки зору користувача
Language Твердження мови програмування Сценарії «Дано-Коли-Тоді» природною мовою
Основний автор Розробник Бізнес-аналітик, власник продукту, тестер і розробник разом
Readable непрограмістами Немає Так
Типова сфера застосування Рівень одиниці Функція, API та рівень прийнятності
Звичайні інструменти JUnit, pytest, NUnit Поводься, Cucumber, SpecFlow, JBehave

Вони радше доповнюють один одного, ніж конкурують. Команди зазвичай продовжують TDD для проектування на рівні одиниць та додайте сценарії BDD зверху, щоб описати поведінку, яку фактично підписують зацікавлені сторони. Оскільки кінцеві точки REST розташовані саме на цьому рівні прийняття, вони природно підходять для BDD.

Що таке тестування REST API?

Оскільки REST став популярним стилем для створення API, автоматизація тестових випадків REST API разом із тестовими випадками інтерфейсу користувача стала не менш важливою. REST Тестування API включає тестування дій CRUD (Create-Read-Update-Delete) за допомогою методів POST, GET, PUT та DELETE відповідно.

Що таке Behave?

Behave є одним із популярних Python Тестові фреймворки BDD. Ось як функціонує Behave:

  • Файли функцій пишуться вашим бізнес-аналітиком, спонсором або тим, хто є власником сценаріїв поведінки. Файл функцій використовує формат природної мови, що описує функцію або її частину з репрезентативними прикладами очікуваних результатів.
  • Ці кроки сценарію зіставлені з реалізаціями кроків, написаними на Python.
  • За потреби, елементи керування середовищем запускають код до та після кроків, сценаріїв, функцій або всього запуску.

Після визначення ролей кожного файлу, фреймворк можна встановити.

Налаштування BDD Testing Framework Behave on Windows

Установка:

  • Завантажити та встановити Python 3 від https://www.python.org/
  • Виконайте таку команду в командному рядку, щоб встановити Behave
  • pip install behave
  • SDI: PyCharm Тут використовується Community Edition — https://www.jetbrains.com/pycharm/download/

Налаштування проекту:

  • Створіть новий проект
  • Створіть наступну структуру каталогів

Налаштування проекту

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

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

Тепер давайте створимо файл функцій Зразок_REST_API_Testing.feature, причому особливістю є операції CRUD у сервісі «posts».

У цьому прикладі використовується https://jsonplaceholder.typicode.com/ Зразок 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

Аналогічно, ви можете записати решту сценаріїв наступним чином:

Налаштування проекту

Зразок_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 ідентифікує функцію step, зіставляючи декоратори з предикатом файлу ознак. Наприклад, при Предикат у сценарії файлу ознак шукає ступінчасту функцію, що містить @given декоратор. Таке саме зіставлення відбувається для Коли та ПотімУ випадку 'But' та 'And', функція step використовує той самий декоратор, що й її попередній крок. Наприклад, якщо 'And' йде після Given, відповідний декоратор функції step — @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 та пізніших версій піднімають Синтаксічне попередження: «є» з літералом для цього шаблону. Будь-яке твердження, записане як 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.

Поширені запитання

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

Так. Behave повертає ненульовий код виходу, коли будь-який сценарій завершується невдачею, тому Jenkins, GitLab CI або GitHub Actions можуть автоматично завершити збірку. Опублікуйте папку результатів Allure як артефакт збірки для звітності.

Так. AI Асистенти створюють сценарії «Дано-Коли-Тоді» на основі історій користувачів або специфікації OpenAPI. Revпереглянути вихідні дані для відсутніх негативних випадків та бізнес-правил, оскільки згенеровані сценарії часто охоплюють лише щасливий шлях.

Інструменти штучного інтелекту виявляють дублікати або майже дублікати сценаріїв, пропонують повторно використовувані параметризовані кроки та кластеризують нестабільні збої за ймовірною причиною. Це зменшує зусилля на ручний аудит, оскільки файли функцій множаться в різних командах.

Отримайте токен один раз у гачку environment.py before_all, збережіть його в об'єкті контексту та додайте його як заголовок авторизації на кожному кроці запиту. Зберігайте облікові дані у змінних середовища, а не у файлах функцій.

Підсумуйте цей пост за допомогою: