What is BDD?

BDD is Behavior-driven development. It is one of the techniques of agile software development. It is also called 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.

In this BDD tutorial, you will learn:

In this BDD tutorial, we are going to see BDD Testing of REST API with Behave and Python.

But, before that, let's get clear on the following concepts first.

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 Behave test framework on Windows:

Installation:

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

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

 

YOU MIGHT LIKE: