Vad är BDD-testning? Beteendedrivet utvecklingsramverk

⚡ Smart sammanfattning

BDD-testning beskriver applikationsbeteende i ett enkelt "Givet-When-Sedan"-språk så att analytiker, utvecklare och testare delar en specifikation. Denna genomgång tillämpar den metoden på REST API-testning med Behave, Python ramverk, som täcker installation, funktionsfiler, stegimplementeringar, exekvering och rapportering.

  • 🗣️ Kärnprincip: Beteendedriven utveckling utökar testdriven utveckling genom att uttrycka varje scenario i ett naturligt språk som icke-programmerare kan läsa och godkänna.
  • 🧱 Scenariostruktur: Givet fastställer förutsättningar, När utför handlingen och Sedan hävdar resultatet, medan Och eller Men ärver dekoratorn från föregående steg.
  • 🌐 REST-täckning: Exemplet utövar fullständigt CRUD-beteende via POST, GET, PUT och DELETE mot den publika jsonplaceholder posts-tjänsten.
  • 🐍 Ramverksinstallation: Bete sig installationer via pip på Python 3 och förväntar sig en features-katalog som innehåller feature-filer plus ett steps-paket som innehåller implementeringarna.
  • 🔗 Stegbindning: Dekoratorer som @given, @when och @then matchar funktionstexten, och förstärkta platshållare skickar värden från scenariot till Python funktion.
  • ▶ ️ Genomförande Den snygga formateraren skriver ut ett färgkodat resultat för godkänt eller underkänt för varje steg i konsolen.
  • 📊 Rapportering: Formateraren allure-behave skriver maskinläsbara resultat som Allure-kommandoraden renderar som en bläddringsbar HTML-rapport.

Vad är BDD-testning

Vad är BDD-testning (Behavior Driven Development)?

BDD (Beteendedriven utveckling) Testning är en teknik för agil mjukvaruutveckling och är en förlängning av TDD, dvs. testdriven utveckling. I BDD skrivs testfall på ett naturligt språk som även icke-programmerare kan läsa.

Hur fungerar BDD-testning?

Anta att du har fått i uppdrag att skapa en modul för överföring av medel i en nätbanksapplikation.

Det finns flera sätt att testa det:

  1. Fondöverföring bör ske om det finns tillräckligt saldo på källkontot
  2. Fondöverföring bör ske om informationen om destinationens luftkonditionering är korrekt
  3. Pengöverföring ska ske om transaktionslösenordet / RSA-koden / säkerhetsautentiseringen som angetts av användaren är korrekt.
  4. Överföring av medel ska ske även om det är en helgdag
  5. Fondöverföring bör ske på ett framtida datum som anges av kontoinnehavaren

Ocuco-landskapet Testscenario blir mer detaljerat och komplext när vi beaktar ytterligare funktioner som att överföra belopp X under ett intervall på Y dagar eller månader, stoppaping en schemalagd överföring när totalen når Z, och så vidare.

Utvecklare har en generell tendens att utveckla funktioner och skriva testkod senare. Som framgår av fallet ovan, Testfall utvecklingen här är komplex, så utvecklaren kommer att skjuta upp Testning tills lanseringen, varvid snabba men ineffektiva tester utförs.

För att övervinna detta problem utformades beteendedriven utveckling (BDD). Det gör hela testprocessen enklare för en utvecklare.

I BDD måste vad du än skriver gå in på Givet-När-Då steg. Låt oss betrakta samma exempel ovan 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

Det här formuläret är lätt att skriva, läsa och förstå. Det täcker alla möjliga testfall för fondöverföringsmodulen och kan snabbt modifieras för att rymma fler. Det kan också läsas som en levande dokumentation för modulen. Eftersom BDD växte fram ur TDD är skillnaden mellan de två värd att reda ut innan man går vidare till verktygsutveckling.

BDD vs TDD: Viktiga skillnader

Båda metoderna är testorienterade och båda förkortar feedback-loopar. De skiljer sig åt i vem som skriver testerna, vilket språk de använder och vilket lager av applikationen de beskriver.

