Feature Testing Layout
要讓behave工作起來需要下面三種類型的文件
- 由非技術(shù)人員 BA/Sponsor等編寫的 feature files , 這些文件包含著應(yīng)用的場景行為
- 一個(gè) step 目錄。這個(gè)目錄下面包含著scenario對應(yīng)的測試代碼
- environmental control文件。這個(gè)是optional的
- environment.py里面包含著關(guān)于
runbeforeafterstepsscenariosfeatures等testing hook代碼
- environment.py里面包含著關(guān)于
一個(gè)可執(zhí)行的最小feature目錄為
features/
features/everything.feature
features/steps/
features/steps/steps.py
最小為一個(gè)features目錄下面一個(gè)feature文件和一個(gè)steps目錄。steps里面包含了測試代碼。
一個(gè)更復(fù)雜的目錄為
features/
features/signup.feature
features/login.feature
features/account_details.feature
features/environment.py
features/steps/
features/steps/website.py
features/steps/utils.py
behave命令執(zhí)行
behave是靈活的,我們通過執(zhí)行帶有不同選項(xiàng)的命令來幫助behave找到feature文件,然后來執(zhí)行指定的文件
- 當(dāng)執(zhí)行
behave時(shí), behave會(huì)從當(dāng)前目錄下尋找”features”子目錄,然后從中讀取feature文件 - 當(dāng)執(zhí)行
behave ./features,給behave傳遞features目錄所在路徑,behave會(huì)從中讀取至少一個(gè) name.feature 文件和一個(gè)叫 steps 的目錄。對于environment.py, 如果有的話,必須是和 steps 目錄在同一個(gè)目錄下 - 當(dāng)執(zhí)行
behave ./features/BookInformationAPITest.feature給behave傳遞了一個(gè)指定的feature file,behave就會(huì)執(zhí)行指定的這個(gè)feature - 也可以給behave傳遞a directory containing your feature files。 behave的工作方式和上述方法類似
behave -v (-v verbose)可以看到更詳細(xì)的behave行為
Gherkin: Feature Testing Language
behave的features文件是由一種叫Gherkin的語言編寫的并且被命名為 name.feature
在理想情況下,這些feature files是有項(xiàng)目內(nèi)的非技術(shù)人員比如需求方使用自然語言來編寫。feature files有兩個(gè)目的,一個(gè)是作為正式文檔 ,二是支持自動(dòng)化測試
feature files的編寫非常自由靈活,但是還是有一些好的準(zhǔn)則可以堅(jiān)持:
- 行結(jié)尾終止聲明
- spaces或tabs都可以作為縮進(jìn)
- 縮進(jìn)都會(huì)被忽略,但是縮進(jìn)是一個(gè)用來展示文本結(jié)構(gòu)的好辦法
- 大部分的行要以關(guān)鍵字為開頭。Keywords: Feature, Scenario, Given
在文件的任何地方都可以使用 “#”來進(jìn)行注釋
Features
Features是由Scenarios來組成。Feature還可以加上description,background和tag,不過這些都是可選的。
最簡單的feature形式為
Feature: feature name
Scenario: some scenario
Given some condition
Then some result is expected.
把description background tag都加上后,feature形式為
@tags @tag
Feature: feature name
description
further description
Background: some requirement of this test
Given some setup condition
And some other setup action
Scenario: some scenario
Given some condition
When some action is taken
Then some result is expected.
Scenario: some other scenario
Given some other condition
When some action is taken
Then some other result is expected.
Feature name應(yīng)該為待測feature的合理描述的標(biāo)題
description
description是optional的,可以用來澄清任何潛在的confusions或是scope issue
Background
background是由一些step組成,這些step和scenarios里面的一樣。Background允許你給feature中的scenario加一些context。Background的執(zhí)行是在feature文件中所有的scenario之前,不過是在所有before hooks之后。
一個(gè)feature文件只有一個(gè)background。ackground必須在scenario或是scenario outline之前定義好。background經(jīng)常被用來做一些setup的事情,比如登陸一個(gè)頁面或是設(shè)置好數(shù)據(jù)庫
使用background的好實(shí)踐:
- 不要用”Background’來設(shè)置較為復(fù)雜的狀態(tài)除非客戶要求
- 保持”Background”短小精煉。如果Background大于4行,我們就可以把一些無關(guān)的細(xì)節(jié)挪到high level的steps
- 使得“Background”栩栩如生,通俗易懂。可以使用豐富的名稱和描述來講述一個(gè)故事。
- 保持scenario不會(huì)很長,也不能太多。不然Background會(huì)執(zhí)行很多遍
Scenarios
Scenarios描述了需要測試的產(chǎn)品行為。Scenario是由一系列steps組成。這些steps典型的形式是”given some condition” “then we expect some test will pass"
scenario最簡單的形式為
Scenario: we have some stock when we open the store
Given that the store has just opened
Then we should have items for sale.
如果有需要的話,and或是but都可以加到scenario里面,放在given when then之后
Scenario: Replaced items should be returned to stock
Given that a customer buys a blue garment
and I have two blue garments in stock
but I have no red garments in stock
and three black garments in stock.
When he returns the garment for a replacement in black,
then I should have three blue garments in stock
and no red garments in stock,
and two black garments in stock.
一個(gè)比較好的實(shí)踐是一個(gè)scenario就測試一個(gè)行為或是一種desired outcome
Scenario Outlines
當(dāng)對于同一個(gè)場景有著很多不同的期待輸入條件和輸出結(jié)果,我們可以使用scenario outline來做數(shù)據(jù)驅(qū)動(dòng),這樣可以重用scenario中的step
一個(gè)outline包含定義在step中的關(guān)鍵字,這些關(guān)鍵字會(huì)被表格中的數(shù)據(jù)來代替。一個(gè)scenario outline可以有多個(gè)example tables
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 |
behave會(huì)跑example table里面的每一行。每一行的數(shù)據(jù)會(huì)去替代step里面尖括號(hào)定義的<thing> <other thing>
注意尖括號(hào)里的名字必須和表格頭的名字一致。
python step implementation如下
from behave import given, when, then
from hamcrest import assert_that, equal_to
from blender import Blender
@given('I put "{thing}" in a blender')
def step_given_put_thing_into_blender(context, thing):
context.blender = Blender()
context.blender.add(thing)
@when('I switch the blender on')
def step_when_switch_blender_on(context):
context.blender.switch_on()
@then('it should transform into "{other_thing}"')
def step_then_should_transform_into(context, other_thing):
assert_that(context.blender.result, equal_to(other_thing))
把<thing>作為參數(shù)傳入方法里面
Steps
每個(gè)step都會(huì)占據(jù)一行。并且以下面五個(gè)關(guān)鍵字中的一個(gè)來開頭:
- given
- when
- then
- and
- but
通常來講這些關(guān)鍵字都應(yīng)該是Title Case就是首字母大寫的形式,部分語言還是支持只有全小寫
step的描述不應(yīng)該包含太多關(guān)于測試機(jī)制的細(xì)節(jié)
比如下面
Given a browser client is used to load the URL "http://website.example/website/home.html"
就應(yīng)該變?yōu)?/p>
Given we are looking at the home page
Steps的實(shí)現(xiàn)是由python寫的測試代碼組成,python的模塊“name.py”會(huì)被放在”steps”文件夾下。python模塊的文件命名是沒有規(guī)定的。所有在”steps”下面的文件都會(huì)被behave引入
Given
givens的目的是在用戶或外部系統(tǒng)對系統(tǒng)進(jìn)行交互之前,使系統(tǒng)處于一個(gè)可知的狀態(tài)。在givens中要避免涉及用戶交互的操作。
在use case中,givens可以被理解為前置條件precondition。也可以在Given中使用table來設(shè)置一些數(shù)據(jù)
When
whens的目的是描述用戶或是外部系統(tǒng)施展的關(guān)鍵行為。這個(gè)交互行為會(huì)引起系統(tǒng)狀態(tài)的變化
Then
thens的目的是觀察結(jié)果。這個(gè)觀察的結(jié)果應(yīng)該與feature描述的用戶價(jià)值business value/benefit相關(guān)聯(lián)。這個(gè)結(jié)果也應(yīng)該是一個(gè)系統(tǒng)產(chǎn)生的
具體的結(jié)果而不是對用戶不可見,埋藏得很深的結(jié)果。對用戶不可見的結(jié)果也許會(huì)沒有business value。比如我們應(yīng)該多驗(yàn)證用戶看得見的結(jié)果而不是去數(shù)據(jù)庫里面找結(jié)果
我們還可以使用And But來組織steps
Scenario: Multiple Givens
Given one thing
Given an other thing
Given yet an other thing
When I open my eyes
Then I see something
Then I don't see something else
可以寫為
Scenario: Multiple Givens
Given one thing
And an other thing
And yet an other thing
When I open my eyes
Then I see something
But I don't see something else
這兩種寫法對behave都是一樣的
Step Data
Steps除了given when then,還允許包含像文本text和表格table樣的數(shù)據(jù)。所以一個(gè)step可以包含自己的text和table,這就是Step Data
Scenario Outline values的一個(gè)替代方法就是使用<name>在Step data text或是table中
Text
任何在step中使用三個(gè)雙引號(hào)包括的文本就是Step data中的text
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. Ut
enim ad minim veniam, quis nostrud exercitation ullamco laboris
nisi ut aliquip ex ea commodo consequat. Duis aute irure dolor in
reprehenderit in voluptate velit esse cillum dolore eu fugiat
nulla pariatur. Excepteur sint occaecat cupidatat non proident,
sunt in culpa qui officia deserunt mollit anim id est laborum.
"""
When we activate the frobulator
Then we will find it similar to English
注意非第一行的whitespace數(shù)不能比第一行少。
在測試代碼中,我們使用Context.text來獲取這段text。Context.text也可以在每一個(gè)step function中傳遞使用。
第一個(gè)step定義的text是可以在scenario后面的step中的context中使用。在候選step中定義的text會(huì)overwrite之前step中的text。
Table
我們可以把一個(gè)表格數(shù)據(jù)賦予給一個(gè)step,只需要通過簡單的輸入,縮進(jìn)即可。當(dāng)我們想把一系列的數(shù)據(jù)賦值給一個(gè)model
的時(shí)候,這個(gè)Table特別有用
整個(gè)表格有兩個(gè) vertical bar “|“ 來區(qū)別列。任何在列和vertical bar之間的whitespace會(huì)被刪除
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來獲取這段table。Context.table也可以在每一個(gè)step function中傳遞使用。測試代碼中,我們使用如下
@given('a set of specific users')
def step_impl(context):
for row in context.table:
model.add_user(name=row['name'], department=row['department'])
Tags
feature file中可以使用tag功能。tag功能可以讓behave實(shí)現(xiàn)選擇性的執(zhí)行feature
tag可以作用于feature,scenario和scenario outline。作用于feature的tag也會(huì)作用于feature下的scenario或scenario outlines
tag使用修飾符的樣式作用于feature/scenario/scenario outlines,多個(gè)tag之間可以使用whitespace來進(jìn)行間隔
For example:
@wip @slow
Feature: annual reporting
Some description of a slow reporting system.
or
@wip
@slow
Feature: annual reporting
Some description of a slow reporting system.
Controlling Your Test Run With Tags 我們可以使用tag來選擇性的執(zhí)行測試
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
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
對于上面這個(gè)feature file,
- 如果運(yùn)行
behave —tags=slow就只會(huì)運(yùn)行標(biāo)記了slow的Scenario Weaker opponent - 如果運(yùn)行
behave —tags=-slow,就會(huì)運(yùn)行沒有標(biāo)記slow的Scenarios
一個(gè)常用的場景是使用@wip來處理正在開發(fā)中的scenario或feature,使用behave —tags=wip來運(yùn)行這個(gè)正在開發(fā)的feature
在命令行中tag的用法:
- 如果我們執(zhí)行behave —tags=slow,slow1 只要被tag為slow或slow1的scenario被執(zhí)行
- 如果我們執(zhí)行behave —tags=slow —tags=slow1 只要被tag為slow并且slow1的scenario被執(zhí)行
tags與environment.py的互動(dòng)
- 如果一個(gè)feature或是scenario被skip了,那么相應(yīng)的before_和after_都不會(huì)執(zhí)行
- environment.py中各方法中的feature/scenario對象都有tags屬性,這個(gè)屬性是列出了所有tag名稱的列表
- environment.py中的before_tag和after_tag。如果這兩個(gè)方法被傳入了”slow”,那么在執(zhí)行被tag為slow的scenario之前,這兩個(gè)方法會(huì)被調(diào)用
for example,部分scenario被tag為@browser,則我們可以使用feature.tags來查看tag有沒有browser.這樣做,我們可以指定哪些feature需要執(zhí)行這個(gè)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()