What is BDD Testing? Framework Example
What is BDD (Behavior Driven Development) Testing?
BDD (Behavior-driven development) Testing is a technique of agile software development and is as an extension of TDD, i.e., Test Driven Development. In BDD, test cases are written in a natural language that even non-programmers can read.
How BDD Testing works?
Consider you are assigned to create Funds Transfer module in a Net Banking application.
There are multiple ways to test it
- Fund Transfer should take place if there is enough balance in source account
- Fund Transfer should take place if the destination a/c details are correct
- Fund Transfer should take place if transaction password / rsa code / security authentication for the transaction entered by user is correct
- Fund Transfer should take place even if it’s a Bank Holiday
- Fund Transfer should take place on a future date as set by the account holder
The Test Scenario become more elaborate and complex as we consider additional features like transfer amount X for an interval Y days/months , stop schedule transfer when the total amount reaches Z , and so on
The general tendency of developers is to develop features and write test code later. As, evident in above case, Test Case development for this case is complex and developer will put off Testing till release , at which point he will do quick but ineffective testing.
To overcome this issue (Behavior Driven Development) BDD was conceived. It makes the entire testing process easy for a developer
In BDD, whatever you write must go into Given-When-Then steps. Lets consider the same example above in BDD
Given that a fund transfer module in net banking application has been developed And I am accessing it with proper authentication
WhenI 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
Isn’t it easy to write and read and understand? It covers all possible test cases for the fund transfer module and can be easily modified to accommodate more. Also, it more like writing documentation for the fund transfer module.
What is REST API Testing?
As REST has become quite a popular style for building APIs nowadays, it has become equally important to automate REST API test cases along with UI test cases. So basically, these REST API testing involves testing of CRUD (Create-Read-Update-Delete) actions with methods POST, GET, PUT, and DELETE respectively.
What is Behave?
Behave is one of the popular Python BDD test frameworks.
Let’s see how does Behave function:
Feature files are written by your Business Analyst / Sponsor / whoever with your behavior scenarios in it. It has a natural language format describing a feature or part of a feature with representative examples of expected outcomes
These Scenario steps are mapped with step implementations written in Python.
And optionally, there are some environmental controls (code to run before and after steps, scenarios, features or the whole shooting match).
Let’s get started with the setup of our automation test framework with Behave:
Setting up BDD Testing Framework Behave on Windows
Installation:
- Download and Install Python 3 from https://www.python.org/
- Execute the following command on command prompt to install behave
- pip install behave
- IDE: I have used PyCharm Community Edition https://www.jetbrains.com/pycharm/download
Project Setup:
- Create a New Project
- Create the following Directory Structure:
Feature Files:
So let’s build our feature file Sample_REST_API_Testing.feature having feature as Performing CRUD operations on ‘posts’ service.
In our example, I have used http://jsonplaceholder.typicode.com/ posts sample REST Service.
Example POST scenario
Scenario: POST post example ->Here we are considering creating new post item using 'posts' service Given: I set post posts API endpoint ->This is prerequisite for the test which is setting URL of posts service When: I set HEADER param request content type as "application/json." And set request body And send POST HTTP request ->This is actual test step of sending a post request Then: Then I receive valid HTPP response code 201 And Response body "POST" is non-empty-> This is verification of response body
Similarly, you can write the remaining Scenarios as follows:
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."
Steps Implementation
Now, for feature Steps used in the above scenarios, you can write implementations in Python files in the “steps” directory.
Behave framework identifies the Step function by decorators matching with feature file predicate. For Example, Given predicate in Feature file Scenario searches for step function having decorator “given.” Similar matching happens for When and Then. But in the case of ‘But,’ ‘And,’ Step function takes decorator same as it’s preceding step. For Example, If ‘And’ comes for Given, matching step function decorator is @given.
For Example, when step for POST can be implemented as follows:
@when (u'I Set HEADER param request content type as "{header_conent_type}"') Mapping of When, here notice “application/json” is been passed from feature file for "{header_conent_type}” . This is called as parameterization def step_impl (context, header_conent_type): This is step implementation method signature request_headers['Content-Type'] = header_conent_type Step implementation code, here you will be setting content type for request header
Similarly, the implementation of other steps in the step python file will look like this:
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 = 'http://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_conent_type}"') def step_impl(context, header_conent_type): request_headers['Content-Type'] = header_conent_type #You may also include "And" or "But" as a step - these are renamed by behave to take the name of their preceding step, so: @when(u'Set request Body') def step_impl(context): request_bodies['POST']={"title": "foo","body": "bar","userId": "1"} #You may also include "And" or "But" as a step - these are renamed by behave to take the name of their preceding step, so: @when(u'Send POST HTTP request') def step_impl(context): # sending get request and saving response as response object response = requests.post(url=api_endpoints['POST_URL'], json=request_bodies['POST'], headers=request_headers) #response = requests.post(url=api_endpoints['POST_URL'], headers=request_headers) #https://jsonplaceholder.typicode.com/posts # extracting response text response_texts['POST']=response.text print("post response :"+response.text) # extracting response status_code statuscode = response.status_code response_codes['POST'] = statuscode @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'] is 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']) #You may also include "And" or "But" as a step - these are renamed by behave to take the name of their preceding step, so: @when(u'Send GET HTTP request') def step_impl(context): # sending get request and saving response as response object response = requests.get(url=api_endpoints['GET_URL'], headers=request_headers) #https://jsonplaceholder.typicode.com/posts # extracting response text response_texts['GET']=response.text # extracting response status_code statuscode = response.status_code response_codes['GET'] = statuscode @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] is 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): # sending get request and saving response as response object # response = requests.post(url=api_endpoints['POST_URL'], headers=request_headers) #https://jsonplaceholder.typicode.com/posts response = requests.put(url=api_endpoints['PUT_URL'], json=request_bodies['PUT'], headers=request_headers) # extracting response text response_texts['PUT'] = response.text print("update response :" + response.text) # extracting response status_code statuscode = response.status_code response_codes['PUT'] = statuscode #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): # sending get request and saving response as response object response = requests.delete(url=api_endpoints['DELETE_URL']) # response = requests.post(url=api_endpoints['POST_URL'], headers=request_headers) #https://jsonplaceholder.typicode.com/posts # extracting response text response_texts['DELETE'] = response.text print("DELETE response :" + response.text) # extracting response status_code statuscode = response.status_code response_codes['DELETE'] = statuscode #END DELETE
Running the Tests
Now, we are done with our test script development part, so let’s run our tests:
Execute the following command on command prompt to run our feature file
C: \Programs\Python\Python37>behave -f pretty C:\<your project path>\features\feature_files_folder\Sample_REST_API_Testing.feature
This will display test execution results as follows:
Report display on the console
Let’s see one more cool thing here.
As users always prefer to see test results in a more readable and presentable format, let’s have reports in HTML format with the help of Allure.
Reports
First, you need to install Allure Behave formatter [https://docs.qameta.io/allure-report/]:
And now execute the following command:
For reports
>behave -f json -o<path-to-your-report-folder> Sample_REST_API_Testing.feature
<allure-bin folder path>> allure serve <path-to-your-report-folder>
This will generate your test results report in the presentable and informative format like this:
Test Report in HTML Format
Test Report displaying individual Scenario result
Summary
- BDD is Behavior-driven development. It is one of the techniques of agile software development.
- REST has become quite a popular style for building APIs nowadays, it has become equally important to automate REST API test cases along with UI test cases.
- BDD has a natural language format describing a feature or part of a feature with representative examples of expected outcomes
- Behave framework identifies the Step function by decorators matching with feature file predicate
- Examples of BDD Testing Frameworks: 1) Cucumber 2) SpecFlow 3) Quantum 4) JBehave 5) Codeception