BDD 行为驱动开发测试
项目描述
behaving
behaving 是一个基于 behave 和 splinter 的行为驱动开发(BDD)测试框架。
behaving 使用Python编写,类似于 Cucumber。 behaving 添加了用于多用户Web/电子邮件/SMS/GCM交互的步骤库,并提供Python的 behaving 命名空间,以便独立的步骤库可以协同工作。
请参阅 behave 的优秀 文档,了解如何使用它,如何编写自定义步骤以及如何扩展 behaving。
你好世界
开始使用 behaving 非常简单。在某个Python模块中,添加你的 features,每个 feature 包含一个或多个场景。这些 features 是扩展名为 .feature
的Gherkin语言文件。在同一目录中,你应该有一个步骤模块,它导入 behaving 步骤以及你的自定义步骤(有关设置部分的更多内容将在后面介绍)。以下是一个基本示例
Feature: Text presence
Background:
Given a browser
Scenario: Search for BDD
When I visit "http://www.wikipedia.org/"
And I fill in "search" with "BDD"
And I press "go"
Then I should see "Behavior-driven development" within 5 seconds
电子邮件,短信 & GCM(Google Cloud Messaging)
虽然 behaving 的重点是Web,但它还包括对邮件、短信和GCM服务器的简单模拟。这些模拟附带了一组步骤,允许你执行以下操作
Feature: Email & SMS
Scenario: Click link in an email
Given a browser
When I send an email to "foo@bar.com" with subject "Hello" and body "Try out this website at http://google.com"
And I click the link in the email I received at "foo@bar.com"
Then the browser's URL should be "http://google.com/"
Scenario: Receive SMS with body
When I send an sms to "+4745690001" with body "Hello world"
Then I should receive an sms at "+4745690001" containing "world"
Scenario: Receive GCM Notification
When I send a gcm message "{"to":"deviceID", "data": {"message": "Foo Bar", "badge": 6}}"
Then I should receive a gcm notification at "deviceID" containing "{'data': {'message': 'Foo Bar'}}"
通常,发送电子邮件/短信/通知的是您的网络应用程序,测试它主要涉及到配置应用程序向模拟服务器发送电子邮件/短信/通知。
人物 & 状态
如今,许多网络应用程序都依赖于多用户交互。为了帮助您处理这些交互,behaving 使用了 人物 的概念。测试中的一个人物在其自己的浏览器实例中运行,您可以有多个同时运行的人物(及其浏览器实例)。您可以通过调用以下方式在人物之间切换
Given "PersonaName" as the persona
人物通常也作为简单的字典实现,允许它们携带状态,在场景内保存和重用变量。当人物首次调用时,它被创建为一个空字典。您可以通过设置预定义的人物值。
让我们以熟悉的中土世界角色作为测试用户。在设置测试环境(详情见设置_部分)时,我们将基本变量设置为测试中可能需要的,如下所示
PERSONAS = {
'Frodo': dict(
fullname=u'Frodo Baggins',
email=u'frodo@shire.com',
password=u'frodopass',
mobile='+4745690001',
address: {
street: "The Shire",
zip: "4321"
}
),
'Gandalf': dict(
fullname=u'Gandalf the Grey',
email=u'gandalf@wizardry.com',
password=u'gandalfpass',
mobile='+4745690004',
address: {
street: "Rivendell street 1",
zip: "1234"
}
),
...
}
def before_scenario(context, scenario):
...
context.personas = PERSONAS
在测试中,给定一个人物,您现在可以使用 $var_name
来访问人物的变量。您也可以在人物上设置新的变量。所以以下内容
Given "Gandalf" as the persona
When I fill in "name" with "$fullname"
And I fill in "street" with "$address.street"
And I set "title" to the text of "document-title"
And I fill in "delete" with "$title"
And I set "address.country" to the text of "country"
And I set "postaddress" to:
"""
$fullname
$address.street, $address.zip, $address.country
"""
将填写具有 id name
的字段,为 Gandalf the Grey
,为 street
设置 Rivendell street 1
,将变量 title
设置为具有 id document-title
的元素的文本,并重用变量 title
来填写具有 id delete
的字段。它还会将具有 id "country" 的字段的值存储在 address[country
] 中。$var_name
模式也可以用于期望文本体的步骤收到的文本中,这意味着 postaddress
人物变量将包含 Gandalf 的完整蜗牛邮件邮寄地址,格式良好地显示在多行中。
Hello Persona 示例
让我们假设以下场景(来自真实示例)。Crypho 是一个在线消息/共享网站,为用户提供端到端加密的实时通信。为了测试 Crypho,编写了 behaving。
Crypho 中,团队在 空间 中协作。要邀请某人加入一个 空间,邀请人必须与邀请者共享一个令牌,以便双方可以验证对方的身份。
Feature: Frodo invites Gandalf to The Shire space
Given state "the-shire"
Scenario: Frodo invites Gandalf to The Shire
Given "Gandalf" as the persona
When I log in
在场景开始之前,自定义步骤 Given state "the-shire"
执行。这预先加载了 db 中的数据,设置了服务器等。然后执行场景
首先,Gandalf 登录。步骤 Given "Gandalf" as the persona
启动属于人物 Gandalf 的浏览器。以下步骤 When I log in
是自定义步骤,如下定义
@when('I log in')
def log_in(context):
assert context.persona
context.execute_steps(u"""
When I go to Home
Then I should see an element with id "email" within 2 seconds
When I fill in "email" with "$email"
And I press "send-sms"
Then I should see "We have sent you an SMS with a security code" within 2 seconds
And I should receive an sms at "$mobile"
And "token" should be enabled
When I parse the sms I received at "$mobile" and set "Your Crypho code is {token}"
And I fill in "token" with "$token"
And I fill in "password" with "$password"
And I press "login"
Then I should see "Crypho" within 5 seconds
""")
观察上面当前人物(Gandalf)如何解析收到的短信并将其保存为 "token"。后来,Gandalf 重新使用它来填写双因素认证字段。
现在 Gandalf 已登录,测试继续进行到 Frodo。Frodo 将登录,并邀请 Gandalf 加入一个私人空间。
Given "Frodo" as the persona
When I log in
And I click the link with text that contains "My spaces"
And I click the link with text that contains "The Shire"
And I press "invite-members"
Then I should see "Invite members" within 1 seconds
When I fill in "invitees" with "gandalf@wizardry.com"
And I fill in "invitation-message" with "Come and join us!"
And I press "send-invitations"
Then I should see "Your invitations have been sent" within 2 seconds
发送邀请后,我们切换回 Gandalf 的浏览器,他应该在他的浏览器中收到通知,以及一封电子邮件。然后他继续向 Frodo 发送包含令牌的短信,Frodo 完成邀请。
Given "Gandalf" as the persona
Then I should see "Your invitations have been updated" within 2 seconds
And I should receive an email at "gandalf@wizardry.com" containing "Frodo Baggins has invited you to join a private workspace in Crypho"
When I click the link with text that contains "Invitations"
And I click the link with text that contains "Pending invitations"
Then I should see "Come and join us!"
When I set "token" to the text of "invitation-token"
And I send an sms to "45699900" with body "$token"
Given "Frodo" as the persona
Then I should receive an sms at "45699900"
When I set "FrodoToken" to the body of the sms I received at "45699900"
And I click the link with text that contains "Invitations"
And I click the link with text that contains "Enter authorization token"
And I fill in "auth-token" with "$FrodoToken"
And I press "Submit"
Then I should see "The invitation has been accepted." within 5 seconds
And I should see "Gandalf the Grey has joined the space, invited by Frodo Baggins" within 10 seconds
您可以在以下视频 此处 看到测试的实际操作。
设置测试环境
首先,使用 pip
或 easy_install
安装 behaving。这将安装依赖项并创建 behave
脚本,您可以用它来调用测试。如果您更喜欢使用 buildout,从其存储库克隆包本身,它已经包含一个 buildout 配置。
通常,您将有一个包含所有功能步骤的文件夹。例如,以下目录结构
features/
features/mytest.feature
features/myothertest.feature
features/environment.py
features/steps/
features/steps/steps.py
在步骤目录中,您需要导入所需的 behaving 步骤。您也可以定义自己的步骤。因此,steps.py
可能看起来像
from behave import when
from behaving.web.steps import *
from behaving.sms.steps import *
from behaving.mail.steps import *
from behaving.notifications.gcm.steps import *
from behaving.personas.steps import *
@when('I go to home')
def go_to_home(context):
context.browser.visit('https://web/')
在 environment.py
中,您可以指定设置以及需要在测试各个阶段发生的事情,例如在所有事情之前和之后、一个功能运行或一个场景运行之前。为了方便起见,您可以导入并重用 behaving.environment
,它将执行默认操作,如场景结束后关闭所有浏览器,清理电子邮件文件夹等。
如果您不需要短信功能,也可以单独使用 behaving.web.environment
、behaving.mail.environment
、behaving.sms.environment
和 behaving.personas.environment
。
以下是一个示例环境,它仅设置一些变量,然后依赖默认操作来完成各个阶段的操作:
import os
from behaving import environment as benv
PERSONAS = {}
def before_all(context):
import mypackage
context.attachment_dir = os.path.join(os.path.dirname(mypackage.__file__), 'tests/data')
context.sms_path = os.path.join(os.path.dirname(mypackage.__file__), '../../var/sms/')
context.gcm_path = os.path.join(os.path.dirname(mypackage.__file__), '../../var/gcm/')
context.mail_path = os.path.join(os.path.dirname(mypackage.__file__), '../../var/mail/')
benv.before_all(context)
def after_all(context):
benv.after_all(context)
def before_feature(context, feature):
benv.before_feature(context, feature)
def after_feature(context, feature):
benv.after_feature(context, feature)
def before_scenario(context, scenario):
benv.before_scenario(context, scenario)
context.personas = PERSONAS
def after_scenario(context, scenario):
benv.after_scenario(context, scenario)
以下变量受支持,并且可以设置以覆盖默认值:
screenshots_dir
(屏幕截图将保存的路径。如果设置,则场景中的任何失败都将导致在失败发生时浏览器屏幕的截图。)attachment_dir
(文件附件可以找到的路径)sms_path
(smsmock 保存短信的路径。默认为current_dir/sms
)gcm_path
(gcmmock 保存 gcm 通知的路径。默认为current_dir/gcm
)mail_path
(mailmock 保存邮件的路径。默认为current_dir/mail
)default_browser
default_browser_size
(元组(宽度,高度),应用于创建的每个浏览器)max_browser_attempts
(在浏览器创建失败时重试创建浏览器的次数)remote_webdriver_url
(指向您的 selenium hub url 或远程 webdriver。默认为None
)browser_args
(在创建浏览器时使用的额外关键字参数的字典)base_url
(浏览器的基 url,允许您使用相对路径)accept_ssl_certs
(设置为True
将接受自签名/无效证书。默认为None
)
您可以通过运行以下命令来执行测试:
./bin/behave ./features
对于 chrome 和 docker 问题,以下代码很有用
from selenium.webdriver.chrome.options import Options
chrome_options = Options()
chrome_options.add_argument('--no-sandbox')
context.browser_args = {
'options': chrome_options
}
邮件、GCM 和 SMS 模拟服务器
当安装 behaving 时,它创建三个脚本来帮助您测试邮件、gcm 和短信,分别是 mailmock
、gcmmock
和 smsmock
。您可以直接在运行测试之前调用它们,它们都接受一个端口号以及输出数据的目录作为参数。例如,
./bin/smsmock -p 8081 -o ./var/sms
./bin/gcmmock -p 8082 -o ./var/notifications/gcm
./bin/mailmock -p 8083 -o ./var/mail [--no-stdout]
behaving.web
支持的匹配器/步骤
-
浏览器
- 给定一个浏览器 [打开默认浏览器,即 Firefox]
- 给定
brand
作为默认浏览器 [将默认浏览器设置为brand
,这是在远程 webdriver 或 Firefox、Chrome、Safari 中使用的浏览器名称] - 给定电子应用程序 "
app_path
" [用于基于电子的桌面应用程序] - 给定浏览器 "
name
" [打开名为name
的浏览器] - 当我重新加载
- 当我后退
- 当我前进
- 当我将浏览器调整到
width
xheight
- 当我调整视口大小为
width
xheight
- 当我截图 [如果环境中的
screenshots_dir
已设置,则将保存浏览器截图。如果设置了screenshots_dir
,则所有失败的测试都将产生截图。] - 当我执行脚本 "
script
" - 当我将 cookie "
key
" 设置为 "value
" - 当我删除 cookie "
key
" - 当我删除所有 cookie
- 当我关闭浏览器 "
name
"
-
框架
- 当我切换到具有 css "
css
" 的框架 - 当我切换回主页
- 当我切换到具有 css "
-
窗口
- 当我打开一个名为 "
name
" 的窗口,地址为 "url
" 时 - 当我将当前窗口命名为 "
name
" - 当我切换到名为 "
name
" 的窗口
- 当我打开一个名为 "
-
URLs
- 给定基本 URL "
url
" [设置基本 URL 为url
,或者在environment.py
中直接设置context.base_url
] - 当我访问 "
url
" - 当我转到 "
url
" - 当我解析 URL 路径并设置 "
{expression}
" - 那么浏览器的 URL 应该是 "
url
" - 那么浏览器的 URL 应该包含 "
text
" - 那么浏览器的 URL 不应该包含 "
text
"
- 给定基本 URL "
-
链接
- 当我点击链接到 "
url
" - 当我点击链接到包含 "
url
" 的 URL - 当我点击文本为 "
text
" 的链接 - 当我点击文本包含 "
text
" 的链接
- 当我点击链接到 "
-
文本、元素 & 类存在
-
当我等待
timeout
秒 -
当我显示 id 为 "
id
" 的元素 -
当我隐藏 id 为 "
id
" 的元素 -
文本
- 那么我应该看到 "
text
" - 那么我不应该看到 "
text
" - 那么我应该看到 "
text
",在timeout
秒内 - 那么我不应该看到 "
text
",在timeout
秒内
- 那么我应该看到 "
-
ID
- 那么我应该看到一个 id 为 "
id
" 的元素 - 那么我不应该看到一个 id 为 "
id
" 的元素 - 那么我应该看到一个 id 为 "
id
" 的元素,在timeout
秒内 - 那么我不应该看到一个 id 为 "
id
" 的元素,在timeout
秒内
- 那么我应该看到一个 id 为 "
-
-
CSS
- 存在
- 那么我应该看到一个具有 CSS 选择器 "
selector
" 的元素 - 那么我不应该看到一个具有 CSS 选择器 "
selector
" 的元素 - 那么我应该看到一个具有 CSS 选择器 "
selector
" 的元素,在timeout
秒内 - 那么我不应该看到一个具有 CSS 选择器 "
selector
" 的元素,在timeout
秒内 - 那么我应该看到
n
个具有 CSS 选择器 "css
" 的元素 - 那么我应该看到至少
n
个具有 CSS 选择器 "css
" 的元素,在timeout
秒内
- 那么我应该看到一个具有 CSS 选择器 "
- 可见性
- 那么具有 CSS 选择器 "
css
" 的元素应该是可见的 - 那么具有 CSS 选择器 "
css
" 的元素应该在timeout
秒内可见 - 那么具有 CSS 选择器 "
css
" 的元素不应该可见 - 那么具有 CSS 选择器 "
css
" 的元素应该在timeout
秒内可见 - 那么应该有 {n:d} 个具有 CSS 选择器 "
css
" 的元素是可见的 - 那么应该有 {n:d} 个具有 CSS 选择器 "
css
" 的元素在timeout
秒内是可见的 - 那么至少应该有 {n:d} 个具有 CSS 选择器 "
css
" 的元素是可见的 - 那么至少应该有 {n:d} 个具有 CSS 选择器 "
css
" 的元素在timeout
秒内是可见的
- 那么具有 CSS 选择器 "
- 元素上的类存在
- 那么具有 xpath "
xpath
" 的元素应该有类 "cls
" - 那么具有 xpath "
xpath
" 的元素不应该有类 "cls
" - 那么具有 xpath "
xpath
" 的元素应该在timeout
秒内有类 "cls
" - 那么具有 xpath "
xpath
" 的元素应该在timeout
秒内没有类 "cls
" - 那么 "
name
" 应该有类 "cls
" - 那么 "
name
" 不应该有类 "cls
" - 那么 "
name
" 应该在timeout
秒内有类 "cls
" - 那么 "
name
" 应该在timeout:d
秒内没有类 "cls
"
- 那么具有 xpath "
- XPath
- 那么我应该看到一个具有 xpath "
xpath
" 的元素 - 那么我不应该看到一个具有 xpath "
xpath
" 的元素 - 那么我应该看到一个具有 xpath "
xpath
" 的元素,在timeout
秒内 - 在
timeout
秒内,我不应看到一个具有 xpath "xpath
" 的元素
- 那么我应该看到一个具有 xpath "
- 存在
-
表单
- 当我在 "
name|id
" 中填写 "value
" - 当我在 "
name|id
" 中清除字段 - 当我将 "
value
" 键入 "name|id
" 中 [与填充相同,但发生较慢,触发键盘事件] - 当我从 "
name
" 中选择 "value
" - 当我选择 "
name|id
" - 当我取消选择 "
name|id
" - 当我切换 "
name|id
" - 当我从 "
name
" 中选择 "value
" - 当我按文本 "
text
" 从 "name
" 中选择 - 当我按下 "
name|id|text|innerText
" - 当我按下具有 xpath "
xpath
" 的元素 - 当我将文件 "
path
"附加到 "name
" 上 - 当我将具有 id "
id
" 的元素的内部 HTML 设置为 "contents
" [将具有 id "id
" 的contenteditable
元素的 HTML 设置为 "contents
"] - 当我将具有 class "
class
" 的元素的内部 HTML 设置为 "contents
" - 当我将具有 class "
class
" 的元素的内部 HTML 设置为 "contents
" - 当我将 "
KEY
" 发送到 "name
" - 当我在 "
name
" 上获得焦点 - 然后字段 "
name
" 应具有值 "value
" - 然后字段 "
name
" 应具有值 "value
" 在timeout
秒内 - 然后选择 "
name
" 应具有选项 "valueA, valueB
" 被选中 - 然后 "
name
" 应启用 - 然后 "
name
" 应禁用 - 然后 "
name
" 不应启用 - 然后 "
name
" 应有效 - 然后 "
name
" 应无效 - 然后 "
name
" 不应有效 - 然后 "
name
" 应为必填项 - 然后 "
name
" 不应为必填项
- 当我在 "
-
HTML 表格
-
然后具有 id "
id
" 的表应该是
| 表头1 | 表头2 | ... | 表头(m) |
| 单元格00 | 单元格01 | ... | 单元格0m |
| 单元格10 | 单元格11 | ... | 单元格1m |
...
| 单元格n0 | 单元格n1 | ... | 单元格nm | -
然后具有 xpath "
xpath
" 的表应该是
| 表头1 | 表头2 | ... | 表头(m) |
| 单元格00 | 单元格01 | ... | 单元格0m |
| 单元格10 | 单元格11 | ... | 单元格1m |
...
| 单元格n0 | 单元格n1 | ... | 单元格nm | -
然后具有 id "
id
" 的表应包含行
| 单元格00 | 单元格01 | ... | 单元格0m |
| 单元格10 | 单元格11 | ... | 单元格1m | -
然后具有 xpath "
xpath
" 的表应包含行
| 单元格00 | 单元格01 | ... | 单元格0m |
| 单元格10 | 单元格11 | ... | 单元格1m | -
然后具有 id "
id
" 的表不应包含行
| 单元格00 | 单元格01 | ... | 单元格0m |
| 单元格10 | 单元格11 | ... | 单元格1m | -
然后具有 xpath "
xpath
" 的表不应包含行
| 单元格00 | 单元格01 | ... | 单元格0m |
| 单元格10 | 单元格11 | ... | 单元格1m | -
然后表格中行
row_no
应该是
| 单元格00 | 单元格01 | ... | 单元格0m | -
然后表格中行
row_no
应该是
| 单元格00 | 单元格01 | ... | 单元格0m | -
然后表格中行
row_no
,列col_no
的值应该是 "value
" -
然后表格中行
row_no
,列col_no
的值应该是 "value
" -
然后表格中行
row_no
,列 "col_header
" 的值应该是 "value
" -
然后表格中行
row_no
,列 "col_header
" 的值应该是 "value
"
-
-
警报 & 提示
- 当我将 "
text
" 输入到警报中 - 当我接受警报 - 当我关闭警报 - 然后我应该看到警报 - 然后我应该看到警报在timeout
秒内 - 然后我应该看到包含 "text
" 的警报 - 然后我应该看到在timeout
秒内包含 "text
" 的警报
- 当我将 "
-
鼠标
- 当我将鼠标悬停在具有 xpath "
xpath
" 的元素上 - 当我将鼠标移出具有 xpath "
xpath
" 的元素
- 当我将鼠标悬停在具有 xpath "
-
下载
- 然后文件 "
filename
" 应在timeout
秒内下载 - 然后文件 "
filename
" 应该在timeout
秒内已下载
- 然后文件 "
-
Persona交互 & 变量
- 当我将 "
key
" 设置为 "id|name
" 的文本时 - 当我将 "
key
" 设置为具有 xpath "xpath
" 的元素的属性 "attr
" 时 - 当我评估脚本 "
script
" 并将结果分配给 "key
" 时
- 当我将 "
behaving.mail
支持的匹配器/步骤
- 当我在 "
address
" 收到的电子邮件中点击链接时 - 当我在 "
address
" 收到的电子邮件中解析并设置 "expression
" 时 - 当我清除电子邮件消息时
- 然后我应该收到 "
address
" 的电子邮件 - 然后我应该收到主题为 "
subject
" 的 "address
" 电子邮件 - 然后我应该收到包含 "
text
" 的 "address
" 电子邮件 - 然后我应该收到包含 "
filename
" 附件的 "address
" 电子邮件 - 然后我应该没有在 "
address
" 收到任何电子邮件
behaving.sms
支持的匹配器/步骤
- 当我在 "
number
" 收到的短信正文中设置 "key
" 时 - 当我在 "
number
" 收到的短信中解析并设置 "expression
" 时 - 然后我应该收到 "
number
" 的短信 - 然后我应该收到包含 "
text
" 的 "number
" 短信
behaving.notifications.gcm
支持的匹配器/步骤
- 当我发送 gcm 消息 "{"to":"deviceID", "data": {"message":"Foo Bar", "badge": 6}}"
- 然后我应该收到包含 "{'data': {'message': 'Foo Bar'}}" 的 "deviceID" gcm 通知
- 然后我应该收到 "deviceID" 的任何 gcm 通知
behaving.personas
支持的匹配器/步骤
- 给定 "
name
" 作为 persona - 当我将 "
key
" 设置为 "value
" 时 - 当我将 "
key
" 设置为
"""some longer body of text
通常是多行
""" - 当我将 persona "
source
" 克隆到 "target
" 时 - 然后 "
key
" 被设置为 "value
"
调试
- 当我在测试中暂停时
Docker 集成
提供了一个 Dockerfile
以及使用 docker-compose
的完整设置,以帮助您创建运行测试的 selenium grid 配置。此外,如果 VSCode 是您的事情,还包含 dev 容器配置。
此外,我们还在 docker hub 上为 linux/amd64
和 linux/arm64
平台提供了预构建的镜像。使用
docker pull behaving/behaving:latest
来拉取镜像。
运行 behaving 测试
您可以通过以下方式运行所有 behaving 测试
启动 docker compose
docker-compose up
在 behaving 容器中打开一个 shell
docker-compose exec behaving bash
运行 behaving 测试
behave tests/features
项目详情
下载文件
下载适用于您平台的文件。如果您不确定选择哪个,请了解有关 安装包 的更多信息。
源分布
构建分布
behaving-3.1.5.tar.gz 的哈希值
算法 | 哈希摘要 | |
---|---|---|
SHA256 | 34a65ddfaaefcc33f99c82078babe15f9526570d808fd52ccc6d67bb5ea23015 |
|
MD5 | 16142f58e299ef54b15e2bb39f72c616 |
|
BLAKE2b-256 | 37583c2ded91ec19f6a36fdf22da02172ba962d2c5c7d83819f36c5ba1bf5607 |
behaving-3.1.5-py3-none-any.whl 的哈希值
算法 | 哈希摘要 | |
---|---|---|
SHA256 | 50b189464ccfd5301b2eb431db98efa3bbd65ac2e18ce263f988b6a79f16aa2c |
|
MD5 | dc974d9f417e92e381694028c9d29dc9 |
|
BLAKE2b-256 | 98840d2eaa9880d9078ba2529d558d11b9eb6525e7f21d4663e3e433676f66cc |