Wat is BDD-testen? Gedragsgestuurd ontwikkelingsframework

⚡ Slimme samenvatting

BDD-testen beschrijft het gedrag van een applicatie in eenvoudige Given-When-Then-taal, zodat analisten, ontwikkelaars en testers dezelfde specificatie delen. Deze handleiding past die aanpak toe op het testen van REST API's met Behave, de Python framework, dat de configuratie, feature-bestanden, stapimplementaties, uitvoering en rapportage omvat.

  • 🗣️ Kernprincipe: Behavior-Driven Development (BDD) is een uitbreiding van Test-Driven Development (TDD) doordat elk scenario in natuurlijke taal wordt beschreven, zodat ook niet-programmeurs het kunnen lezen en goedkeuren.
  • 🧱 Scenario-structuur: Given stelt voorwaarden vast, When voert de actie uit en Then bevestigt het resultaat, terwijl And of But de decorator van de voorgaande stap overnemen.
  • 🌐 REST-dekking: Het voorbeeld demonstreert de volledige CRUD-functionaliteit via POST, GET, PUT en DELETE op de openbare jsonplaceholder-postservice.
  • 🐍 Frameworkconfiguratie: Behave wordt geïnstalleerd via pip op Python 3 en verwacht een 'features'-directory met daarin de feature-bestanden, plus een 'steps'-pakket met de implementaties.
  • 🔗 Stapbinding: Decorators zoals @given, @when en @then komen overeen met de featuretekst, en placeholders tussen accolades geven waarden uit het scenario door aan de code. Python functie.
  • ▶ ️ Uitvoering: De fraaie opmaakfunctie print een kleurgecodeerd resultaat (geslaagd of mislukt) voor elke stap in de console.
  • 📊 Rapportage: De allure-behave formatter schrijft machineleesbare resultaten die de Allure-opdrachtregel vervolgens weergeeft als een bladerbaar HTML-rapport.

Wat is BDD-testen?

Wat is BDD-testen (Behavior Driven Development)?

BDD (Gedragsgestuurde Ontwikkeling) Testen BDD is een techniek voor agile softwareontwikkeling en een uitbreiding van TDD, oftewel Test Driven Development. Bij BDD worden testgevallen geschreven in een natuurlijke taal die zelfs niet-programmeurs kunnen lezen.

Hoe BDD-testen werkt?

Stel, u krijgt de opdracht om een ​​module voor geldoverboekingen te ontwikkelen voor een internetbankierapplicatie.

Er zijn meerdere manieren om het te testen:

  1. Geldoverboekingen moeten plaatsvinden als er voldoende saldo op de bronrekening staat.
  2. De geldtransfer moet plaatsvinden als de gegevens van de bestemmingsrekening correct zijn
  3. De geldoverdracht kan alleen plaatsvinden als het transactiewachtwoord / RSA-code / beveiligingsauthenticatie die door de gebruiker is ingevoerd correct is.
  4. Geld overmaken kan ook op een feestdag.
  5. De geldoverdracht dient plaats te vinden op een toekomstige datum zoals vastgesteld door de rekeninghouder

De Testscenario Het wordt steeds uitgebreider en complexer naarmate we extra functies in overweging nemen, zoals het overmaken van bedrag X gedurende een periode van Y dagen of maanden, en het stoppen van de overdracht.ping een geplande overdracht wanneer het totaal Z bereikt, enzovoort.

Ontwikkelaars hebben over het algemeen de neiging om functionaliteiten te ontwikkelen en de testcode later te schrijven. Zoals blijkt uit het bovenstaande voorbeeld, Testgeval De ontwikkeling hier is complex, dus de ontwikkelaar zal het uitstellen. Testen tot aan de release, waarna snelle maar ineffectieve tests worden uitgevoerd.

Om dit probleem op te lossen, werd Behavior Driven Development (BDD) bedacht. Het maakt het hele testproces eenvoudiger voor een ontwikkelaar.

In BDD moet alles wat je schrijft erin staan Gegeven-wanneer-dan stappen. Laten we hetzelfde voorbeeld hierboven eens bekijken 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

Dit formulier is gemakkelijk te schrijven, te lezen en te begrijpen. Het dekt alle mogelijke testgevallen voor de module voor geldovermakingen en kan snel worden aangepast om er meer te omvatten. Het leest ook als levende documentatie voor de module. Omdat BDD is voortgekomen uit TDD, is het de moeite waard om het verschil tussen de twee te begrijpen voordat we verdergaan met de tooling.

BDD versus TDD: Belangrijkste verschillen

Beide werkwijzen zijn testgericht en verkorten de feedbackloops. Ze verschillen in wie de tests schrijft, welke programmeertaal die tests gebruiken en welke laag van de applicatie ze beschrijven.

