Hvad er BDD-testning? Adfærdsdrevet udviklingsramme

⚡ Smart opsummering

BDD-testning beskriver applikationsadfærd i et almindeligt "Givet-When-Then"-sprog, så analytikere, udviklere og testere deler én specifikation. Denne gennemgang anvender denne tilgang til REST API-testning med Behave, Python framework, der dækker opsætning, funktionsfiler, trinvise implementeringer, udførelse og rapportering.

  • 🗣️ Kerneprincip: Adfærdsdrevet udvikling udvider testdrevet udvikling ved at udtrykke ethvert scenarie i naturligt sprog, som ikke-programmører kan læse og godkende.
  • 🧱 Scenariestruktur: Givet etablerer forudsætninger, When udfører handlingen, og Then hævder resultatet, mens Og eller Men arver dekoratoren fra det foregående trin.
  • 🌐 REST-dækning: Eksemplet udøver fuld CRUD-adfærd via POST, GET, PUT og DELETE mod den offentlige jsonplaceholder posts-tjeneste.
  • 🐍 Opsætning af rammeværk: Opfør installationer via pip på Python 3 og forventer en features-mappe, der indeholder feature-filer plus en steps-pakke, der indeholder implementeringerne.
  • 🔗 Trinbinding: Dekoratorer som @given, @when og @then matcher funktionsteksten, og afstivede pladsholdere overfører værdier fra scenariet til Python funktion.
  • ▶ ️ Udførelse: Den flotte formateringssoftware udskriver et farvekodet bestået eller ikke bestået resultat for hvert trin i konsollen.
  • 📊 Rapportering: allure-behave-formateringen skriver maskinlæsbare resultater, som Allure-kommandolinjen gengiver som en gennemsebar HTML-rapport.

Hvad er BDD-testning

Hvad er BDD-test (Behavior Driven Development)?

BDD (Adfærdsdrevet udvikling) Test er en teknik til agil softwareudvikling og er en udvidelse af TDD, dvs. testdrevet udvikling. I BDD skrives testcases i et naturligt sprog, som selv ikke-programmører kan læse.

Hvordan fungerer BDD-test?

Forestil dig, at du har fået til opgave at oprette et pengeoverførselsmodul i en netbankapplikation.

Der er flere måder at teste det på:

  1. Pengeoverførsel skal finde sted, hvis der er tilstrækkelig saldo på kildekontoen
  2. Pengeoverførsel bør finde sted, hvis destinations-a/c-oplysningerne er korrekte
  3. Pengeoverførsel skal finde sted, hvis den indtastede transaktionsadgangskode / RSA-kode / sikkerhedsgodkendelse er korrekt.
  4. Pengeoverførsel skal finde sted, selvom det er en helligdag
  5. Pengeoverførsel bør finde sted på en fremtidig dato som fastsat af kontohaveren

Testscenarie bliver mere detaljeret og kompleks, efterhånden som vi overvejer yderligere funktioner såsom at overføre beløb X i et interval på Y dage eller måneder, stoppeping en planlagt overførsel, når det samlede beløb når Z, og så videre.

Udvikleres generelle tendens er at udvikle funktioner og skrive testkode senere. Som det fremgår af ovenstående tilfælde, Test sag Udviklingen her er kompleks, så udvikleren vil udskyde Test indtil udgivelsen, hvorefter der udføres hurtig, men ineffektiv testning.

For at overkomme dette problem blev adfærdsdrevet udvikling (BDD) udtænkt. Det gør hele testprocessen nemmere for en udvikler.

I BDD skal det, du skriver, gå ind i Givet-Hvornår-Så trin. Lad os betragte det samme eksempel ovenfor i 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

Denne formular er nem at skrive, læse og forstå. Den dækker alle mulige testcases for pengeoverførselsmodulet og kan hurtigt ændres for at imødekomme flere. Den kan også læses som levende dokumentation for modulet. Fordi BDD voksede ud af TDD, er forskellen mellem de to værd at afklare, før man går videre til værktøjsdesign.

BDD vs TDD: Vigtigste forskelle

Begge praksisser er testorienterede, og begge forkorter feedback-loops. De adskiller sig i, hvem der skriver testene, hvilket sprog disse tests bruger, og hvilket lag af applikationen de beskriver.

