什么是BDD测试?框架示例

什么是BDD(行为驱动开发)测试?

BDD(行为驱动开发)测试是一种敏捷软件开发技术,是TDD(测试驱动开发)的扩展。在BDD中,测试用例以自然语言编写,即使是非程序员也能阅读。

BDD测试如何工作?

假设你被指派在网上银行应用程序中创建资金转账模块。

有多种方法可以测试它

  1. 如果源账户有足够的余额,则应进行资金转账
  2. 如果目标账户详情正确,则应进行资金转账
  3. 如果用户输入的交易密码/rsa代码/交易安全认证正确,则应进行资金转账
  4. 即使是银行假日,也应进行资金转账
  5. 应在账户持有人设定的未来日期进行资金转账

当我们考虑更多功能,例如在Y天/月内转账X金额,当总金额达到Z时停止定期转账等等时,测试场景变得更加复杂

开发人员的普遍倾向是先开发功能,再编写测试代码。如上例所示,此案例的测试用例开发很复杂,开发人员会将测试推迟到发布,届时他将进行快速但不有效的测试。

为了克服这个问题,BDD(行为驱动开发)应运而生。它使整个测试过程对开发人员来说变得简单

在BDD中,你编写的所有内容都必须遵循Given-When-Then步骤。让我们考虑上面相同的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

是不是很容易编写、阅读和理解?它涵盖了资金转账模块的所有可能测试用例,并且可以轻松修改以适应更多情况。此外,它更像是为资金转账模块编写文档。

什么是REST API测试?

由于REST如今已成为构建API的流行风格,因此自动化REST API测试用例以及UI测试用例变得同样重要。因此,这些REST API测试主要涉及使用POST、GET、PUT和DELETE方法分别测试CRUD(创建-读取-更新-删除)操作。

什么是Behave?

Behave是流行的Python BDD测试框架之一。

让我们看看Behave是如何运作的

功能文件由你的业务分析师/发起人/任何人在其中编写,包含你的行为场景。它具有自然语言格式,描述了一个功能或功能的一部分,并附有预期结果的代表性示例

这些场景步骤与用Python编写的步骤实现相映射。

并且,可选地,还有一些环境控制(在步骤、场景、功能或整个匹配之前和之后运行的代码)。

让我们开始使用Behave设置我们的自动化测试框架

在Windows上设置BDD测试框架Behave

安装

项目设置

  • 创建一个新项目
  • 创建以下目录结构

Project Setup

功能文件

因此,让我们构建我们的功能文件Sample_REST_API_Testing.feature,其功能是对“帖子”服务执行CRUD操作。

在我们的示例中,我使用了http://jsonplaceholder.typicode.com/ posts 示例 REST 服务。

POST场景示例

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	

同样,你可以按如下方式编写其余场景

Project Setup

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”目录中的 Python 文件中编写实现。

Behave 框架通过与功能文件谓词匹配的装饰器来识别步骤函数。例如,功能文件场景中的 Given 谓词会搜索带有装饰器“given”的步骤函数。When 和 Then 也会发生类似的匹配。但对于“But”和“And”的情况,步骤函数采用与其前一个步骤相同的装饰器。例如,如果“And”出现在 Given 之后,则匹配的步骤函数装饰器为 @given。

例如,POST 的步骤可以实现如下

@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

同样,步骤 Python 文件中其他步骤的实现将如下所示

Steps Implementation

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

运行测试

现在,我们的测试脚本开发部分已经完成,所以让我们运行测试

在命令提示符下执行以下命令来运行我们的功能文件

C:\Programs\Python\Python37>behave -f pretty C:\<你的项目路径>\features\feature_files_folder\Sample_REST_API_Testing.feature

这将显示测试执行结果如下

Running The Tests

控制台上的报告显示

我们再来看看一个很棒的东西。

由于用户总是喜欢以更具可读性和更具表现力的格式查看测试结果,因此让我们借助 Allure 以 HTML 格式生成报告。

报告

首先,你需要安装 Allure Behave 格式化程序 [https://docs.qameta.io/allure-report/]

现在执行以下命令

用于报告

>behave -f json -o<你的报告文件夹路径> Sample_REST_API_Testing.feature

> allure serve <你的报告文件夹路径>

这将以这种可呈现且信息丰富的格式生成你的测试结果报告

Reports

HTML 格式的测试报告

Test Report in HTML Format

显示单个场景结果的测试报告

摘要

  • BDD 是行为驱动开发。它是敏捷软件开发技术之一。
  • REST 如今已成为构建 API 的流行风格,因此自动化 REST API 测试用例以及 UI 测试用例变得同样重要。
  • BDD 具有自然语言格式,描述了一个功能或功能的一部分,并附有预期结果的代表性示例
  • Behave 框架通过与功能文件谓词匹配的装饰器来识别步骤函数
  • BDD 测试框架示例:1) Cucumber 2) SpecFlow 3) Quantum 4) JBehave 5) Codeception