Che cos'è il BDD Testing? Framework di sviluppo guidato dal comportamento

⚡ Riepilogo intelligente

Il test BDD descrive il comportamento dell'applicazione in un linguaggio semplice Given-When-Then in modo che analisti, sviluppatori e tester condividano una specifica. Questa guida applica tale approccio al test delle API REST con Behave, il Python Framework che comprende configurazione, file di funzionalità, implementazione dei passaggi, esecuzione e reporting.

  • 🗣️ Principio fondamentale: Lo sviluppo guidato dal comportamento (Behavior-Driven Development, BDD) estende lo sviluppo guidato dai test (Test-Driven Development, DDDD) esprimendo ogni scenario in un linguaggio naturale che anche i non programmatori possono leggere e approvare.
  • 🧱 Struttura dello scenario: Given stabilisce le precondizioni, When esegue l'azione e Then afferma il risultato, mentre And o But ereditano il decoratore del passaggio precedente.
  • 🌐 Copertura REST: L'esempio illustra il funzionamento completo delle operazioni CRUD tramite POST, GET, PUT e DELETE sul servizio pubblico jsonplaceholder posts.
  • 🐍 Configurazione del framework: Behave installa tramite pip su Python 3 e prevede una directory features contenente i file delle funzionalità più un pacchetto steps contenente le implementazioni.
  • 🔗 Collegamento a passi: Decoratori come @given, @when e @then corrispondono al testo della funzionalità e i segnaposto tra parentesi graffe passano i valori dallo scenario al Python funzione.
  • ▶ ️ Esecuzione: Il pratico strumento di formattazione stampa nella console un risultato di successo o fallimento con codifica a colori per ogni passaggio.
  • 📊 Reporting: Il formattatore allure-behave genera risultati leggibili automaticamente che la riga di comando di Allure visualizza come un report HTML navigabile.

Che cos'è il test BDD?

Cos'è il test BDD (sviluppo comportamentale)?

Test BDD (sviluppo comportamentale). BDD è una tecnica di sviluppo software agile ed è un'estensione del TDD, ovvero dello sviluppo guidato dai test. Nel BDD, i casi di test sono scritti in un linguaggio naturale che può essere compreso anche da chi non è programmatore.

Come funziona il test BDD?

Immagina di dover creare un modulo per i trasferimenti di fondi in un'applicazione di internet banking.

Esistono diversi modi per testarlo:

  1. Il trasferimento dei fondi dovrebbe avvenire solo se il conto di origine dispone di un saldo sufficiente.
  2. Il trasferimento dei fondi dovrebbe avvenire se i dettagli del conto di destinazione sono corretti
  3. Il trasferimento dei fondi dovrebbe avvenire solo se la password di transazione, il codice RSA e le credenziali di sicurezza inserite dall'utente sono corrette.
  4. Il trasferimento dei fondi deve essere effettuato anche nei giorni festivi.
  5. Il trasferimento dei fondi dovrebbe avvenire in una data futura stabilita dal titolare del conto

Migliori Scenario di prova diventa più elaborato e complesso man mano che consideriamo caratteristiche aggiuntive come il trasferimento dell'importo X per un intervallo di Y giorni o mesi, l'arrestoping un trasferimento programmato al raggiungimento del totale Z, e così via.

La tendenza generale degli sviluppatori è quella di sviluppare le funzionalità e scrivere il codice di test in un secondo momento. Come è evidente nel caso sopra riportato, Test Case lo sviluppo qui è complesso, quindi lo sviluppatore lo rimanderà Collaudo fino al rilascio, momento in cui vengono effettuati test rapidi ma inefficaci.

Per superare questo problema, è stato ideato il Behavior Driven Development (BDD), che semplifica l'intero processo di test per lo sviluppatore.

In BDD, qualunque cosa scrivi deve entrare Dato-Quando-Allora passaggi. Consideriamo lo stesso esempio precedente in 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

Questo modulo è facile da scrivere, leggere e comprendere. Copre ogni possibile caso di test per il modulo di trasferimento fondi e può essere modificato rapidamente per includerne altri. Si presenta inoltre come una documentazione dinamica del modulo. Poiché il BDD deriva dal TDD, vale la pena chiarire la differenza tra i due prima di passare agli strumenti.

BDD vs TDD: differenze chiave

Entrambe le pratiche sono basate sui test e accorciano i cicli di feedback. Differiscono per chi scrive i test, per il linguaggio di programmazione utilizzato e per il livello dell'applicazione che descrivono.