Aspect TDD (Testdriven utveckling) BDD (Behavior Driven Development)
Fokus Hur en kodenhet fungerar internt Hur systemet beter sig ur användarperspektivet
Språk Programmeringsspråkspåståenden Naturligt språk Givna-När-Sedan-scenarier
Huvudförfattare Developer Affärsanalytiker, produktägare, testare och utvecklare tillsammans
Readable av icke-programmerare Nej Ja
Typisk omfattning Enhetsnivå Funktion, API och acceptansnivå
Vanliga verktyg JUnit, pytest, NUnit Bete sig, Cucumber, SpecFlow, JBehave

De två kompletterar snarare än konkurrerar. Lagen håller vanligtvis TDD för design på enhetsnivå och lägg till BDD-scenarier ovanpå för att beskriva det beteende som intressenter faktiskt godkänner. Eftersom REST-slutpunkter sitter exakt på det acceptansskiktet passar de naturligt för BDD.

Vad är REST API-testning?

I takt med att REST har blivit en populär metod för att bygga API:er har det blivit lika viktigt att automatisera REST API-testfall tillsammans med UI-testfall. API-testning innebär att testa CRUD-åtgärder (Create-Read-Update-Delete) med metoderna POST, GET, PUT respektive DELETE.

Vad är Behave?

Behave är en av de populära Python BDD-testramverk. Så här fungerar Behave:

  • Funktionsfiler skrivs av din affärsanalytiker, sponsor eller den som äger beteendescenarierna. En funktionsfil använder ett naturligt språkformat som beskriver en funktion, eller en del av en funktion, med representativa exempel på förväntade resultat.
  • Dessa scenariosteg är mappade till stegimplementeringar skrivna i Python.
  • Valfritt kan miljökontroller köra kod före och efter steg, scenarier, funktioner eller hela körningen.

När rollerna för varje fil är klara kan ramverket nu installeras.

Ställa in BDD Testing Framework Behave on Windows

Installation:

Projektinställning:

  • Skapa ett nytt projekt
  • Skapa följande katalogstruktur

Projektinställning

Skärmdumpen ovan visar layouten som Behave förväntar sig: a pass katalog som innehåller funktionsfilerna och en kapslad steg katalogen som innehåller Python implementeringar. Behave upptäcker båda med namn, så mappnamnen måste matcha exakt.

Funktionsfiler:

Nu ska vi bygga funktionsfilen Sample_REST_API_Testing.feature, med funktionen CRUD-åtgärder på tjänsten 'inlägg'.

I detta exempel används https://jsonplaceholder.typicode.com/ inlägg exempel på REST-tjänst, ett gratis falskt API som accepterar skrivförfrågningar och returnerar realistiska svar utan att behålla ändringarna.

Exempel 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

På samma sätt kan du skriva de återstående scenarierna enligt följande:

Projektinställning

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"

Steg Implementering

Nu, för funktionsstegen som används i scenarierna ovan, kan du skriva implementeringar i Python filer i katalogen "steps".

Behave-ramverket identifierar stegfunktionen genom att matcha dekoratörer mot featurefilspredikatet. Till exempel, en Med tanke på predikat i ett funktionsfilscenario söker efter en stegfunktion som bär @given dekoratör. Samma matchning sker för När och SedanI fallet med 'Men' och 'Och' tar stegfunktionen samma dekorator som dess föregående steg. Om till exempel 'Och' följer efter en given, är den matchande stegfunktionens dekorator @given.

Till exempel kan steget När för POST implementeras enligt följande. Observera hur "application/json" skickas från funktionsfilen till den spetsade platshållaren — detta kallas 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å samma sätt gäller implementeringen av de andra stegen i steget Python filen ser ut så här:

Steg 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

⚠️ Obs: Jämföra statuskoder med == snarare än is frågor. Den is operatorn testar objektidentitet, inte likhet, och Python 3.8 och senare höja en SyntaxVarning: ”är” med en bokstav för det mönstret. Alla påståenden som skrivs som assert code is 201 bör skrivas om som assert code == 201.

Kör testerna

Utvecklingen av testskriptet är klar, så låt oss köra testerna. Kör följande kommando vid kommandotolken för att köra 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

Detta visar testkörningsresultaten enligt följande:

Kör testerna

Rapportvisning på konsolen

Konsolutdata är praktiskt under utveckling, men intressenter föredrar vanligtvis en läsbar rapport, vilket Allure tillhandahåller.