Aspect TDD (Testgestuurde ontwikkeling) BDD (gedragsgedreven ontwikkeling)
Focus Hoe een code-eenheid intern werkt Hoe het systeem zich gedraagt ​​vanuit het perspectief van de gebruiker.
Taal Beweringen in programmeertalen Natuurlijke taalscenario's van het type 'Gegeven-Wanneer-Dan'
Hoofdauteur Ontwikkelaar Bedrijfsanalist, producteigenaar, tester en ontwikkelaar in één.
Readable door niet-programmeurs Nee Ja
Typische reikwijdte Eenheidsniveau Functie, API en acceptatieniveau
Gemeenschappelijke tools JUnit, pytest, NUnit Gedragen, CucumberSpecFlow, JBehave

De twee vullen elkaar aan in plaats van met elkaar te concurreren. Teams houden doorgaans... TDD Voor het ontwerpen op unitniveau kunnen we BDD-scenario's toevoegen om het gedrag te beschrijven waar stakeholders uiteindelijk mee instemmen. Omdat REST-endpoints zich precies op die acceptatielaag bevinden, zijn ze bij uitstek geschikt voor BDD.

Wat is REST API-testen?

Omdat REST een populaire stijl is geworden voor het bouwen van API's, is het automatiseren van REST API-testcases net zo belangrijk geworden als UI-testcases. REST API-testen Dit omvat het testen van CRUD-acties (Create-Read-Update-Delete) met respectievelijk de methoden POST, GET, PUT en DELETE.

Wat is gedrag?

Gedragen is een van de populaire Python BDD-testframeworks. Zo werkt Behave:

  • Featurebestanden worden geschreven door uw businessanalist, sponsor of degene die verantwoordelijk is voor de gedragsscenario's. Een featurebestand gebruikt een natuurlijke taalindeling om een ​​feature, of een deel van een feature, te beschrijven met representatieve voorbeelden van de verwachte resultaten.
  • Deze scenario-stappen worden gekoppeld aan stapimplementaties die zijn geschreven in Python.
  • Optioneel kunnen omgevingsbesturingselementen code uitvoeren vóór en na stappen, scenario's, functies of de hele uitvoering.

Nu de rol van elk bestand duidelijk is, kan het framework worden geïnstalleerd.

BDD Testing Framework instellen Behave aan Windows

Installatie:

Projectopstelling:

  • Maak een nieuw project
  • Maak de volgende mapstructuur aan.

Project Setup

De bovenstaande schermafbeelding toont de lay-out die Behave verwacht: een functionaliteiten map met de functiebestanden en een geneste stappen map met daarin de Python implementaties. Behave detecteert beide op naam, dus de mapnamen moeten exact overeenkomen.

Functiebestanden:

Laten we nu het feature-bestand bouwen. Voorbeeld_REST_API_Testing.functie, waarbij de functionaliteit bestaat uit CRUD-bewerkingen op de 'posts'-service.

Dit voorbeeld gebruikt de https://jsonplaceholder.typicode.com/ Dit bericht plaatst een voorbeeld van een REST-service, een gratis nep-API die schrijfverzoeken accepteert en realistische antwoorden retourneert zonder wijzigingen op te slaan.

Voorbeeld POST-scenario

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

Op dezelfde manier kunt u de overige scenario's als volgt beschrijven:

Project Setup

Voorbeeld_REST_API_Testing.functie

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"

Stappen Implementatie

Voor de functiestappen die in de bovenstaande scenario's worden gebruikt, kunt u implementaties schrijven in Python bestanden in de map "steps".

Het Behave-framework identificeert de stapfunctie door decorators te vergelijken met het predicaat in het featurebestand. Bijvoorbeeld: Gegeven In een scenario met een featurebestand zoekt een predicaat naar een stapfunctie die de volgende eigenschappen bevat: @given decorateur. Dezelfde afstemming vindt plaats voor . en Neem danIn het geval van 'Maar' en 'En' gebruikt de stapfunctie dezelfde decorator als de voorgaande stap. Als bijvoorbeeld 'En' volgt op 'Gegeven', is de bijbehorende decorator voor de stapfunctie... @given.

De 'When'-stap voor POST kan bijvoorbeeld als volgt worden geïmplementeerd. Let op hoe "application/json" wordt vanuit het feature-bestand doorgegeven aan de placeholder tussen accolades — dit wordt parameterisering genoemd.

# 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

Op dezelfde manier wordt de uitvoering van de andere stappen in de stap uitgevoerd. Python bestand ziet er als volgt uit:

Stappen Implementatie

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

⚠️ Opmerking: Het vergelijken van statuscodes met == dan is doet ertoe. De is De operator test de identiteit van een object, niet de gelijkheid ervan, en Python 3.8 en later verhogen een Syntaxwaarschuwing: “is” met een letterlijke waarde voor dat patroon. Elke bewering die is geschreven als assert code is 201 moet worden herschreven als assert code == 201.

Het uitvoeren van de tests

De ontwikkeling van het testscript is voltooid, dus laten we de tests uitvoeren. Voer de volgende opdracht in de opdrachtprompt uit om het feature-bestand te starten:

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

Dit toont de testuitvoeringsresultaten als volgt:

Het uitvoeren van de tests

Rapportweergave op de console