Aspetto TDD (Sviluppo guidato dai test) BDD (sviluppo guidato dal comportamento)
Focus Come funziona internamente un'unità di codice Come si comporta il sistema dal punto di vista dell'utente
Lingue disponibili Asserzioni del linguaggio di programmazione Scenari in linguaggio naturale Dato-Quando-Allora
Autore principale Sviluppatore Analista aziendale, responsabile prodotto, tester e sviluppatore, tutto insieme.
Readable da non programmatori Non Si
Ambito tipico Livello dell'unità Funzionalità, API e livello di accettazione
Strumenti comuni JUnit, pytest, NUnit Comportarsi, Cucumber, SpecFlow, JBehave

I due sono complementari piuttosto che in competizione. Le squadre comunemente mantengono TDD per la progettazione a livello di unità e aggiungere scenari BDD per descrivere il comportamento che le parti interessate effettivamente approvano. Poiché gli endpoint REST si trovano esattamente a quel livello di accettazione, si adattano naturalmente al BDD.

Cos'è il test dell'API REST?

Poiché REST è diventato uno stile popolare per la creazione di API, automatizzare i casi di test delle API REST insieme ai casi di test dell'interfaccia utente è diventato altrettanto importante. REST Test dell'API implica il test delle azioni CRUD (Create-Read-Update-Delete) con i metodi POST, GET, PUT e DELETE rispettivamente.

Cos'è Comportarsi?

Comportarsi è uno dei popolari Python Framework di test BDD. Ecco come funziona Behave:

  • I file delle funzionalità vengono redatti dal Business Analyst, dallo Sponsor o da chiunque sia responsabile degli scenari di comportamento. Un file delle funzionalità utilizza un formato in linguaggio naturale per descrivere una funzionalità, o parte di essa, con esempi rappresentativi dei risultati attesi.
  • Questi passaggi dello scenario sono mappati alle implementazioni dei passaggi scritte in Python.
  • Facoltativamente, i controlli ambientali eseguono il codice prima e dopo passaggi, scenari, funzionalità o l'intera esecuzione.

Ora che i ruoli di ciascun file sono chiari, è possibile installare il framework.

Configurazione del framework di test BDD Comportarsi bene Windows

Installazione:

Configurazione del progetto:

  • Creare un nuovo progetto
  • Creare la seguente struttura di directory

Impostazione del progetto

Lo screenshot qui sopra mostra il layout previsto da Behave: un Caratteristiche directory contenente i file delle funzionalità e una directory nidificata passi directory contenente il Python implementazioni. Behave le rileva entrambe tramite il nome, quindi i nomi delle cartelle devono corrispondere esattamente.

File di funzionalità:

Ora procediamo alla creazione del file delle funzionalità. Esempio_REST_API_Testing.feature, con la funzionalità che consiste in operazioni CRUD sul servizio 'posts'.

Questo esempio utilizza il https://jsonplaceholder.typicode.com/ Questo post presenta un servizio REST di esempio, una finta API gratuita che accetta richieste di scrittura e restituisce risposte realistiche senza salvare le modifiche.

Scenario POST di esempio

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

Analogamente, è possibile scrivere gli scenari rimanenti come segue:

Impostazione del progetto

Esempio_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"

Attuazione delle fasi

Ora, per le fasi delle funzionalità utilizzate negli scenari precedenti, è possibile scrivere implementazioni in Python file all'interno della directory “steps”.

Il framework Behave identifica la funzione step confrontando i decoratori con il predicato del file feature. Ad esempio, un Dato predicato in uno scenario di file di funzionalità cerca una funzione di passaggio che trasporta il @given decoratore. Lo stesso abbinamento avviene per Quando and Poi. Nel caso di 'But' e 'And', la funzione step prende lo stesso decoratore del passo precedente. Ad esempio, se 'And' segue un Given, il decoratore della funzione step corrispondente è @given.

Ad esempio, il passaggio When per POST può essere implementato come segue. Nota come "application/json" viene passato dal file delle caratteristiche al segnaposto tra parentesi graffe: questo processo è chiamato parametrizzazione.

# 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

Allo stesso modo, l'implementazione degli altri passaggi nella fase Python il file apparirà così:

Attuazione delle fasi

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

⚠️ Nota: Confrontando i codici di stato con == anziché is questioni. Il is L'operatore verifica l'identità dell'oggetto, non l'uguaglianza, e Python 3.8 e successivi sollevano un SyntaxWarning: “is” con un letterale per quel modello. Qualsiasi affermazione scritta come assert code is 201 dovrebbe essere riscritto come assert code == 201.

Esecuzione dei test

Lo sviluppo dello script di test è completo, quindi eseguiamo i test. Esegui il seguente comando nel prompt dei comandi per avviare il file feature:

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

Di seguito vengono visualizzati i risultati dell'esecuzione del test:

Esecuzione dei test

Visualizzazione del report sulla console

L'output della console è comodo durante lo sviluppo, ma le parti interessate di solito preferiscono un report leggibile, che Allure è in grado di fornire.

Report

