244 lines
6.2 KiB
ReStructuredText
244 lines
6.2 KiB
ReStructuredText
|
BDD library for the py.test runner
|
|||
|
==================================
|
|||
|
|
|||
|
.. |Build Status| image:: https://api.travis-ci.org/olegpidsadnyi/pytest-bdd.png
|
|||
|
:target: https://travis-ci.org/olegpidsadnyi/pytest-bdd
|
|||
|
.. |Pypi| image:: https://pypip.in/v/pytest-bdd/badge.png
|
|||
|
:target: https://crate.io/packages/pytest-bdd/
|
|||
|
.. |Coverrals| image:: https://coveralls.io/repos/olegpidsadnyi/pytest-bdd/badge.png?branch=master
|
|||
|
:target: https://coveralls.io/r/olegpidsadnyi/pytest-bdd
|
|||
|
|
|||
|
|
|||
|
Install pytest-bdd
|
|||
|
==================
|
|||
|
|
|||
|
::
|
|||
|
|
|||
|
pip install pytest-bdd
|
|||
|
|
|||
|
Example
|
|||
|
=======
|
|||
|
|
|||
|
publish\_article.feature:
|
|||
|
|
|||
|
::
|
|||
|
|
|||
|
Scenario: Publishing the article
|
|||
|
Given I'm an author user
|
|||
|
And I have an article
|
|||
|
When I go to the article page
|
|||
|
And I press the publish button
|
|||
|
Then I should not see the error message
|
|||
|
And the article should be published # Note: will query the database
|
|||
|
|
|||
|
test\_publish\_article.py:
|
|||
|
|
|||
|
::
|
|||
|
|
|||
|
from pytest_bdd import scenario, given, when, then
|
|||
|
|
|||
|
test_publish = scenario('publish_article.feature', 'Publishing the article')
|
|||
|
|
|||
|
|
|||
|
@given('I have an article')
|
|||
|
def article(author):
|
|||
|
return create_test_article(author=author)
|
|||
|
|
|||
|
|
|||
|
@when('I go to the article page')
|
|||
|
def go_to_article(article, browser):
|
|||
|
browser.visit(urljoin(browser.url, '/manage/articles/{0}/'.format(article.id)))
|
|||
|
|
|||
|
|
|||
|
@when('I press the publish button')
|
|||
|
def publish_article(browser):
|
|||
|
browser.find_by_css('button[name=publish]').first.click()
|
|||
|
|
|||
|
|
|||
|
@then('I should not see the error message')
|
|||
|
def no_error_message(browser):
|
|||
|
with pytest.raises(ElementDoesNotExist):
|
|||
|
browser.find_by_css('.message.error').first
|
|||
|
|
|||
|
|
|||
|
@then('And the article should be published')
|
|||
|
def article_is_published(article):
|
|||
|
article.refresh() # Refresh the object in the SQLAlchemy session
|
|||
|
assert article.is_published
|
|||
|
|
|||
|
Step aliases
|
|||
|
============
|
|||
|
|
|||
|
Sometimes it is needed to declare the same fixtures or steps with the
|
|||
|
different names for better readability. In order to use the same step
|
|||
|
function with multiple step names simply decorate it multiple times:
|
|||
|
|
|||
|
::
|
|||
|
|
|||
|
@given('I have an article')
|
|||
|
@given('there\'s an article')
|
|||
|
def article(author):
|
|||
|
return create_test_article(author=author)
|
|||
|
|
|||
|
Note that the given step aliases are independent and will be executed
|
|||
|
when mentioned.
|
|||
|
|
|||
|
For example if you assoicate your resource to some owner or not. Admin
|
|||
|
user can’t be an author of the article, but article should have some
|
|||
|
default author.
|
|||
|
|
|||
|
::
|
|||
|
|
|||
|
Scenario: I'm the author
|
|||
|
Given I'm an author
|
|||
|
And I have an article
|
|||
|
|
|||
|
|
|||
|
Scenario: I'm the admin
|
|||
|
Given I'm the admin
|
|||
|
And there is an article
|
|||
|
|
|||
|
Step parameters
|
|||
|
===============
|
|||
|
|
|||
|
Sometimes it is hard to write good scenarios without duplicating most of
|
|||
|
contents of existing scenario. For example if you create some object
|
|||
|
with static param value, you might want to create another test with
|
|||
|
different param value. By Gherkin specification it’s possible to have
|
|||
|
parameters in steps: http://docs.behat.org/guides/1.gherkin.html
|
|||
|
Example:
|
|||
|
|
|||
|
::
|
|||
|
|
|||
|
Scenario: Parametrized given, when, thens
|
|||
|
Given there are <start> cucumbers
|
|||
|
When I eat <eat> cucumbers
|
|||
|
Then I should have <left> cucumbers
|
|||
|
|
|||
|
As you can see we don’t use Scenario Outline, but use just Scenario,
|
|||
|
just because it’s simple to implement for pytest.
|
|||
|
|
|||
|
The code will look like:
|
|||
|
|
|||
|
::
|
|||
|
|
|||
|
# here we use pytest power to parametrize test
|
|||
|
@pytest.mark.parametrize(
|
|||
|
['start', 'eat', 'left'],
|
|||
|
[(12, 5, 7)])
|
|||
|
@scenario(
|
|||
|
'parametrized.feature',
|
|||
|
'Parametrized given, when, thens',
|
|||
|
)
|
|||
|
# note that we should receive same arguments in function that we use for test parametrization either directly
|
|||
|
# or indirectly (throught fixtures)
|
|||
|
def test_parametrized(start, eat, left):
|
|||
|
"""We don't need to do anything here, everything will be managed by scenario decorator."""
|
|||
|
|
|||
|
|
|||
|
@given('there are <start> cucumbers')
|
|||
|
def start_cucumbers(start):
|
|||
|
return dict(start=start)
|
|||
|
|
|||
|
|
|||
|
@when('I eat <eat> cucumbers')
|
|||
|
def eat_cucumbers(start_cucumbers, start, eat):
|
|||
|
start_cucumbers['eat'] = eat
|
|||
|
|
|||
|
|
|||
|
@then('I should have <left> cucumbers')
|
|||
|
def should_have_left_cucumbers(start_cucumbers, start, eat, left):
|
|||
|
assert start - eat == left
|
|||
|
assert start_cucumbers['start'] == start
|
|||
|
assert start_cucumbers['eat'] == eat
|
|||
|
|
|||
|
Reuse fixtures
|
|||
|
==============
|
|||
|
|
|||
|
Sometimes scenarios define new names for the fixture that can be
|
|||
|
inherited. Fixtures can be reused with other names using given():
|
|||
|
|
|||
|
::
|
|||
|
|
|||
|
given('I have beautiful article', fixture='article')
|
|||
|
|
|||
|
Reuse steps
|
|||
|
===========
|
|||
|
|
|||
|
It is possible to define some common steps in the parent conftest.py and
|
|||
|
simply expect them in the child test file.
|
|||
|
|
|||
|
common\_steps.feature:
|
|||
|
|
|||
|
::
|
|||
|
|
|||
|
Scenario: All steps are declared in the conftest
|
|||
|
Given I have a bar
|
|||
|
Then bar should have value "bar"
|
|||
|
|
|||
|
conftest.py:
|
|||
|
|
|||
|
::
|
|||
|
|
|||
|
from pytest_bdd import given, then
|
|||
|
|
|||
|
|
|||
|
@given('I have a bar')
|
|||
|
def bar():
|
|||
|
return 'bar'
|
|||
|
|
|||
|
|
|||
|
@then('bar should have value "bar"')
|
|||
|
def bar_is_bar(bar):
|
|||
|
assert bar == 'bar'
|
|||
|
|
|||
|
test\_common.py:
|
|||
|
|
|||
|
::
|
|||
|
|
|||
|
test_conftest = scenario('common_steps.feature', 'All steps are declared in the conftest')
|
|||
|
|
|||
|
There are no definitions of the steps in the test file. They were
|
|||
|
collected from the parent conftests.
|
|||
|
|
|||
|
Feature file paths
|
|||
|
==================
|
|||
|
|
|||
|
But default, pytest-bdd will use current module’s path as base path for
|
|||
|
finding feature files, but this behaviour can be changed by having
|
|||
|
fixture named ‘pytestbdd\_feature\_base\_dir’ which should return the
|
|||
|
new base path.
|
|||
|
|
|||
|
test\_publish\_article.py:
|
|||
|
|
|||
|
::
|
|||
|
|
|||
|
import pytest
|
|||
|
from pytest_bdd import scenario
|
|||
|
|
|||
|
|
|||
|
@pytest.fixture
|
|||
|
def pytestbdd_feature_base_dir():
|
|||
|
return '/home/user/projects/foo.bar/features'
|
|||
|
|
|||
|
test_publish = scenario('publish_article.feature', 'Publishing the article')
|
|||
|
|
|||
|
Subplugins
|
|||
|
==========
|
|||
|
|
|||
|
The pytest BDD has plugin support, and the main purpose of plugins
|
|||
|
(subplugins) is to provide useful and specialized fixtures.
|
|||
|
|
|||
|
List of known subplugins:
|
|||
|
|
|||
|
::
|
|||
|
|
|||
|
* pytest-bdd-splinter -- collection of fixtures for real browser BDD testing
|
|||
|
|
|||
|
License
|
|||
|
=======
|
|||
|
|
|||
|
This software is licensed under the `MIT license <http://en.wikipedia.org/wiki/MIT_License>`_.
|
|||
|
|
|||
|
© 2013 Oleg Pidsadnyi
|