基本概念
behave要被執(zhí)行缴守,需要運行在滿足下面兩種情況的目錄下
- 有feature files。這個feature files可以試由非技術人員編寫
- 一個“steps”目錄铝穷,steps里面包含python step implementation
還可以添加一些environmental controls。 比如
before
after
scenarios
features
一個可執(zhí)行的最小feature目錄為
features/
features/everything.feature
features/steps/
features/steps/steps.py
最小為一個features目錄 下面一個feature文件和一個steps目錄佳魔。steps里面包含了測試代碼曙聂。
一個更復雜的目錄為
features/
features/signup.feature
features/login.feature
features/account_details.feature
features/environment.py
features/steps/
features/steps/website.py
features/steps/utils.py
按照項目的不同模塊有不同的feature files,也有environment.py這樣的環(huán)境配置文件鞠鲜,在steps下面也有測試代碼
在命令行執(zhí)行behave
即可執(zhí)行所有測試宁脊,在console能看到測試結果
什么是feature files
feature file是指一個通常命名為 **.feature的純文本文件(UTF-8)。這個文件里面包含了用自然語言(Gherkin)描述的系統(tǒng)的功能特征贤姆。這些功能特征是具有代表性的期望結果榆苞。
Feature: Fight or flight
In order to increase the ninja survival rate,
As a ninja commander
I want my ninjas to decide whether to take on an
opponent based on their skill levels
Scenario: Weaker opponent
Given the ninja has a third level black-belt
When attacked by a samurai
Then the ninja should engage the opponent
Scenario: Stronger opponent
Given the ninja has a third level black-belt
When attacked by Chuck Norris
Then the ninja should run for his life
使用Gherkin來描述,具有以下特點:
- 結構是 feature下面有多個scenarios霞捡,scenarios下面是 Given When Then的表述方法
- feature的描述用的是 in order to ….. as a ….. I want …….
- behave會采用 Given When Then的步驟描述來map到測試代碼中對應的相同步驟描述坐漏。Given When Then是真實的執(zhí)行步驟
- Given: 在用戶或是外部系統(tǒng)對應用進行交互之前,我們要把系統(tǒng)處于一個已知的狀態(tài)。這個更加明確測試執(zhí)行的前提條件和所要求的系統(tǒng)狀態(tài)赊琳。要避免在GIVEN中涉及到用戶交互
- When:用戶或是外部系統(tǒng)所采取的與待測試系統(tǒng)的交互步驟街夭。這個交互能改變待測試系統(tǒng)的狀態(tài)
- Then: 待觀察的結果或是期望結果
- 除了Given When Then,我們還可以使用And或是But來做為步驟從而進行步驟描述的擴展躏筏。
Scenario: Stronger opponent
Given the ninja has a third level black-belt
When attacked by Chuck Norris
Then the ninja should run for his life
And fall off a cliff
Scenarios Outlines來實現數據驅動
在behave實現數據驅動測試可以使用Scenario Outline這個關鍵字配合Examples這個關鍵字使用板丽。 不同的數據會在相同的方法中執(zhí)行。
Scenario Outline: Blenders
Given I put <thing> in a blender,
when I switch the blender on
then it should transform into <other thing>
Examples: Amphibians
| thing | other thing |
| Red Tree Frog | mush |
Examples: Consumer Electronics
| thing | other thing |
| iPhone | toxic waste |
| Galaxy Nexus | toxic waste |
上面的例子中 使用了Scenario Outline來定義scenario 在GIVEN中使用 <varible name>
來定義數據變量趁尼。
例子中有兩個變量 <thing>
和<other thing>
這兩個變量名會在Examples中變?yōu)閠able的heading埃碱。Examples中的表格數據就是傳入方法的數據。
behave會運行表格中的每一行酥泞,每一行就代表著一個場景
Step Data 步驟數據
behave支持對feature文件中的step增加描述(context.text)和表格(context.table)來增加對feature的描述以及實現數據驅動
描述 Context.text : 在step后用兩個“”“包含的文本
Scenario: some scenario
Given a sample text loaded into the frobulator
"""
Lorem ipsum dolor sit amet, consectetur adipisicing elit, sed do
eiusmod tempor incididunt ut labore et dolore magna aliqua.
"""
When we activate the frobulator
Then we will find it similar to English
這個文本可以在測試代碼中使用Context.text來調用
表格**Context.table**: 在step后縮進表示的數據表格
Scenario: some scenario
Given a set of specific users
| name | department |
| Barry | Beer Cans |
| Pudey | Silly Walks |
| Two-Lumps | Silly Walks |
When we count the number of people in each department
Then we will find two people in "Silly Walks"
But we will find one person in "Beer Cans"
這個表格的數據可以再測試代碼中使用Context.table來調用砚殿,然后加到model里面。下面是example
@given('a set of specific users')
def step_impl(context):
for row in context.table:
model.add_user(name=row['name'], department=row['department'])
測試代碼 - Python Step Implementations
測試代碼實現在python文件中芝囤,這些python文件都需要被放入到”steps“文件夾下瓮具。測試代碼的文件名并不需要與feature文件的名稱一致。
Steps是通過修飾符來進行匹配的凡人。在測試代碼中名党,修飾符接受一串字符串,這串字符串要和feature文件中scenario使用的字符串一樣
feature文件中描述如下
Scenario: Search for an account
Given I search for a valid account
Then I will see the account details
則測試代碼文件中應該如下
@given('I search for a valid account')
def step_impl(context):
context.browser.get('http://localhost:8000/index')
form = get_element(context.browser, tag='form')
get_element(form, name="msisdn").send_keys('61415551234')
form.submit()
@then('I will see the account details')
def step_impl(context):
elements = find_elements(context.browser, id='no-account')
eq_(elements, [], 'account not found')
h = get_element(context.browser, id='account-head')
ok_(h.text.startswith("Account 61415551234"),
'Heading %r has wrong text' % h.text)
given when then 都不是必須的挠轴,都可以根據情況來使用
step修飾符即是 @given @then @when
”and“和”but“會被當做when/given/then的一部分來執(zhí)行传睹。比如一個and屬于given step下,那么and這個step就會變?yōu)間iven的一部分
如果你希望你的step能夠invoke另外一個step岸晦,可以使用Context.execute_steps()
@when('I do the same thing as before')
def step_impl(context):
context.execute_steps('''
when I press the big red button
and I duck
''')
”I do the same thing as before“ step會執(zhí)行另外兩個step
Step Parameters
在feature文件中step的描述可以包含參數 step parameter以方便重用
比如下面的scenario中 Then都是使用了差不多的描述
Scenario: look up a book
Given I search for a valid book
Then the result page will include "success"
Scenario: look up an invalid book
Given I search for a invalid book
Then the result page will include "failure"
唯一的不同就是 “success” 和 “failure”欧啤。這個可以使用測試代碼來處理
@then('the result page will include "{text}"')
def step_impl(context, text):
if text not in context.response:
fail('%r not in %r' % (text, context.response))
success和failure會作為text參數傳入方法里面,然后會判斷是否在context.response里面启上。context.response會在given step里面被加入一點text
這樣的話 就可以多次使用同一個描述邢隧,實現step的重用
behave使用下面三種parsers
- parse
- cfparse
- re
Context
在behave的step中都有一個context參數。這個參數的作用是用于存儲信息以及在不同的step中分享信息冈在。context在given when then三個level中都會run倒慧,并且有behave自動管理
當隨著測試執(zhí)行,behave執(zhí)行到了新的feature或是scenario包券,它給context加了a new layer纫谅,可以允許像增加新字段,或是overwrite以前定義好的溅固「讹酰可以看成這是一個scopes
context可以在step中間分享信息
@given('I request a new widget for an account via SOAP')
def step_impl(context):
client = Client("http://127.0.0.1:8000/soap/")
context.response = client.Allocate(customer_first='Firstname',
customer_last='Lastname', colour='red')
@then('I should receive an OK SOAP response')
def step_impl(context):
eq_(context.response['ok'], 1)
given里面起了一個服務器,然后發(fā)送了請求侍郭,response賦予了context.response询吴。在then中可以繼續(xù)使用context.response來檢查剛才的response里面有沒有我們需要驗證的值
除了context.response,還有context.text, context.table, context.failed可以使用
也可以自定義一些變量來傳遞
context.chrome = webdriver.Chrome()
context.server = simple_server.Server()
Environmental Controls
environment.py定義一些測試hook掠河,比如在event before 或是 after 執(zhí)行的方法
在feature level定義的environmental controls file會override scenario level;如果改變了scenario level是不會影響feature level
before_step(context, step), after_step(context, step)
These run before and after every step.
before_scenario(context, scenario), after_scenario(context, scenario)
These run before and after each scenario is run.
before_feature(context, feature), after_feature(context, feature)
These run before and after each feature file is exercised.
before_tag(context, tag), after_tag(context, tag)
These run before and after a section tagged with the given name. They are invoked for each tag encountered in the order they’re found in the feature file. Seecontrolling things with tags.
before_all(context), after_all(context)
These run before and after the whole shooting match.
The feature, scenario and step objects represent the information parsed from the feature file. They have a number of attributes:
上面方法的feature猛计, scenario唠摹, step對象代表著從feature file解析過來的信息。這些對象有著下面的屬性有滑。我們可以在測試中使用這些屬性信息
- keyword: “Feature”, “Scenario”, “Given”, etc.
- name: The name of the step (the text after the keyword.)
- tags: A list of the tags attached to the section or step. See controlling things with tags.
- filename and line: The file name (or “<string>”) and line number of the statement.
一個比較通用的用法是設置瀏覽器或是web server來運行你的測試
import threading
from wsgiref import simple_server
from selenium import webdriver
from my_application import model
from my_application import web_app
def before_all(context):
context.server = simple_server.WSGIServer(('', 8000))
context.server.set_app(web_app.main(environment='test'))
context.thread = threading.Thread(target=context.server.serve_forever)
context.thread.start()
context.browser = webdriver.Chrome()
def after_all(context):
context.server.shutdown()
context.thread.join()
context.browser.quit()
def before_feature(context, feature):
model.init(environment='test')
Controlling Things With Tags
在behave中我們可以tag我們的feature文件。這就意味著我們能夠選擇的執(zhí)行feature或是feature文件中的scenarios
如果我們有下面這樣的feature 文件
Feature: Fight or flight
In order to increase the ninja survival rate,
As a ninja commander
I want my ninjas to decide whether to take on an
opponent based on their skill levels
@slow
Scenario: Weaker opponent
Given the ninja has a third level black-belt
When attacked by a samurai
Then the ninja should engage the opponent
@slow1
Scenario: Stronger opponent
Given the ninja has a third level black-belt
When attacked by Chuck Norris
Then the ninja should run for his life
如果我們執(zhí)行 behave —tags=slow
嵌削,只有被tag為slow的scenario Weaker opponent被執(zhí)行
如果我們執(zhí)行 behave —tags=-slow
, 只有被tag為slow的scenario Weaker opponent不被執(zhí)行
如果我們執(zhí)行behave —tags=slow,slow1
只要被tag為slow或slow1的scenario被執(zhí)行
如果我們執(zhí)行behave —tags=slow —tags=slow1
只要被tag為slow并且slow1的scenario被執(zhí)行
tags與environment.py的互動:
- 如果一個feature或是scenario被skip了毛好,那么相應的before_和after_都不會執(zhí)行
- environment.py中各方法中的feature/scenario對象都有tags屬性,這個屬性是列出了所有tag名稱的列表
- environment.py中的before_tag和after_tag苛秕。如果這兩個方法被傳入了”slow”肌访,那么在執(zhí)行被tag為slow的scenario之前,這兩個方法會被調用
for example艇劫,部分scenario被tag為@browser吼驶,則我們可以使用feature.tags來查看tag有沒有browser.這樣做,我們可以指定哪些feature需要執(zhí)行這個before_和after_
def before_feature(context, feature):
model.init(environment='test')
if 'browser' in feature.tags:
context.server = simple_server.WSGIServer(('', 8000))
context.server.set_app(web_app.main(environment='test'))
context.thread = threading.Thread(target=context.server.serve_forever)
context.thread.start()
context.browser = webdriver.Chrome()
def after_feature(context, feature):
if 'browser' in feature.tags:
context.server.shutdown()
context.thread.join()
context.browser.quit()
Debug-on-Error (in Case of Step Failures)
當一個step失敗的時候店煞,behave提供”debug on error/failure”功能來幫助debug蟹演。這個功能會通過”after_step()"這個hook來實現
當需要使用這個功能的時候,我們可以通過命令行傳入的配置數據來使其enable或是disable顷蟀。
用戶可以:
- 在命令行提供相關參數(定義好的userdata)
- 把參數值存儲在”behave.userdata”
# -- FILE: features/environment.py
# USE: behave -D BEHAVE_DEBUG_ON_ERROR (to enable debug-on-error)
# USE: behave -D BEHAVE_DEBUG_ON_ERROR=yes (to enable debug-on-error)
# USE: behave -D BEHAVE_DEBUG_ON_ERROR=no (to disable debug-on-error)
BEHAVE_DEBUG_ON_ERROR = False
def setup_debug_on_error(userdata):
global BEHAVE_DEBUG_ON_ERROR
BEHAVE_DEBUG_ON_ERROR = userdata.getbool("BEHAVE_DEBUG_ON_ERROR")
def before_all(context):
setup_debug_on_error(context.config.userdata)
def after_step(context, step):
if BEHAVE_DEBUG_ON_ERROR and step.status == "failed":
# -- ENTER DEBUGGER: Zoom in on failure location.
# NOTE: Use IPython debugger, same for pdb (basic python debugger).
import ipdb
ipdb.post_mortem(step.exc_traceback)