Innanzitutto, installa il formattatore Allure Behave e lo strumento da riga di comando Allure. Il formattatore è un Python pacchetto; lo strumento da riga di comando è un download separato descritto nel Documentazione del rapporto 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

Questo genera il report dei risultati del test in un formato presentabile e informativo come questo:

Report

Rapporto di prova in formato HTML

Rapporto di prova in formato HTML

Rapporto di prova che mostra il risultato del singolo scenario

Una suite di funzionalità funzionante è utile solo se rimane leggibile man mano che l'API si evolve, ed è qui che la disciplina nella gestione dei file di funzionalità ripaga.

Migliori pratiche per la scrittura di file di funzionalità di comportamento

Una suite Behave si degrada rapidamente quando gli scenari descrivono clic e payload invece del comportamento. Le pratiche seguenti mantengono i file delle funzionalità leggibili per gli stakeholder aziendali mentre mantengonoping , il Python livello gestibile dagli ingegneri.

  1. Descrivi il comportamento, non l'implementazione. Scrivi "Dato che un cliente ha un saldo sufficiente", non "Dato che la colonna del saldo è uguale a 500". Il file delle funzionalità descrive cosa fa il sistema; il file dei passaggi descrive come viene verificato.
  2. Mantenere un solo comportamento per scenario. Uno scenario che prevede un codice di stato, un corpo di risposta e una riga nel database è in realtà composto da tre scenari distinti. Suddividendoli, gli errori vengono indirizzati direttamente alla causa.
  3. Sposta la configurazione condivisa in background. L'esempio sopra utilizza Background: Given I set sample REST API url in modo che ogni scenario erediti la base URL senza ripeterlo.
  4. Parametrizza invece di duplicare. Segnaposto rinforzati come "{request_name}" Lasciamo che una singola funzione gestisca le asserzioni GET, PUT e DELETE, motivo per cui l'esempio richiede molte meno definizioni di passaggi rispetto agli scenari.
  5. Utilizzare la funzione "Scenario Outline" per le variazioni dei dati. Quando lo stesso comportamento deve essere dimostrato per diversi input, un Examples: La tabella è più chiara degli scenari copiati.
  6. Evitare dipendenze tra scenari diversi. Behave non garantisce che uno scenario POST venga eseguito prima di uno scenario GET in ogni configurazione. Ogni scenario dovrebbe creare lo stato di cui ha bisogno.
  7. Sostituire le variabili globali a livello di modulo con il contesto. L'esempio memorizza endpoint e risposte nei dizionari dei moduli per brevità. Nelle suite di produzione, memorizzali su Behave context oggetto in modo che lo stato si ripristini correttamente tra gli scenari.
  8. Etichettare gli scenari per le esecuzioni selettive. Etichette come @smoke or @regression consentire behave --tags=@smoke, il che mantiene rapido il feedback della pipeline.

Applicare queste abitudini fin dal primo file di funzionalità mantiene una suite BDD utile ben oltre gli esempi CRUD iniziali. I team che estendono ulteriormente questo approccio spesso lo abbinano a Cucumber per progetti JVM o Postman per verifiche esplorative delle API.

DOMANDE FREQUENTI

Entrambi leggono i file delle funzionalità di Gherkin. Cucumber lega i passaggi a Java, Rubino o JavaScript, mentre Behave li lega a PythonScegli quello che corrisponde al tuo stack applicativo in modo che le definizioni dei passaggi riutilizzino le utilità di test esistenti.

Sì. Behave restituisce un codice di uscita diverso da zero quando uno qualsiasi degli scenari fallisce, quindi JenkinsGitLab CI o GitHub Actions possono interrompere automaticamente la build. Pubblica la cartella dei risultati di Allure come artefatto di build per la creazione di report.

Sì. Le serrature scorrevoli portatili e i catenacci a superficie possono essere usati per mettere in sicurezza una porta a scomparsa dall'esterno. Alcuni kit con catena di sicurezza consentono anche il bloccaggio esterno con chiave o manopola girevole. AI Gli assistenti elaborano scenari Given-When-Then a partire da user story o da una specifica OpenAPI. RevEsaminate l'output per individuare eventuali casi negativi e regole aziendali mancanti, poiché gli scenari generati spesso coprono solo il percorso ideale.

Gli strumenti di intelligenza artificiale rilevano scenari duplicati o quasi duplicati, suggeriscono passaggi parametrizzati riutilizzabili e raggruppano i guasti instabili in base alla probabile causa. Ciò riduce lo sforzo di verifica manuale man mano che i file delle funzionalità si moltiplicano tra i team.

Recupera il token una sola volta nell'hook `before_all` del file `environment.py`, memorizzalo nell'oggetto `context` e allegalo come intestazione `Authorization` in ogni passaggio della richiesta. Mantieni le credenziali nelle variabili d'ambiente, mai nei file di funzionalità.

Riassumi questo post con: