python behave学习笔记 - 基础

基本概念

behave要被执行,需要运行在满足下面两种情况的目录下

  1. 有feature files。这个feature files可以试由非技术人员编写
  2. 一个“steps”目录,steps里面包含python step implementation

还可以添加一些environmental controls。 比如

before
after
scenarios
features

一个可执行的最小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这样的环境配置文件,在steps下面也有测试代码

在命令行执行behave即可执行所有测试,在console能看到测试结果

什么是feature files

feature file是指一个通常命名为 **.feature的纯文本文件(UTF-8)。这个文件里面包含了用自然语言(Gherkin)描述的系统的功能特征。这些功能特征是具有代表性的期望结果。

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是真实的执行步骤
  • Given: 在用户或是外部系统对应用进行交互之前,我们要把系统处于一个已知的状态。这个更加明确测试执行的前提条件和所要求的系统状态。要避免在GIVEN中涉及到用户交互
  • When:用户或是外部系统所采取的与待测试系统的交互步骤。这个交互能改变待测试系统的状态
  • 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这个关键字使用。 不同的数据会在相同的方法中执行。

Scenario Outline: Blenders
   Given I put  in a blender,
    when I switch the blender on
    then it should transform into 

 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中使用 来定义数据变量。
例子中有两个变量 这两个变量名会在Examples中变为table的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的一部分来执行。比如一个and属于given step下,那么and这个step就会变为given的一部分

如果你希望你的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会执行另外两个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自动管理

当随着测试执行,behave执行到了新的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里面起了一个服务器,然后发送了请求,response赋予了context.response。在then中可以继续使用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 执行的方法

在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 “”) 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文件。这就意味着我们能够选择的执行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

如果我们执行 behave —tags=slow,只有被tag为slow的scenario Weaker opponent被执行

如果我们执行 behave —tags=-slow, 只有被tag为slow的scenario Weaker opponent不被执行

如果我们执行behave —tags=slow,slow1 只要被tag为slow或slow1的scenario被执行

如果我们执行behave —tags=slow —tags=slow1 只要被tag为slow并且slow1的scenario被执行

tags与environment.py的互动:

  • 如果一个feature或是scenario被skip了,那么相应的before_和after_都不会执行
  • environment.py中各方法中的feature/scenario对象都有tags属性,这个属性是列出了所有tag名称的列表
  • environment.py中的before_tag和after_tag。如果这两个方法被传入了”slow”,那么在执行被tag为slow的scenario之前,这两个方法会被调用

for example,部分scenario被tag为@browser,则我们可以使用feature.tags来查看tag有没有browser.这样做,我们可以指定哪些feature需要执行这个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)

你可能感兴趣的:(python behave学习笔记 - 基础)