Aspect TDD (Testdrevet Udvikling) BDD (Behavior Driven Development)
Fokus Hvordan en kodeenhed fungerer internt Hvordan systemet opfører sig fra brugerens perspektiv
Sprog Programmeringssprogspåstande Natursprogede givne-hvornår-så-scenarier
Primær forfatter Udvikler Forretningsanalytiker, produktejer, tester og udvikler sammen
Readable af ikke-programmører Ingen Ja
Typisk omfang Enhedsniveau Funktion, API og acceptniveau
Almindelige værktøjer JUnit, pytest, NUnit Opføre sig, Cucumber, SpecFlow, JBehave

De to komplementerer snarere end konkurrerer. Hold holder ofte TDD til design på enhedsniveau og tilføj BDD-scenarier ovenpå for at beskrive den adfærd, som interessenter rent faktisk godkender. Da REST-slutpunkter sidder præcis på det acceptlag, er de et naturligt match til BDD.

Hvad er REST API-testning?

Efterhånden som REST er blevet en populær stil til at bygge API'er, er det blevet lige så vigtigt at automatisere REST API-testcases sammen med UI-testcases. API-test involverer testning af CRUD (Create-Read-Update-Delete) handlinger med metoderne henholdsvis POST, GET, PUT og DELETE.

Hvad er Behave?

Behave er en af ​​de populære Python BDD-testrammer. Sådan fungerer Behave:

  • Funktionsfiler skrives af din forretningsanalytiker, sponsor eller den, der ejer adfærdsscenarierne. En funktionsfil bruger et naturligt sprogformat, der beskriver en funktion eller en del af en funktion med repræsentative eksempler på forventede resultater.
  • Disse scenarietrin er knyttet til trinimplementeringer skrevet i Python.
  • Valgfrit kan miljøkontroller køre kode før og efter trin, scenarier, funktioner eller hele kørslen.

Når rollerne for hver fil er klare, kan frameworket nu installeres.

Opsætning af BDD Testing Framework Behave on Windows

Installation:

Projektopsætning:

  • Opret et nyt projekt
  • Opret følgende mappestruktur

Projektopsætning

Skærmbilledet ovenfor viser det layout, Behave forventer: a funktioner mappe, der indeholder funktionsfilerne, og en indlejret trin kataloget, der indeholder Python implementeringer. Behave opdager begge ved navn, så mappenavnene skal stemme nøjagtigt overens.

Funktionsfiler:

Lad os nu bygge funktionsfilen Sample_REST_API_Testing.feature, hvor funktionen er CRUD-operationer på 'indlæg'-tjenesten.

Dette eksempel bruger https://jsonplaceholder.typicode.com/ indlæg eksempel REST-tjeneste, en gratis falsk API, der accepterer skriveanmodninger og returnerer realistiske svar uden at bevare ændringerne.

Eksempel POST-scenarie

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

På samme måde kan du skrive de resterende scenarier som følger:

Projektopsætning

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"

Trin implementering

Nu kan du skrive implementeringer i de funktionstrin, der bruges i scenarierne ovenfor. Python filer i mappen "steps".

Behave-frameworket identificerer step-funktionen ved at matche dekoratører mod feature-filprædikatet. For eksempel en I betragtning af prædikat i et featurefilscenarie søger efter en trinfunktion, der bærer @given dekoratør. Den samme matchning sker for Når og DerefterI tilfælde af 'Men' og 'Og' tager trinfunktionen den samme dekorator som dens foregående trin. Hvis 'Og' f.eks. følger efter en given, er den matchende trinfunktionsdekorator @given.

For eksempel kan When-trinnet for POST implementeres som følger. Bemærk hvordan "application/json" sendes fra feature-filen til den afstivede pladsholder — dette kaldes parametrisering.

# 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

På samme måde gælder implementeringen af ​​de andre trin i trinnet Python filen ser sådan ud:

Trin implementering

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

⚠️ Bemærk: Sammenligning af statuskoder med == snarere end is sager. Det is operatoren tester objektidentitet, ikke lighed, og Python 3.8 og senere hæve en SyntaksAdvarsel: "er" med en bogstavelig for det mønster. Enhver påstand skrevet som assert code is 201 bør omskrives som assert code == 201.

Afvikling af testene

Udviklingen af ​​testscriptet er færdig, så lad os køre testene. Udfør følgende kommando ved kommandoprompten for at køre funktionsfilen:

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

Dette viser resultaterne af testens udførelse som følger:

Afvikling af testene

Rapportvisning på konsollen

Konsoloutput er praktisk under udvikling, men interessenter foretrækker normalt en læsbar rapport, hvilket Allure leverer.

Rapporter

