什么是BDD测试?框架示例
什么是BDD(行为驱动开发)测试?
BDD(行为驱动开发)测试是一种敏捷软件开发技术,是TDD(测试驱动开发)的扩展。在BDD中,测试用例以自然语言编写,即使是非程序员也能阅读。
BDD测试如何工作?
假设你被指派在网上银行应用程序中创建资金转账模块。
有多种方法可以测试它
- 如果源账户有足够的余额,则应进行资金转账
- 如果目标账户详情正确,则应进行资金转账
- 如果用户输入的交易密码/rsa代码/交易安全认证正确,则应进行资金转账
- 即使是银行假日,也应进行资金转账
- 应在账户持有人设定的未来日期进行资金转账
当我们考虑更多功能,例如在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
安装
- 从https://pythonlang.cn/下载并安装Python 3
- 在命令提示符下执行以下命令以安装behave
- pip install behave
- IDE:我使用了PyCharm Community Edition https://www.jetbrains.com/pycharm/download
项目设置
- 创建一个新项目
- 创建以下目录结构
功能文件
因此,让我们构建我们的功能文件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
同样,你可以按如下方式编写其余场景
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 文件中其他步骤的实现将如下所示
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
这将显示测试执行结果如下
控制台上的报告显示
我们再来看看一个很棒的东西。
由于用户总是喜欢以更具可读性和更具表现力的格式查看测试结果,因此让我们借助 Allure 以 HTML 格式生成报告。
报告
首先,你需要安装 Allure Behave 格式化程序 [https://docs.qameta.io/allure-report/]
现在执行以下命令
用于报告
>behave -f json -o<你的报告文件夹路径> Sample_REST_API_Testing.feature
这将以这种可呈现且信息丰富的格式生成你的测试结果报告
HTML 格式的测试报告
显示单个场景结果的测试报告
摘要
- BDD 是行为驱动开发。它是敏捷软件开发技术之一。
- REST 如今已成为构建 API 的流行风格,因此自动化 REST API 测试用例以及 UI 测试用例变得同样重要。
- BDD 具有自然语言格式,描述了一个功能或功能的一部分,并附有预期结果的代表性示例
- Behave 框架通过与功能文件谓词匹配的装饰器来识别步骤函数
- BDD 测试框架示例:1) Cucumber 2) SpecFlow 3) Quantum 4) JBehave 5) Codeception