Console-uitvoer is handig tijdens de ontwikkeling, maar belanghebbenden geven meestal de voorkeur aan een leesbaar rapport, en dat biedt Allure.

Rapporten

Installeer eerst de Allure Behave-formatter en de Allure-opdrachtregeltool. De formatter is een Python pakket; de opdrachtregeltool is een aparte download die wordt beschreven in de Allure-rapportdocumentatie.

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

Dit genereert het testresultatenrapport in een overzichtelijk en informatief formaat, zoals dit:

Rapporten

Testrapport in HTML-formaat

Testrapport in HTML-formaat

Testrapport met het individuele scenarioresultaat

Een functionele suite is alleen nuttig als deze leesbaar blijft naarmate de API groeit, en dat is waar discipline in feature-bestanden zijn vruchten afwerpt.

Aanbevelingen voor het schrijven van Behave-functiebestanden

Een Behave-suite degradeert snel wanneer scenario's klikken en payloads beschrijven in plaats van gedrag. De onderstaande werkwijzen zorgen ervoor dat feature-bestanden leesbaar blijven voor zakelijke belanghebbenden, terwijl ze tegelijkertijd de functionaliteit behouden.ping the Python laag die door ingenieurs kan worden onderhouden.

  1. Beschrijf het gedrag, niet de uitvoering. Schrijf "Gegeven dat een klant voldoende saldo heeft", niet "Gegeven dat de kolom 'saldo' gelijk is aan 500". Het feature-bestand beschrijft wat het systeem doet; het step-bestand beschrijft hoe dit wordt gecontroleerd.
  2. Houd per scenario één gedragspatroon aan. Een scenario dat een statuscode, een antwoordbody en een database-rij controleert, bestaat in feite uit drie afzonderlijke scenario's. Door ze te scheiden, wijzen fouten direct naar de oorzaak.
  3. Verplaats gedeelde instellingen naar de achtergrond. Het bovenstaande voorbeeld gebruikt Background: Given I set sample REST API url zodat elk scenario de basis overneemt URL zonder het te herhalen.
  4. Parameteriseer in plaats van te dupliceren. Plaatsaanduidingen met accolades, zoals "{request_name}" Laat één stapfunctie de GET-, PUT- en DELETE-asserties afhandelen, waardoor het voorbeeld veel minder stapdefinities nodig heeft dan scenario's.
  5. Gebruik het scenario-overzicht voor datavariaties. Wanneer hetzelfde gedrag voor meerdere invoerwaarden moet worden aangetoond, een Examples: De tabel is duidelijker dan de gekopieerde scenario's.
  6. Vermijd afhankelijkheden tussen scenario's. Behave garandeert niet dat een POST-scenario in elke configuratie vóór een GET-scenario wordt uitgevoerd. Elk scenario moet de benodigde status creëren.
  7. Vervang globale variabelen op module-niveau door contextvariabelen. Het voorbeeld slaat eindpunten en antwoorden voor de duidelijkheid op in modulewoordenboeken. In productieomgevingen kunt u ze beter opslaan in de Behave-directory. context object zodat de status netjes wordt gereset tussen scenario's.
  8. Tagscenario's voor selectieve uitvoeringen. Labels zoals @smoke or @regression toelaten behave --tags=@smokewaardoor de feedback in de pipeline snel blijft.

Door deze werkwijzen vanaf het eerste feature-bestand toe te passen, blijft een BDD-suite waardevol, ook lang na de eerste CRUD-voorbeelden. Teams die deze aanpak verder uitbreiden, combineren deze vaak met... Cucumber voor JVM-projecten of Postman voor verkennende API-controles.

Veelgestelde vragen

Beide lezen Gherkin-functiebestanden. Cucumber verbindt stappen aan Java, Ruby, of JavaScript, terwijl Behave ze bindt aan PythonKies de optie die overeenkomt met uw applicatiestack, zodat stapdefinities bestaande testtools hergebruiken.

Ja. Behave retourneert een exitcode die niet nul is wanneer een scenario mislukt, dus JenkinsGitLab CI of GitHub Actions kunnen de build automatisch laten mislukken. Publiceer de Allure-resultatenmap als een build-artefact voor rapportage.

Ja. AI Assistenten stellen Given-When-Then-scenario's op aan de hand van user stories of een OpenAPI-specificatie. RevControleer de uitvoer op ontbrekende negatieve gevallen en bedrijfsregels, omdat gegenereerde scenario's vaak alleen het 'happy path' (het gunstige scenario) omvatten.

AI-tools detecteren dubbele of bijna-dubbele scenario's, suggereren herbruikbare geparameteriseerde stappen en groeperen onregelmatige fouten op basis van waarschijnlijke oorzaak. Dit vermindert de handmatige controle-inspanning naarmate het aantal feature-bestanden binnen verschillende teams toeneemt.

Haal het token eenmalig op in de `before_all` hook in `environment.py`, sla het op in het `context` object en voeg het toe als een `Authorization` header in elke request-stap. Bewaar de inloggegevens in omgevingsvariabelen, nooit in feature-bestanden.

Vat dit bericht samen met: