Qu’est-ce que le test BDD ? Cadre de développement piloté par le comportement

⚡ Résumé intelligent

Les tests BDD décrivent le comportement d'une application en langage clair « Étant donné-Quand-Alors », permettant ainsi aux analystes, développeurs et testeurs de partager une spécification commune. Ce guide applique cette approche aux tests d'API REST avec Behave. Python cadre, couvrant la configuration, les fichiers de fonctionnalités, les implémentations d'étapes, l'exécution et la génération de rapports.

  • 🗣️ Principe de base : Le développement piloté par le comportement (BDD) étend le développement piloté par les tests (TDD) en exprimant chaque scénario en langage naturel, que les non-programmeurs peuvent lire et approuver.
  • 🧱 Structure du scénario : Given établit les préconditions, When effectue l'action et Then affirme le résultat, tandis que And ou But héritent du décorateur de l'étape précédente.
  • 🌐 Couverture REST : Cet exemple met en œuvre un comportement CRUD complet via les méthodes POST, GET, PUT et DELETE sur le service public de publications jsonplaceholder.
  • (I.e. Configuration du framework : Behave s'installe via pip sur Python 3 et attend un répertoire de fonctionnalités contenant des fichiers de fonctionnalités ainsi qu'un package d'étapes contenant les implémentations.
  • 🔗 Liaison par étapes : Les décorateurs tels que @given, @when et @then correspondent au texte de la fonctionnalité, et les espaces réservés entre accolades transmettent les valeurs du scénario au texte. Python la fonction.
  • ▶ ️ Exécution: Le formateur affiche dans la console un résultat de réussite ou d'échec, codé par couleur, pour chaque étape.
  • (I.e. Reporting: Le formateur allure-behave écrit des résultats lisibles par machine que la ligne de commande Allure rend sous forme de rapport HTML consultable.

Qu'est-ce que le test BDD ?

Qu'est-ce que les tests BDD (Behaviour Driven Development) ?

Tests BDD (Développement piloté par le comportement) Le BDD est une technique de développement logiciel agile et une extension du TDD (Test Driven Development). En BDD, les cas de test sont rédigés dans un langage naturel compréhensible même par les non-programmeurs.

Comment fonctionnent les tests BDD ?

Imaginez que vous êtes chargé de créer un module de transfert de fonds dans une application de banque en ligne.

Il existe plusieurs façons de le tester :

  1. Le transfert de fonds devrait avoir lieu si le solde du compte source est suffisant.
  2. Le transfert de fonds doit avoir lieu si les détails de la climatisation de destination sont corrects.
  3. Le transfert de fonds devrait avoir lieu si le mot de passe de transaction / le code RSA / l'authentification de sécurité saisis par l'utilisateur sont corrects.
  4. Le transfert de fonds doit être effectué même s'il s'agit d'un jour férié.
  5. Le transfert de fonds doit avoir lieu à une date ultérieure fixée par le titulaire du compte.

Le Scénario de test La procédure devient plus élaborée et complexe lorsque l'on considère des fonctionnalités supplémentaires telles que le transfert d'un montant X pendant une période de Y jours ou mois, ou l'arrêt de ce transfert.ping un virement programmé lorsque le total atteint Z, et ainsi de suite.

La tendance générale des développeurs est de développer les fonctionnalités et d'écrire le code de test ultérieurement. Comme le montre l'exemple ci-dessus, Cas de test Le développement est complexe ici, donc le développeur va le reporter. Tests jusqu'à la mise en production, moment auquel des tests rapides mais inefficaces sont effectués.

Pour pallier ce problème, le développement piloté par le comportement (BDD) a été conçu. Il simplifie l'ensemble du processus de test pour le développeur.

En BDD, tout ce que vous écrivez doit être placé dans Étant donné-quand-alors étapes. Prenons le même exemple que ci-dessus dans le cadre du 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

Ce formulaire est facile à rédiger, à lire et à comprendre. Il couvre tous les cas de test possibles pour le module de transfert de fonds et peut être rapidement modifié pour en intégrer davantage. Il constitue également une documentation évolutive pour le module. Le BDD étant issu du TDD, il est important de bien comprendre la différence entre les deux avant de passer aux outils.

BDD vs TDD : Principales différences

Ces deux pratiques privilégient les tests préalables et raccourcissent les cycles de retour d'information. Elles diffèrent par l'auteur des tests, le langage utilisé et la couche applicative qu'ils décrivent.

Aspect Développement piloté par les tests (TDD) BDD (développement axé sur le comportement)
Focus Fonctionnement interne d'une unité de code Comportement du système du point de vue de l'utilisateur
Langue assertions de langage de programmation Scénarios de langage naturel Étant donné-Quand-Alors
Auteur principal Développeur Analyste d'affaires, responsable produit, testeur et développeur ensemble
Readable par des non-programmeurs Non Oui
Portée typique Niveau de l'unité Niveau de fonctionnalités, d'API et d'acceptation
Outils communs JUnit, pytest, NUnit Se comporter, CucumberSpecFlow, JBehave

Les deux sont complémentaires plutôt que concurrents. Les équipes conservent généralement TDD Pour la conception au niveau unitaire, ajoutez des scénarios BDD afin de décrire le comportement validé par les parties prenantes. Les points de terminaison REST se situant précisément à ce niveau d'acceptation, ils sont parfaitement adaptés à l'approche BDD.

Qu'est-ce que les tests d'API REST ?

Avec la popularité croissante de REST pour la création d'API, l'automatisation des cas de test d'API REST, au même titre que celle des cas de test d'interface utilisateur, est devenue tout aussi importante. Test d'API implique de tester les actions CRUD (Créer-Lire-Mettre à jour-Supprimer) avec les méthodes POST, GET, PUT et DELETE respectivement.

Qu'est-ce que le comportement ?

Le comportement est l'un des plus populaires Python Frameworks de tests BDD. Voici comment fonctionne Behave :

  • Les fiches de fonctionnalités sont rédigées par votre analyste métier, votre commanditaire ou toute personne responsable des scénarios de comportement. Une fiche de fonctionnalités utilise un format en langage naturel pour décrire une fonctionnalité, ou une partie de fonctionnalité, avec des exemples représentatifs des résultats attendus.
  • Ces étapes de scénario sont mappées sur des implémentations d'étapes écrites en Python.
  • En option, les contrôles environnementaux exécutent du code avant et après les étapes, les scénarios, les fonctionnalités ou l'exécution complète.

Les rôles de chaque fichier étant désormais clairement définis, le framework peut être installé.

Configuration du cadre de test BDD Windows

Installation:

Configuration du projet :

  • Créer un nouveau projet
  • Créez la structure de répertoires suivante

Configuration du projet

La capture d'écran ci-dessus montre la mise en page attendue par Behave : un Caractéristiques répertoire contenant les fichiers de fonctionnalités, et un répertoire imbriqué mesures répertoire contenant le Python implémentations. Behave détecte les deux par leur nom, les noms de dossiers doivent donc correspondre exactement.

Fichiers de fonctionnalités :

Commençons maintenant par créer le fichier de fonctionnalités. Sample_REST_API_Testing.feature, la fonctionnalité étant les opérations CRUD sur le service « posts ».

Cet exemple utilise le https://jsonplaceholder.typicode.com/ Posts est un service REST d'exemple, une API factice gratuite qui accepte les requêtes d'écriture et renvoie des réponses réalistes sans enregistrer les modifications.

Exemple de scénario 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

De même, vous pouvez écrire les scénarios restants comme suit :

Configuration du projet

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"

Étapes de mise en œuvre

Maintenant, pour les étapes fonctionnelles utilisées dans les scénarios ci-dessus, vous pouvez écrire des implémentations dans Python fichiers dans le répertoire « steps ».

Le framework Behave identifie la fonction d'étape en faisant correspondre les décorateurs au prédicat du fichier de fonctionnalités. Par exemple, un Donné Le prédicat dans un scénario de fichier de fonctionnalités recherche une fonction en escalier qui transporte le @given décorateur. La même correspondance s'applique au décorateur. Lorsque vous et EnsuiteDans le cas de « Mais » et « Et », la fonction d'étape adopte le même décorateur que l'étape précédente. Par exemple, si « Et » suit une étape donnée, le décorateur correspondant de la fonction d'étape est : @given.

Par exemple, l'étape When pour POST peut être implémentée comme suit. Notez comment "application/json" est transmis du fichier de fonctionnalités à l'espace réservé entre accolades — c'est ce qu'on appelle la paramétrisation.

# 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

De même, la mise en œuvre des autres étapes de l'étape Python le fichier ressemblera à ceci:

Étapes de mise en œuvre

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

⚠️ Remarque : Comparaison des codes d'état avec == plutôt que des is Cela compte. is L'opérateur teste l'identité des objets, et non leur égalité. Python 3.8 et versions ultérieures soulèvent une Avertissement de syntaxe : « is » avec un sens littéral pour ce modèle. Toute assertion écrite comme assert code is 201 devrait être réécrit comme assert code == 201.

Exécution des tests

Le développement du script de test est terminé, nous pouvons donc exécuter les tests. Saisissez la commande suivante dans l'invite de commandes pour exécuter le fichier de fonctionnalités :

:: 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

Les résultats de l'exécution du test s'affichent comme suit :

Exécuter les tests

Affichage du rapport sur la console

L'affichage dans la console est pratique pendant le développement, mais les parties prenantes préfèrent généralement un rapport lisible, ce qu'offre Allure.

Rapports

Commencez par installer le formateur Allure Behave et l'outil en ligne de commande Allure. Le formateur est un Python Le package comprend un outil en ligne de commande, disponible en téléchargement séparé et décrit dans le manuel. Documentation du rapport 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

Ceci génère le rapport de résultats de test dans un format présentable et informatif comme celui-ci :

Rapports

Rapport de test au format HTML

Rapport de test au format HTML

Rapport de test affichant le résultat du scénario individuel

Une suite fonctionnelle n'est utile que si elle reste lisible à mesure que l'API se développe, et c'est là que la rigueur dans la gestion des fichiers de fonctionnalités prend tout son sens.

Meilleures pratiques pour la rédaction des fichiers de fonctionnalités Behave

Une suite de tests Behave se dégrade rapidement lorsque les scénarios décrivent des clics et des charges utiles plutôt que des comportements. Les pratiques ci-dessous permettent de conserver des fichiers de fonctionnalités lisibles pour les parties prenantes métier tout en préservant…ping le Python couche maintenable par les ingénieurs.

  1. Décrivez le comportement, pas la mise en œuvre. Écrivez « Étant donné qu'un client dispose d'un solde suffisant », et non « Étant donné que la colonne Solde est égale à 500 ». Le fichier de fonctionnalités décrit le fonctionnement du système ; le fichier d'étapes décrit la méthode de vérification.
  2. Conservez un comportement par scénario. Un scénario qui génère un code d'état, un corps de réponse et une ligne de base de données correspond en réalité à trois scénarios distincts. Les séparer permet d'identifier précisément la cause des échecs.
  3. Déplacer la configuration partagée en arrière-plan. L'exemple ci-dessus utilise Background: Given I set sample REST API url de sorte que chaque scénario hérite de la base URL sans le répéter.
  4. Paramétrez au lieu de dupliquer. Des espaces réservés entre accolades tels que "{request_name}" Une seule fonction étape par étape gère les assertions GET, PUT et DELETE, ce qui explique pourquoi l'exemple nécessite beaucoup moins de définitions d'étapes que de scénarios.
  5. Utilisez le schéma de scénario pour les variations de données. Lorsque le même comportement doit être prouvé pour plusieurs entrées, un Examples: Le tableau est plus clair que les scénarios copiés.
  6. Évitez les dépendances entre les scénarios. Behave ne garantit pas qu'un scénario POST s'exécute avant un scénario GET dans toutes les configurations. Chaque scénario doit créer l'état dont il a besoin.
  7. Remplacez les variables globales au niveau du module par le contexte. Par souci de concision, cet exemple stocke les points de terminaison et les réponses dans des dictionnaires de modules. Dans les environnements de production, stockez-les sur le serveur Behave. context L'objet permet ainsi une réinitialisation propre de l'état entre les scénarios.
  8. Scénarios d'étiquetage pour les exécutions sélectives. Étiquettes telles que @smoke or @regression permettre behave --tags=@smokece qui permet de maintenir un retour d'information rapide dans le pipeline.

L'application de ces bonnes pratiques dès le premier fichier de fonctionnalités permet à une suite BDD de conserver toute sa valeur, bien au-delà des exemples CRUD initiaux. Les équipes qui étendent cette approche la combinent souvent avec… Cucumber pour les projets JVM ou Postman pour des vérifications exploratoires de l'API.

FAQ

Les deux lisent les fichiers de fonctionnalités Gherkin. Cucumber lie les étapes à Java, Ruby, ou JavaScript, tandis que Behave les lie à PythonChoisissez celui qui correspond à votre pile d'applications afin que les définitions d'étapes réutilisent les utilitaires de test existants.

Oui. Behave renvoie un code de sortie différent de zéro lorsqu'un scénario échoue. JenkinsGitLab CI ou GitHub Actions peuvent faire échouer la compilation automatiquement. Publiez le dossier de résultats Allure en tant qu'artefact de compilation pour la génération de rapports.

Oui. AI Les assistants rédigent des scénarios « Étant donné-Quand-Alors » à partir de récits utilisateurs ou d'une spécification OpenAPI. RevExaminez les résultats pour détecter les cas négatifs et les règles métier manquants, car les scénarios générés ne couvrent souvent que le cas idéal.

Les outils d'IA détectent les scénarios dupliqués ou quasi-dupliqués, suggèrent des étapes paramétrées réutilisables et regroupent les défaillances aléatoires par cause probable. Cela réduit l'effort d'audit manuel à mesure que les fichiers de fonctionnalités se multiplient entre les équipes.

Récupérez le jeton une seule fois dans le hook `before_all` du fichier `environment.py`, stockez-le dans l'objet de contexte et joignez-le comme en-tête d'autorisation à chaque étape de requête. Conservez les informations d'identification dans des variables d'environnement, jamais dans les fichiers de fonctionnalités.

Résumez cet article avec :