Rapport

Installera först formateringsverktyget Allure Behave och kommandoradsverktyget Allure. Formateringsverktyget är en Python paketet; kommandoradsverktyget är en separat nedladdning som beskrivs i Dokumentation av 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

Detta genererar testresultatsrapporten i ett presentabelt och informativt format som detta:

Rapport

Testrapport i HTML-format

Testrapport i HTML-format

Testrapport som visar individuella scenarieresultat

En fungerande svit är bara användbar om den förblir läsbar allt eftersom API:et växer, vilket är där funktionsfilsdisciplin lönar sig.

Bästa praxis för att skriva Behave Feature-filer

En Behave-svit försämras snabbt när scenarier beskriver klick och nyttolaster istället för beteende. Metoderna nedan gör att funktionsfilerna är läsbara för affärsintressenter samtidigt som de hållsping d Python lager som är underhållbart för ingenjörer.

  1. Beskriv beteende, inte implementering. Skriv ”Givet att kunden har tillräckligt saldo”, inte ”Givet att saldokolumnen är lika med 500”. Funktionsfilen anger vad systemet gör; stegfilen anger hur det kontrolleras.
  2. Behåll ett beteende per scenario. Ett scenario som anger en statuskod, en svarstext och en databasrad är egentligen tre scenarier. Att dela upp dem gör att fel pekar direkt på orsaken.
  3. Flytta delade inställningar till bakgrunden. Exemplet ovan använder Background: Given I set sample REST API url så att varje scenario ärver basen URL utan att upprepa det.
  4. Parametrisera istället för att duplicera. Stabila platshållare som t.ex. "{request_name}" låt en stegfunktion hantera GET-, PUT- och DELETE-påståenden, vilket är anledningen till att exemplet behöver betydligt färre stegdefinitioner än scenarier.
  5. Använd scenarioöversikt för datavariationer. När samma beteende måste bevisas för flera ingångar, en Examples: Tabellen är tydligare än kopierade scenarier.
  6. Undvik beroenden mellan scenarier. Behave garanterar inte att ett POST-scenario körs före ett GET-scenario i varje konfiguration. Varje scenario bör skapa det tillstånd det behöver.
  7. Ersätt globala värden på modulnivå med kontext. Exemplet lagrar slutpunkter och svar i modulordböcker för korthets skull. I produktionssviter, lagra dem på Behaves context objekt så att tillståndet återställs korrekt mellan scenarier.
  8. Taggscenarier för selektiva körningar. Taggar som @smoke or @regression tillåter behave --tags=@smoke, vilket håller pipeline-feedbacken snabb.

Att tillämpa dessa vanor från den första funktionsfilen gör att en BDD-svit behåller sitt värde långt bortom de initiala CRUD-exemplen. Team som utökar denna metod ytterligare kombinerar den ofta med Cucumber för JVM-projekt eller Postman för utforskande API-kontroller.

Vanliga frågor

Båda läser Gherkin-funktionsfiler. Cucumber binder steg till Java, Ruby, eller JavaScript, medan Behave binder dem till PythonVälj den som matchar din applikationsstack så att stegdefinitionerna återanvänder befintliga testverktyg.

Ja. Behave returnerar en avslutningskod som inte är noll när ett scenario misslyckas, så Jenkins, GitLab CI eller GitHub Actions kan misslyckas automatiskt med bygget. Publicera Allure-resultatmappen som en byggartefakt för rapportering.

Ja. AI assistenter utarbetar Givet-När-Sedan-scenarier från användarberättelser eller en OpenAPI-specifikation. RevVisa utdata för saknade negativa fall och affärsregler, eftersom genererade scenarier ofta bara täcker den lyckliga vägen.

AI-verktyg upptäcker dubbletter eller nästan dubbletter, föreslår återanvändbara parametriserade steg och klustrar instabila fel efter sannolik orsak. Detta minskar den manuella granskningsinsatsen när funktionsfiler mångdubblas mellan team.

Hämta token en gång i environment.py before_all-hooken, lagra den på context-objektet och bifoga den som en Authorization-header i varje förfrågningssteg. Spara autentiseringsuppgifter i miljövariabler, aldrig i funktionsfiler.

Sammanfatta detta inlägg med: