Behave структура
Основыная статья в документации
Структура проекта [behave]
- app
- features
- steps
- step.py
- environment.py
- step.feature
- [...]
В environment.py
мы утанавливаем страт/остановку сервера и другие регулярные штуки для каждого теста. в .feature
файлах пишем тест на [gherkin]
Features
- Реализует бизнес-логику
- Шаги соответствуют python функциям, тестирующим сценарий
Feature: showing off behave
Scenario: run a simple test
Given we have behave installed
When we implement a test
Then behave will test it for us!
Пример организации
features/ features/signup.feature features/login.feature features/account_details.feature features/environment.py features/steps/ features/steps/website.py features/steps/utils.py
Scenarios
Background steps
@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.
В бекграунд обычно помещается что-то, что необходимо зааффектить перед стартом основных шагов сценария. Например логирование или развертку базы данных.
- не спользовать background для натсройки сложных стейтментов
- сделть этот раздел максимально коротким
- использовать яркие имена и близкие к юзеру термины, а не символические привязки типа user 1
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.
Scenario Outlines
Используется для запуска нескольких тестирующих примеров. Тест будет запущен отдельно для каждой строки
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 |
[2021-04-02-daily-note] пример работы с таблицами
Other steps
Given помещаем систему в заранее заданное состояние прежде чем пользователь начнет что-то делать самостоятельно
When пользователь что-то делает - это приводит к определенным изменениям
Then что-то происходит после измененеий, что можем увидеть мы, пользователь или другие пользователи в каких-то местах, напирмер в вимде сообщеинй или отображений на экране
And и But подразумевают теже шаги, под которыми использованы эти термины.
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
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
Step data
Следующие строки будут доступны в имплементации через context.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.
"""
When we activate the frobulator
Then we will find it similar to English
Другой вариант - использовать таблицу внутри шага, если нужно протестировать несколько состояний. Таблица будет доступна в виде списка словарей через атрибут context.table
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"
@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
Шаги идентифицируются по декоратору given, when, then и step. Декоратор должен содержать строку шага. Название функции может не буть уникальным, т.к. функция регистрируется и матчится перед ее вызовом.
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)
Внутри имплементации шага можно вызвать другие шаги за счет метода 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
''')
Step Parameters
В описании
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"
в имплементации (при условии, что в Given мы определили context.response
)
@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))
Варианты имплеменаций
parse (the default, based on: parse)
- синтаксис{param:Type}
cfparse (extends: parse, requires: parse_type)
- extended parser with “Cardinality Field” (CF) support
{values:Type+} (cardinality=1..N, many) {values:Type*} (cardinality=0..N, many0) {value:Type?} (cardinality=0..1, optional)
= re
- регулярки типа “(?P<name>…)”
, чтобы выбрать определенные данные из текста, которые мы дали на этапе задания шагов. Типы не поддерживает
use_step_matcher() задает тип парсера (там же смотри про типы данных)
Context
Собирает информацию от старта до окончания теста. Можно использовать как дефолтные атрибуты, так и собственные.
@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)
Дефолтные
- table
- text
- failed
Environmental Controls
Поозволяет задать настройки до запуска и после теста.
before_step(context, step), after_step(context, step)
для каждого шагаbefore_scenario(context, scenario), after_scenario(context, scenario)
для каждого сценарияbefore_feature(context, feature), after_feature(context, feature)
для каждого feature файлаbefore_tag(context, tag), after_tag(context, tag)
для теговbefore_all(context), after_all(context)
для любых объектов
.feature сценарий и step объект имеют аттрибуты:
keyword
- “Feature”, “Scenario”, “Given”, etc.name
- имя шага (текст после ключевого слова)tags
- тегиfilename
andline
- имя файла теста и номер состояния
Пример использования:
# -- FILE: features/environment.py
from behave import fixture, use_fixture
from behave4my_project.fixtures import wsgi_server
from selenium import webdriver
@fixture
def selenium_browser_chrome(context):
# -- HINT: @behave.fixture is similar to @contextlib.contextmanager
context.browser = webdriver.Chrome()
yield context.browser
# -- CLEANUP-FIXTURE PART:
context.browser.quit()
def before_all(context):
use_fixture(wsgi_server, context, port=8000)
use_fixture(selenium_browser_chrome, context)
# -- HINT: CLEANUP-FIXTURE is performed after after_all() hook is called.
def before_feature(context, feature):
model.init(environment='test')
# -- FILE: behave4my_project/fixtures.py
# ALTERNATIVE: Place fixture in "features/environment.py" (but reuse is harder)
from behave import fixture
import threading
from wsgiref import simple_server
from my_application import model
from my_application import web_app
@fixture
def wsgi_server(context, port=8000):
context.server = simple_server.WSGIServer(('', port))
context.server.set_app(web_app.main(environment='test'))
context.thread = threading.Thread(target=context.server.serve_forever)
context.thread.start()
yield context.server
# -- CLEANUP-FIXTURE PART:
context.server.shutdown()
context.thread.join()
tags
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
@whip
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=slow1
запустит только сценарий с тегом @slow
Еще варианты:
--tags=wip,slow
оба тега--tags=wip --tags=slow
при условии, что шаг тегирован обоими тегами
Теги доступны в качестве аттрибутов в окружении, что позволяет задавать запуск специфических скриптов для специфических случаев по тегу
# -- FILE: features/environment.py
# HINT: Reusing some code parts from above.
...
def before_feature(context, feature):
model.init(environment='test')
if 'browser' in feature.tags:
use_fixture(wsgi_server, context)
use_fixture(selenium_browser_chrome, context)
work in progress
Флаг -w отключает логирование и вывод теста в консоль. Запускаются только сценарии с тегом @wip
. Тест останавливается на первой ошибке. Раболтают самописныек логи типа
if not context.config.log_capture:
logging.basicConfig(level=logging.DEBUG)
Fixtures
Фикстуры содержат yield-состояния, которые очищаются, когда удаляется context
(после сценария, запуска фичи и т.д., в зависимости от того, когда фикстуры применяются)
# -- FILE: behave4my_project/fixtures.py (or: features/environment.py)
from behave import fixture
from somewhere.browser import FirefoxBrowser
@fixture(name="fixture.browser.firefox")
def browser_firefox(context, *args, **kwargs):
# -- SETUP-FIXTURE PART:
context.browser = FirefoxBrowser(*args, **kwargs)
yield context.browser
# -- CLEANUP-FIXTURE PART:
context.browser.shutdown()
# -- FILE: features/environment.py
from behave import use_fixture
from behave4my_project.fixtures import browser_firefox
def before_tag(context, tag):
if tag == "fixture.browser.firefox":
use_fixture(browser_firefox, context, timeout=10)
Параметры фикстур:
- fixture_func
- context
- fixture_kwargs
- fxture_kwargs
Debug-on-Error (in Case of Step Failures)
Чтобы не захламлять вывод в консоль мусором ошибок, можно логировать, проще всего через after_step()
Пример
# -- 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)