Installer først Allure Behave-formateringsværktøjet og Allure-kommandolinjeværktøjet. Formateringsværktøjet er en Python pakke; kommandolinjeværktøjet er en separat download beskrevet i Dokumentation af Allure-rapporten.

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

Dette genererer testresultatrapporten i et præsentabelt og informativt format som dette:

Rapporter

Testrapport i HTML-format

Testrapport i HTML-format

Testrapport, der viser individuelle scenarieresultater

En fungerende suite er kun nyttig, hvis den forbliver læsbar, efterhånden som API'en vokser, hvilket er hvor disciplin inden for funktionsfiler betaler sig.

Bedste praksis for at skrive Behave Feature Files

En Behave-suite forringes hurtigt, når scenarier beskriver klik og data i stedet for adfærd. Fremgangsmåderne nedenfor holder funktionsfiler læsbare for forretningsinteressenter, samtidig med at de holdesping og Python lag, der kan vedligeholdes af ingeniører.

  1. Beskriv adfærd, ikke implementering. Skriv "Forudsat at en kunde har tilstrækkelig saldo", ikke "Forudsat at saldokolonnen er lig med 500". Funktionsfilen angiver, hvad systemet gør; trinfilen angiver, hvordan det kontrolleres.
  2. Behold én adfærd pr. scenarie. Et scenarie, der hævder en statuskode, en svartekst og en databaserække, er i virkeligheden tre scenarier. Opdeling af dem får fejl til at pege direkte på årsagen.
  3. Flyt delt opsætning til baggrunden. Eksemplet ovenfor bruger Background: Given I set sample REST API url så hvert scenarie arver grundlaget URL uden at gentage det.
  4. Parameteriser i stedet for at duplikere. Afstivede pladsholdere såsom "{request_name}" Lad én trinfunktion betjene GET-, PUT- og DELETE-påstande, hvilket er grunden til, at eksemplet kræver langt færre trindefinitioner end scenarier.
  5. Brug scenarieoversigt til datavariationer. Når den samme adfærd skal bevises for flere input, Examples: Tabellen er tydeligere end kopierede scenarier.
  6. Undgå afhængigheder mellem scenarier. Behave garanterer ikke, at et POST-scenarie kører før et GET-scenarie i alle konfigurationer. Hvert scenarie bør oprette den tilstand, det har brug for.
  7. Erstat globaler på modulniveau med kontekst. Eksemplet gemmer slutpunkter og svar i modulordbøger for at overskue det. I produktionspakker skal de gemmes på Behaves context objekt, så tilstanden nulstilles præcist mellem scenarier.
  8. Tag scenarier for selektive kørsler. Tags såsom @smoke or @regression tillade behave --tags=@smoke, hvilket holder pipeline-feedbacken hurtig.

Ved at anvende disse vaner fra den første funktionsfil bevares en BDD-suites værdifulde værdi langt ud over de indledende CRUD-eksempler. Teams, der udvider denne tilgang yderligere, kombinerer den ofte med Cucumber til JVM-projekter eller Postman til udforskende API-tjek.

Ofte Stillede Spørgsmål

Begge læser Gherkin-funktionsfiler. Cucumber binder trin til Java, Ruby, eller JavaScript, mens Behave binder dem til PythonVælg den, der matcher din applikationsstak, så trindefinitionerne genbruger eksisterende testværktøjer.

Ja. Behave returnerer en exit-kode, der ikke er nul, når et scenarie fejler, så Jenkins, GitLab CI eller GitHub-handlinger kan automatisk få buildet til at mislykkes. Udgiv Allure-resultatmappen som en build-artefakt til rapportering.

Ja. AI Assistenter udarbejder Givet-Hvornår-Så-scenarier fra brugerhistorier eller en OpenAPI-specifikation. RevSe outputtet for manglende negative tilfælde og forretningsregler, fordi genererede scenarier ofte kun dækker den lykkelige vej.

AI-værktøjer registrerer dubletter eller næsten dubletter, foreslår genanvendelige parametriserede trin og grupperer ustabile fejl efter sandsynlig årsag. Dette reducerer den manuelle revisionsindsats, efterhånden som funktionsfiler formeres på tværs af teams.

Hent tokenet én gang i environment.py before_all hook'en, gem det på kontekstobjektet, og vedhæft det som en Authorization-header i hvert anmodningstrin. Gem legitimationsoplysninger i miljøvariabler, aldrig i funktionsfiler.

Opsummer dette indlæg med: