pax_global_header00006660000000000000000000000064135272244440014521gustar00rootroot0000000000000052 comment=386ed90cec68f4a9e000728928bbb4854f9a56d4 pytest-bdd-3.2.1/000077500000000000000000000000001352722444400136035ustar00rootroot00000000000000pytest-bdd-3.2.1/.gitignore000066400000000000000000000007051352722444400155750ustar00rootroot00000000000000*.rej *.py[cod] /.env *.orig # C extensions *.so # Packages *.egg *.egg-info dist build _build eggs parts bin var sdist develop-eggs .installed.cfg lib lib64 # Installer logs pip-log.txt # Unit test / coverage reports .coverage .tox nosetests.xml # Translations *.mo # Mr Developer .mr.developer.cfg .project .pydevproject .pytest_cache .ropeproject # Sublime /*.sublime-* #PyCharm /.idea # virtualenv /.Python /lib /include /src /share /local pytest-bdd-3.2.1/.travis.yml000066400000000000000000000004141352722444400157130ustar00rootroot00000000000000dist: xenial language: python python: - "2.7" - "3.5" - "3.6" - "3.7" - "3.8-dev" install: pip install tox tox-travis script: tox --recreate branches: except: - /^\d/ notifications: email: - bubenkoff@gmail.com - oleg.pidsadnyi@gmail.com pytest-bdd-3.2.1/AUTHORS.rst000066400000000000000000000017241352722444400154660ustar00rootroot00000000000000Authors ======= `Oleg Pidsadnyi `_ original idea, initial implementation and further improvements `Anatoly Bubenkov `_ key implementation idea and realization, many new features and improvements These people have contributed to `pytest-bdd`, in alphabetical order: * `Adam Coddington `_ * `Albert-Jan Nijburg `_ * `Alessio Bogon `_ * `Andrey Makhnach `_ * `Aron Curzon `_ * `Dmitrijs Milajevs `_ * `Dmitry Kolyagin `_ * `Florian Bruhin `_ * `Floris Bruynooghe `_ * `Harro van der Klauw `_ * `Laurence Rowe `_ * `Leonardo Santagada `_ * `Milosz Sliwinski `_ * `Michiel Holtkamp `_ * `Robin Pedersen `_ * `Sergey Kraynev `_ pytest-bdd-3.2.1/CHANGES.rst000066400000000000000000000203131352722444400154040ustar00rootroot00000000000000Changelog ========= Unreleased ---------- 3.2.1 ---------- - Fix regression introduced in 3.2.0 where pytest-bdd would break in presence of test items that are not functions. 3.2.0 ---------- - Fix Python 3.8 support - Remove code that rewrites code. This should help with the maintenance of this project and make debugging easier. 3.1.1 ---------- - Allow unicode string in ``@given()`` step names when using python2. This makes the transition of projects from python 2 to 3 easier. 3.1.0 ---------- - Drop support for pytest < 3.3.2. - Step definitions generated by ``$ pytest-bdd generate`` will now raise ``NotImplementedError`` by default. - ``@given(...)`` no longer accepts regex objects. It was deprecated long ago. - Improve project testing by treating warnings as exceptions. - ``pytest_bdd_step_validation_error`` will now always receive ``step_func_args`` as defined in the signature. 3.0.2 ------ - Add compatibility with pytest 4.2 (sliwinski-milosz) #288. 3.0.1 ------ - Minimal supported version of `pytest` is now 2.9.0 as lower versions do not support `bool` type ini options (sliwinski-milosz) #260 - Fix RemovedInPytest4Warning warnings (sliwinski-milosz) #261. 3.0.0 ------ - Fixtures `pytestbdd_feature_base_dir` and `pytestbdd_strict_gherkin` have been removed. Check the `Migration of your tests from versions 2.x.x `_ for more information (sliwinski-milosz) #255 - Fix step definitions not being found when using parsers or converters after a change in pytest (youtux) #257 2.21.0 ------ - Gherkin terminal reporter expanded format (pauk-slon) 2.20.0 ------ - Added support for But steps (olegpidsadnyi) - Fixed compatibility with pytest 3.3.2 (olegpidsadnyi) - MInimal required version of pytest is now 2.8.1 since it doesn't support earlier versions (olegpidsadnyi) 2.19.0 ------ - Added --cucumber-json-expanded option for explicit selection of expanded format (mjholtkamp) - Step names are filled in when --cucumber-json-expanded is used (mjholtkamp) 2.18.2 ------ - Fix check for out section steps definitions for no strict gherkin feature 2.18.1 ------ - Relay fixture results to recursive call of 'get_features' (coddingtonbear) 2.18.0 ------ - Add gherkin terminal reporter (spinus + thedrow) 2.17.2 ------ - Fix scenario lines containing an ``@`` being parsed as a tag. (The-Compiler) 2.17.1 ------ - Add support for pytest 3.0 2.17.0 ------ - Fix FixtureDef signature for newer pytest versions (The-Compiler) - Better error explanation for the steps defined outside of scenarios (olegpidsadnyi) - Add a ``pytest_bdd_apply_tag`` hook to customize handling of tags (The-Compiler) - Allow spaces in tag names. This can be useful when using the ``pytest_bdd_apply_tag`` hook with tags like ``@xfail: Some reason``. 2.16.1 ------ - Cleaned up hooks of the plugin (olegpidsadnyi) - Fixed report serialization (olegpidsadnyi) 2.16.0 ------ - Fixed deprecation warnings with pytest 2.8 (The-Compiler) - Fixed deprecation warnings with Python 3.5 (The-Compiler) 2.15.0 ------ - Add examples data in the scenario report (bubenkoff) 2.14.5 ------ - Properly parse feature description (bubenkoff) 2.14.3 ------ - Avoid potentially random collection order for xdist compartibility (bubenkoff) 2.14.1 ------ - Pass additional arguments to parsers (bubenkoff) 2.14.0 ------ - Add validation check which prevents having multiple features in a single feature file (bubenkoff) 2.13.1 ------ - Allow mixing feature example table with scenario example table (bubenkoff, olegpidsadnyi) 2.13.0 ------ - Feature example table (bubenkoff, sureshvv) 2.12.2 ------ - Make it possible to relax strict Gherkin scenario validation (bubenkoff) 2.11.3 ------ - Fix minimal `six` version (bubenkoff, dustinfarris) 2.11.1 ------ - Mention step type on step definition not found errors and in code generation (bubenkoff, lrowe) 2.11.0 ------ - Prefix step definition fixture names to avoid name collisions (bubenkoff, lrowe) 2.10.0 ------ - Make feature and scenario tags to be fully compartible with pytest markers (bubenkoff, kevinastone) 2.9.1 ----- - Fixed FeatureError string representation to correctly support python3 (bubenkoff, lrowe) 2.9.0 ----- - Added possibility to inject fixtures from given keywords (bubenkoff) 2.8.0 ----- - Added hook before the step is executed with evaluated parameters (olegpidsadnyi) 2.7.2 ----- - Correct base feature path lookup for python3 (bubenkoff) 2.7.1 ----- - Allow to pass ``scope`` for ``given`` steps (bubenkoff, sureshvv) 2.7.0 ----- - Implemented `scenarios` shortcut to automatically bind scenarios to tests (bubenkoff) 2.6.2 ----- - Parse comments only in the begining of words (santagada) 2.6.1 ----- - Correctly handle `pytest-bdd` command called without the subcommand under python3 (bubenkoff, spinus) - Pluggable parsers for step definitions (bubenkoff, spinus) 2.5.3 ----- - Add after scenario hook, document both before and after scenario hooks (bubenkoff) 2.5.2 ----- - Fix code generation steps ordering (bubenkoff) 2.5.1 ----- - Fix error report serialization (olegpidsadnyi) 2.5.0 ----- - Fix multiline steps in the Background section (bubenkoff, arpe) - Code cleanup (olegpidsadnyi) 2.4.5 ----- - Fix unicode issue with scenario name (bubenkoff, aohontsev) 2.4.3 ----- - Fix unicode regex argumented steps issue (bubenkoff, aohontsev) - Fix steps timings in the json reporting (bubenkoff) 2.4.2 ----- - Recursion is fixed for the --generate-missing and the --feature parameters (bubenkoff) 2.4.1 ----- - Better reporting of a not found scenario (bubenkoff) - Simple test code generation implemented (bubenkoff) - Correct timing values for cucumber json reporting (bubenkoff) - Validation/generation helpers (bubenkoff) 2.4.0 ----- - Background support added (bubenkoff) - Fixed double collection of the conftest files if scenario decorator is used (ropez, bubenkoff) 2.3.3 ----- - Added timings to the cucumber json report (bubenkoff) 2.3.2 ----- - Fixed incorrect error message using e.argname instead of step.name (hvdklauw) 2.3.1 ----- - Implemented cucumber tags support (bubenkoff) - Implemented cucumber json formatter (bubenkoff, albertjan) - Added 'trace' keyword (bubenkoff) 2.1.2 ----- - Latest pytest compartibility fixes (bubenkoff) 2.1.1 ----- - Bugfixes (bubenkoff) 2.1.0 ----- - Implemented multiline steps (bubenkoff) 2.0.1 ----- - Allow more than one parameter per step (bubenkoff) - Allow empty example values (bubenkoff) 2.0.0 ----- - Pure pytest parametrization for scenario outlines (bubenkoff) - Argumented steps now support converters (transformations) (bubenkoff) - scenario supports only decorator form (bubenkoff) - Code generation refactoring and cleanup (bubenkoff) 1.0.0 ----- - Implemented scenario outlines (bubenkoff) 0.6.11 ------ - Fixed step arguments conflict with the fixtures having the same name (olegpidsadnyi) 0.6.9 ----- - Implemented support of Gherkin "Feature:" (olegpidsadnyi) 0.6.8 ----- - Implemented several hooks to allow reporting/error handling (bubenkoff) 0.6.6 ----- - Fixes to unnecessary mentioning of pytest-bdd package files in py.test log with -v (bubenkoff) 0.6.5 ----- - Compartibility with recent pytest (bubenkoff) 0.6.4 ----- - More unicode fixes (amakhnach) 0.6.3 ----- - Added unicode support for feature files. Removed buggy module replacement for scenario. (amakhnach) 0.6.2 ----- - Removed unnecessary mention of pytest-bdd package files in py.test log with -v (bubenkoff) 0.6.1 ----- - Step arguments in whens when there are no given arguments used. (amakhnach, bubenkoff) 0.6.0 ----- - Added step arguments support. (curzona, olegpidsadnyi, bubenkoff) - Added checking of the step type order. (markon, olegpidsadnyi) 0.5.2 ----- - Added extra info into output when FeatureError exception raises. (amakhnach) 0.5.0 ----- - Added parametrization to scenarios - Coveralls.io integration - Test coverage improvement/fixes - Correct wrapping of step functions to preserve function docstring 0.4.7 ----- - Fixed Python 3.3 support 0.4.6 ----- - Fixed a bug when py.test --fixtures showed incorrect filenames for the steps. 0.4.5 ----- - Fixed a bug with the reuse of the fixture by given steps being evaluated multiple times. 0.4.3 ----- - Update the license file and PYPI related documentation. pytest-bdd-3.2.1/LICENSE.txt000066400000000000000000000021041352722444400154230ustar00rootroot00000000000000Copyright (C) 2013-2014 Oleg Pidsadnyi, Anatoly Bubenkov and others Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. pytest-bdd-3.2.1/MANIFEST.in000066400000000000000000000001201352722444400153320ustar00rootroot00000000000000include *.rst include *.txt include setup.py include pytest_bdd/templates/*.mak pytest-bdd-3.2.1/Makefile000066400000000000000000000006241352722444400152450ustar00rootroot00000000000000# create virtual environment PATH := .env/bin:$(PATH) .env: virtualenv .env # install all needed for development develop: .env pip install -e . -r requirements-testing.txt tox python-coveralls coverage: develop coverage run --source=pytest_bdd .env/bin/py.test tests coverage report -m test: develop tox coveralls: coverage coveralls # clean the development envrironment clean: -rm -rf .env pytest-bdd-3.2.1/README.rst000066400000000000000000001202351352722444400152750ustar00rootroot00000000000000BDD library for the py.test runner ================================== .. image:: http://img.shields.io/pypi/v/pytest-bdd.svg :target: https://pypi.python.org/pypi/pytest-bdd .. image:: http://img.shields.io/coveralls/pytest-dev/pytest-bdd/master.svg :target: https://coveralls.io/r/pytest-dev/pytest-bdd .. image:: https://travis-ci.org/pytest-dev/pytest-bdd.svg?branch=master :target: https://travis-ci.org/pytest-dev/pytest-bdd .. image:: https://readthedocs.org/projects/pytest-bdd/badge/?version=latest :target: https://readthedocs.org/projects/pytest-bdd/?badge=latest :alt: Documentation Status pytest-bdd implements a subset of the Gherkin language to enable automating project requirements testing and to facilitate behavioral driven development. Unlike many other BDD tools, it does not require a separate runner and benefits from the power and flexibility of pytest. It enables unifying unit and functional tests, reduces the burden of continuous integration server configuration and allows the reuse of test setups. Pytest fixtures written for unit tests can be reused for setup and actions mentioned in feature steps with dependency injection. This allows a true BDD just-enough specification of the requirements without maintaining any context object containing the side effects of Gherkin imperative declarations. .. _behave: https://pypi.python.org/pypi/behave .. _pytest-splinter: https://github.com/pytest-dev/pytest-splinter Install pytest-bdd ------------------ :: pip install pytest-bdd The minimum required version of pytest is 3.3.2 Example ------- An example test for a blog hosting software could look like this. Note that pytest-splinter_ is used to get the browser fixture. publish_article.feature: .. code-block:: gherkin Feature: Blog A site where you can publish your articles. 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 Note that only one feature is allowed per feature file. test_publish_article.py: .. code-block:: python from pytest_bdd import scenario, given, when, then @scenario('publish_article.feature', 'Publishing the article') def test_publish(): pass @given("I'm an author user") def author_user(auth, author): auth['user'] = author.user @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('the article should be published') def article_is_published(article): article.refresh() # Refresh the object in the SQLAlchemy session assert article.is_published Scenario decorator ------------------ The scenario decorator can accept the following optional keyword arguments: * ``encoding`` - decode content of feature file in specific encoding. UTF-8 is default. * ``example_converters`` - mapping to pass functions to convert example values provided in feature files. Functions decorated with the `scenario` decorator behave like a normal test function, and they will be executed after all scenario steps. You can consider it as a normal pytest test function, e.g. order fixtures there, call other functions and make assertions: .. code-block:: python from pytest_bdd import scenario, given, when, then @scenario('publish_article.feature', 'Publishing the article') def test_publish(browser): assert article.title in browser.html Step aliases ------------ Sometimes, one has to declare the same fixtures or steps with different names for better readability. In order to use the same step function with multiple step names simply decorate it multiple times: .. code-block:: python @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 associate your resource to some owner or not. Admin user can’t be an author of the article, but articles should have a default author. .. code-block:: gherkin 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's an article Given step scope ---------------- If you need your given step to be executed less than once per scenario (for example: once for module, session), you can pass optional ``scope`` argument: .. code-block:: python @given('there is an article', scope='session') def article(author): return create_test_article(author=author) .. code-block:: gherkin Scenario: I'm the author Given I'm an author And there is an article Scenario: I'm the admin Given I'm the admin And there is an article In this example, the step function for the 'there is an article' given step will be executed once, even though there are 2 scenarios using it. Note that for other step types, it makes no sense to have scope larger than 'function' (the default) as they represent an action (when step), and assertion (then step). Step arguments -------------- Often it's possible to reuse steps giving them a parameter(s). This allows to have single implementation and multiple use, so less code. Also opens the possibility to use same step twice in single scenario and with different arguments! And even more, there are several types of step parameter parsers at your disposal (idea taken from behave_ implementation): .. _pypi_parse: http://pypi.python.org/pypi/parse .. _pypi_parse_type: http://pypi.python.org/pypi/parse_type **string** (the default) This is the default and can be considered as a `null` or `exact` parser. It parses no parameters and matches the step name by equality of strings. **parse** (based on: pypi_parse_) Provides a simple parser that replaces regular expressions for step parameters with a readable syntax like ``{param:Type}``. The syntax is inspired by the Python builtin ``string.format()`` function. Step parameters must use the named fields syntax of pypi_parse_ in step definitions. The named fields are extracted, optionally type converted and then used as step function arguments. Supports type conversions by using type converters passed via `extra_types` **cfparse** (extends: pypi_parse_, based on: pypi_parse_type_) Provides an extended parser with "Cardinality Field" (CF) support. Automatically creates missing type converters for related cardinality as long as a type converter for cardinality=1 is provided. Supports parse expressions like: * ``{values:Type+}`` (cardinality=1..N, many) * ``{values:Type*}`` (cardinality=0..N, many0) * ``{value:Type?}`` (cardinality=0..1, optional) Supports type conversions (as above). **re** This uses full regular expressions to parse the clause text. You will need to use named groups "(?P...)" to define the variables pulled from the text and passed to your ``step()`` function. Type conversion can only be done via `converters` step decorator argument (see example below). The default parser is `string`, so just plain one-to-one match to the keyword definition. Parsers except `string`, as well as their optional arguments are specified like: for `cfparse` parser .. code-block:: python from pytest_bdd import parsers @given(parsers.cfparse('there are {start:Number} cucumbers', extra_types=dict(Number=int))) def start_cucumbers(start): return dict(start=start, eat=0) for `re` parser .. code-block:: python from pytest_bdd import parsers @given(parsers.re(r'there are (?P\d+) cucumbers'), converters=dict(start=int)) def start_cucumbers(start): return dict(start=start, eat=0) Example: .. code-block:: gherkin Scenario: Arguments for given, when, thens Given there are 5 cucumbers When I eat 3 cucumbers And I eat 2 cucumbers Then I should have 0 cucumbers The code will look like: .. code-block:: python import re from pytest_bdd import scenario, given, when, then, parsers @scenario('arguments.feature', 'Arguments for given, when, thens') def test_arguments(): pass @given(parsers.parse('there are {start:d} cucumbers')) def start_cucumbers(start): return dict(start=start, eat=0) @when(parsers.parse('I eat {eat:d} cucumbers')) def eat_cucumbers(start_cucumbers, eat): start_cucumbers['eat'] += eat @then(parsers.parse('I should have {left:d} cucumbers')) def should_have_left_cucumbers(start_cucumbers, start, left): assert start_cucumbers['start'] == start assert start - start_cucumbers['eat'] == left Example code also shows possibility to pass argument converters which may be useful if you need to postprocess step arguments after the parser. You can implement your own step parser. It's interface is quite simple. The code can looks like: .. code-block:: python import re from pytest_bdd import given, parsers class MyParser(parsers.StepParser): """Custom parser.""" def __init__(self, name, **kwargs): """Compile regex.""" super(re, self).__init__(name) self.regex = re.compile(re.sub('%(.+)%', '(?P<\1>.+)', self.name), **kwargs) def parse_arguments(self, name): """Get step arguments. :return: `dict` of step arguments """ return self.regex.match(name).groupdict() def is_matching(self, name): """Match given name with the step name.""" return bool(self.regex.match(name)) @given(parsers.parse('there are %start% cucumbers')) def start_cucumbers(start): return dict(start=start, eat=0) Step arguments are fixtures as well! ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ Step arguments are injected into pytest `request` context as normal fixtures with the names equal to the names of the arguments. This opens a number of possibilies: * you can access step's argument as a fixture in other step function just by mentioning it as an argument (just like any othe pytest fixture) * if the name of the step argument clashes with existing fixture, it will be overridden by step's argument value; this way you can set/override the value for some fixture deeply inside of the fixture tree in a ad-hoc way by just choosing the proper name for the step argument. Override fixtures via given steps --------------------------------- Dependency injection is not a panacea if you have complex structure of your test setup data. Sometimes there's a need such a given step which would imperatively change the fixture only for certain test (scenario), while for other tests it will stay untouched. To allow this, special parameter `target_fixture` exists in the `given` decorator: .. code-block:: python from pytest_bdd import given @pytest.fixture def foo(): return "foo" @given("I have injecting given", target_fixture="foo") def injecting_given(): return "injected foo" @then('foo should be "injected foo"') def foo_is_foo(foo): assert foo == 'injected foo' .. code-block:: gherkin Scenario: Test given fixture injection Given I have injecting given Then foo should be "injected foo" In this example existing fixture `foo` will be overridden by given step `I have injecting given` only for scenario it's used in. Multiline steps --------------- As Gherkin, pytest-bdd supports multiline steps (aka `PyStrings `_). But in much cleaner and powerful way: .. code-block:: gherkin Scenario: Multiline step using sub indentation Given I have a step with: Some Extra Lines Then the text should be parsed with correct indentation Step is considered as multiline one, if the **next** line(s) after it's first line, is indented relatively to the first line. The step name is then simply extended by adding further lines with newlines. In the example above, the Given step name will be: .. code-block:: python 'I have a step with:\nSome\nExtra\nLines' You can of course register step using full name (including the newlines), but it seems more practical to use step arguments and capture lines after first line (or some subset of them) into the argument: .. code-block:: python import re from pytest_bdd import given, then, scenario @scenario( 'multiline.feature', 'Multiline step using sub indentation', ) def test_multiline(): pass @given(parsers.parse('I have a step with:\n{text}')) def i_have_text(text): return text @then('the text should be parsed with correct indentation') def text_should_be_correct(i_have_text, text): assert i_have_text == text == 'Some\nExtra\nLines' Note that `then` step definition (`text_should_be_correct`) in this example uses `text` fixture which is provided by a a `given` step (`i_have_text`) argument with the same name (`text`). This possibility is described in the `Step arguments are fixtures as well!`_ section. Scenarios shortcut ------------------ If you have relatively large set of feature files, it's boring to manually bind scenarios to the tests using the scenario decorator. Of course with the manual approach you get all the power to be able to additionally parametrize the test, give the test function a nice name, document it, etc, but in the majority of the cases you don't need that. Instead you want to bind `all` scenarios found in the `feature` folder(s) recursively automatically. For this - there's a `scenarios` helper. .. code-block:: python from pytest_bdd import scenarios # assume 'features' subfolder is in this file's directory scenarios('features') That's all you need to do to bind all scenarios found in the `features` folder! Note that you can pass multiple paths, and those paths can be either feature files or feature folders. .. code-block:: python from pytest_bdd import scenarios # pass multiple paths/files scenarios('features', 'other_features/some.feature', 'some_other_features') But what if you need to manually bind certain scenario, leaving others to be automatically bound? Just write your scenario in a `normal` way, but ensure you do it `BEFORE` the call of `scenarios` helper. .. code-block:: python from pytest_bdd import scenario, scenarios @scenario('features/some.feature', 'Test something') def test_something(): pass # assume 'features' subfolder is in this file's directory scenarios('features') In the example above `test_something` scenario binding will be kept manual, other scenarios found in the `features` folder will be bound automatically. Scenario outlines ----------------- Scenarios can be parametrized to cover few cases. In Gherkin the variable templates are written using corner braces as . `Gherkin scenario outlines `_ are supported by pytest-bdd exactly as it's described in be behave_ docs. Example: .. code-block:: gherkin Scenario Outline: Outlined given, when, thens Given there are cucumbers When I eat cucumbers Then I should have cucumbers Examples: | start | eat | left | | 12 | 5 | 7 | pytest-bdd feature file format also supports example tables in different way: .. code-block:: gherkin Scenario Outline: Outlined given, when, thens Given there are cucumbers When I eat cucumbers Then I should have cucumbers Examples: Vertical | start | 12 | 2 | | eat | 5 | 1 | | left | 7 | 1 | This form allows to have tables with lots of columns keeping the maximum text width predictable without significant readability change. The code will look like: .. code-block:: python from pytest_bdd import given, when, then, scenario @scenario( 'outline.feature', 'Outlined given, when, thens', example_converters=dict(start=int, eat=float, left=str) ) def test_outlined(): pass @given('there are cucumbers') def start_cucumbers(start): assert isinstance(start, int) return dict(start=start) @when('I eat cucumbers') def eat_cucumbers(start_cucumbers, eat): assert isinstance(eat, float) start_cucumbers['eat'] = eat @then('I should have cucumbers') def should_have_left_cucumbers(start_cucumbers, start, eat, left): assert isinstance(left, str) assert start - eat == int(left) assert start_cucumbers['start'] == start assert start_cucumbers['eat'] == eat Example code also shows possibility to pass example converters which may be useful if you need parameter types different than strings. Feature examples ^^^^^^^^^^^^^^^^ It's possible to declare example table once for the whole feature, and it will be shared among all the scenarios of that feature: .. code-block:: gherkin Feature: Outline Examples: | start | eat | left | | 12 | 5 | 7 | | 5 | 4 | 1 | Scenario Outline: Eat cucumbers Given there are cucumbers When I eat cucumbers Then I should have cucumbers Scenario Outline: Eat apples Given there are apples When I eat apples Then I should have apples For some more complex case, you might want to parametrize on both levels: feature and scenario. This is allowed as long as parameter names do not clash: .. code-block:: gherkin Feature: Outline Examples: | start | eat | left | | 12 | 5 | 7 | | 5 | 4 | 1 | Scenario Outline: Eat fruits Given there are When I eat Then I should have Examples: | fruits | | oranges | | apples | Scenario Outline: Eat vegetables Given there are When I eat Then I should have Examples: | vegetables | | carrots | | tomatoes | Combine scenario outline and pytest parametrization ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ It's also possible to parametrize the scenario on the python side. The reason for this is that it is sometimes not needed to mention example table for every scenario. The code will look like: .. code-block:: python import pytest from pytest_bdd import scenario, given, when, then # Here we use pytest to parametrize the test with the parameters table @pytest.mark.parametrize( ['start', 'eat', 'left'], [(12, 5, 7)]) @scenario( 'parametrized.feature', 'Parametrized given, when, thens', ) # Note that we should take the same arguments in the test function that we use # for the test parametrization either directly or indirectly (fixtures depend on them). def test_parametrized(start, eat, left): """We don't need to do anything here, everything will be managed by the scenario decorator.""" @given('there are cucumbers') def start_cucumbers(start): return dict(start=start) @when('I eat cucumbers') def eat_cucumbers(start_cucumbers, start, eat): start_cucumbers['eat'] = eat @then('I should have 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 With a parametrized.feature file: .. code-block:: gherkin Feature: parametrized Scenario: Parametrized given, when, thens Given there are cucumbers When I eat cucumbers Then I should have cucumbers The significant downside of this approach is inability to see the test table from the feature file. Organizing your scenarios ------------------------- The more features and scenarios you have, the more important becomes the question about their organization. The things you can do (and that is also a recommended way): * organize your feature files in the folders by semantic groups: :: features │ ├──frontend │ │ │ └──auth │ │ │ └──login.feature └──backend │ └──auth │ └──login.feature This looks fine, but how do you run tests only for certain feature? As pytest-bdd uses pytest, and bdd scenarios are actually normal tests. But test files are separate from the feature files, the mapping is up to developers, so the test files structure can look completely different: :: tests │ └──functional │ └──test_auth.py │ └ """Authentication tests.""" from pytest_bdd import scenario @scenario('frontend/auth/login.feature') def test_logging_in_frontend(): pass @scenario('backend/auth/login.feature') def test_logging_in_backend(): pass For picking up tests to run we can use `tests selection `_ technique. The problem is that you have to know how your tests are organized, knowing only the feature files organization is not enough. `cucumber tags `_ introduce standard way of categorizing your features and scenarios, which pytest-bdd supports. For example, we could have: .. code-block:: gherkin @login @backend Feature: Login @successful Scenario: Successful login pytest-bdd uses `pytest markers `_ as a `storage` of the tags for the given scenario test, so we can use standard test selection: .. code-block:: bash py.test -m "backend and login and successful" The feature and scenario markers are not different from standard pytest markers, and the `@` symbol is stripped out automatically to allow test selector expressions. If you want to have bdd-related tags to be distinguishable from the other test markers, use prefix like `bdd`. Note that if you use pytest `--strict` option, all bdd tags mentioned in the feature files should be also in the `markers` setting of the `pytest.ini` config. Also for tags please use names which are python-compartible variable names, eg starts with a non-number, underscore alphanumberic, etc. That way you can safely use tags for tests filtering. You can customize how hooks are converted to pytest marks by implementing the ``pytest_bdd_apply_tag`` hook and returning ``True`` from it: .. code-block:: python def pytest_bdd_apply_tag(tag, function): if tag == 'todo': marker = pytest.mark.skip(reason="Not implemented yet") marker(function) return True else: # Fall back to pytest-bdd's default behavior return None Test setup ---------- Test setup is implemented within the Given section. Even though these steps are executed imperatively to apply possible side-effects, pytest-bdd is trying to benefit of the PyTest fixtures which is based on the dependency injection and makes the setup more declarative style. .. code-block:: python @given('I have a beautiful article') def article(): return Article(is_beautiful=True) This also declares a PyTest fixture "article" and any other step can depend on it. .. code-block:: gherkin Given I have a beautiful article When I publish this article When step is referring the article to publish it. .. code-block:: python @when('I publish this article') def publish_article(article): article.publish() Many other BDD toolkits operate a global context and put the side effects there. This makes it very difficult to implement the steps, because the dependencies appear only as the side-effects in the run-time and not declared in the code. The publish article step has to trust that the article is already in the context, has to know the name of the attribute it is stored there, the type etc. In pytest-bdd you just declare an argument of the step function that it depends on and the PyTest will make sure to provide it. Still side effects can be applied in the imperative style by design of the BDD. .. code-block:: gherkin Given I have a beautiful article And my article is published Functional tests can reuse your fixture libraries created for the unit-tests and upgrade them by applying the side effects. .. code-block:: python given('I have a beautiful article', fixture='article') @given('my article is published') def published_article(article): article.publish() return article This way side-effects were applied to our article and PyTest makes sure that all steps that require the "article" fixture will receive the same object. The value of the "published_article" and the "article" fixtures is the same object. Fixtures are evaluated only once within the PyTest scope and their values are cached. In case of Given steps and the step arguments mentioning the same given step makes no sense. It won't be executed second time. .. code-block:: gherkin Given I have a beautiful article And some other thing And I have a beautiful article # Won't be executed, exception is raised pytest-bdd will raise an exception even in the case of the steps that use regular expression patterns to get arguments. .. code-block:: gherkin Given I have 1 cucumbers And I have 2 cucumbers # Exception is raised Will raise an exception if the step is using the regular expression pattern. .. code-block:: python @given(re.compile('I have (?P\d+) cucumbers')) def cucumbers(n): return create_cucumbers(n) Backgrounds ----------- It's often the case that to cover certain feature, you'll need multiple scenarios. And it's logical that the setup for those scenarios will have some common parts (if not equal). For this, there are `backgrounds`. pytest-bdd implements `Gherkin backgrounds `_ for features. .. code-block:: gherkin Feature: Multiple site support Background: Given a global administrator named "Greg" And a blog named "Greg's anti-tax rants" And a customer named "Wilson" And a blog named "Expensive Therapy" owned by "Wilson" Scenario: Wilson posts to his own blog Given I am logged in as Wilson When I try to post to "Expensive Therapy" Then I should see "Your article was published." Scenario: Greg posts to a client's blog Given I am logged in as Greg When I try to post to "Expensive Therapy" Then I should see "Your article was published." In this example, all steps from the background will be executed before all the scenario's own given steps, adding possibility to prepare some common setup for multiple scenarios in a single feature. About background best practices, please read `here `_. .. NOTE:: There is only step "Given" should be used in "Background" section, steps "When" and "Then" are prohibited, because their purpose are related to actions and consuming outcomes, that is conflict with "Background" aim - prepare system for tests or "put the system in a known state" as "Given" does it. The statement above is applied for strict Gherkin mode, which is enabled by default. Reusing fixtures ---------------- Sometimes scenarios define new names for the existing fixture that can be inherited (reused). For example, if we have pytest fixture: .. code-block:: python @pytest.fixture def article(): """Test article.""" return Article() Then this fixture can be reused with other names using given(): .. code-block:: python given('I have beautiful article', fixture='article') This will be equivalent to: .. code-block:: python @given('I have beautiful article') def i_have_an_article(article): """I have an article.""" return article Reusing 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: .. code-block:: gherkin Scenario: All steps are declared in the conftest Given I have a bar Then bar should have value "bar" conftest.py: .. code-block:: python 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: .. code-block:: python @scenario('common_steps.feature', 'All steps are declared in the conftest') def test_conftest(): pass There are no definitions of the steps in the test file. They were collected from the parent conftests. Using unicode in the feature files ---------------------------------- As mentioned above, by default, utf-8 encoding is used for parsing feature files. For steps definition, you should use unicode strings, which is the default in python 3. If you are on python 2, make sure you use unicode strings by prefixing them with the `u` sign. .. code-block:: python @given(parsers.re(u"у мене є рядок який містить '{0}'".format(u'(?P.+)'))) def there_is_a_string_with_content(content, string): """Create string with unicode content.""" string['content'] = content Default steps ------------- Here is the list of steps that are implemented inside of the pytest-bdd: given * trace - enters the `pdb` debugger via `pytest.set_trace()` when * trace - enters the `pdb` debugger via `pytest.set_trace()` then * trace - enters the `pdb` debugger via `pytest.set_trace()` Feature file paths ------------------ By default, pytest-bdd will use current module's path as base path for finding feature files, but this behaviour can be changed in the pytest configuration file (i.e. `pytest.ini`, `tox.ini` or `setup.cfg`) by declaring the new base path in the `bdd_features_base_dir` key. The path is interpreted as relative to the working directory when starting pytest. You can also override features base path on a per-scenario basis, in order to override the path for specific tests. pytest.ini: .. code-block:: ini [pytest] bdd_features_base_dir = features/ tests/test_publish_article.py: .. code-block:: python from pytest_bdd import scenario @scenario('foo.feature', 'Foo feature in features/foo.feature') def test_foo(): pass @scenario( 'foo.feature', 'Foo feature in tests/local-features/foo.feature', features_base_dir='./local-features/', ) def test_foo_local(): pass The `features_base_dir` parameter can also be passed to the `@scenario` decorator. Avoid retyping the feature file name ------------------------------------ If you want to avoid retyping the feature file name when defining your scenarios in a test file, use functools.partial. This will make your life much easier when defining multiple scenarios in a test file. For example: test_publish_article.py: .. code-block:: python from functools import partial import pytest_bdd scenario = partial(pytest_bdd.scenario, '/path/to/publish_article.feature') @scenario('Publishing the article') def test_publish(): pass @scenario('Publishing the article as unprivileged user') def test_publish_unprivileged(): pass You can learn more about `functools.partial `_ in the Python docs. Relax strict Gherkin language validation ---------------------------------------- If your scenarios are not written in `proper` Gherkin language, e.g. they are more like textual scripts, then you might find it hard to use `pytest-bdd` as by default it validates the order of step types (given-when-then). To relax that validation, just pass ``strict_gherkin=False`` to the ``scenario`` and ``scenarios`` decorators: test_publish_article.py: .. code-block:: python from pytest_bdd import scenario @scenario('publish_article.feature', 'Publishing the article in a weird way', strict_gherkin=False) def test_publish(): pass Hooks ----- pytest-bdd exposes several `pytest hooks `_ which might be helpful building useful reporting, visualization, etc on top of it: * pytest_bdd_before_scenario(request, feature, scenario) - Called before scenario is executed * pytest_bdd_after_scenario(request, feature, scenario) - Called after scenario is executed (even if one of steps has failed) * pytest_bdd_before_step(request, feature, scenario, step, step_func) - Called before step function is executed and it's arguments evaluated * pytest_bdd_before_step_call(request, feature, scenario, step, step_func, step_func_args) - Called before step function is executed with evaluated arguments * pytest_bdd_after_step(request, feature, scenario, step, step_func, step_func_args) - Called after step function is successfully executed * pytest_bdd_step_error(request, feature, scenario, step, step_func, step_func_args, exception) - Called when step function failed to execute * pytest_bdd_step_validation_error(request, feature, scenario, step, step_func, step_func_args, exception) - Called when step failed to validate * pytest_bdd_step_func_lookup_error(request, feature, scenario, step, exception) - Called when step lookup failed Browser testing --------------- Tools recommended to use for browser testing: * pytest-splinter_ - pytest `splinter `_ integration for the real browser testing Reporting --------- It's important to have nice reporting out of your bdd tests. Cucumber introduced some kind of standard for `json format `_ which can be used for `this `_ jenkins plugin To have an output in json format: :: py.test --cucumberjson= This will output an expanded (meaning scenario outlines will be expanded to several scenarios) cucumber format. To also fill in parameters in the step name, you have to explicitly tell pytest-bdd to use the expanded format: :: py.test --cucumberjson= --cucumberjson-expanded To enable gherkin-formatted output on terminal, use :: py.test --gherkin-terminal-reporter Terminal reporter supports expanded format as well :: py.test --gherkin-terminal-reporter-expanded Test code generation helpers ---------------------------- For newcomers it's sometimes hard to write all needed test code without being frustrated. To simplify their life, simple code generator was implemented. It allows to create fully functional but of course empty tests and step definitions for given a feature file. It's done as a separate console script provided by pytest-bdd package: :: pytest-bdd generate .. It will print the generated code to the standard output so you can easily redirect it to the file: :: pytest-bdd generate features/some.feature > tests/functional/test_some.py Advanced code generation ------------------------ For more experienced users, there's smart code generation/suggestion feature. It will only generate the test code which is not yet there, checking existing tests and step definitions the same way it's done during the test execution. The code suggestion tool is called via passing additional pytest arguments: :: py.test --generate-missing --feature features tests/functional The output will be like: :: ============================= test session starts ============================== platform linux2 -- Python 2.7.6 -- py-1.4.24 -- pytest-2.6.2 plugins: xdist, pep8, cov, cache, bdd, bdd, bdd collected 2 items Scenario is not bound to any test: "Code is generated for scenarios which are not bound to any tests" in feature "Missing code generation" in /tmp/pytest-552/testdir/test_generate_missing0/tests/generation.feature -------------------------------------------------------------------------------- Step is not defined: "I have a custom bar" in scenario: "Code is generated for scenario steps which are not yet defined(implemented)" in feature "Missing code generation" in /tmp/pytest-552/testdir/test_generate_missing0/tests/generation.feature -------------------------------------------------------------------------------- Please place the code above to the test file(s): @scenario('tests/generation.feature', 'Code is generated for scenarios which are not bound to any tests') def test_Code_is_generated_for_scenarios_which_are_not_bound_to_any_tests(): """Code is generated for scenarios which are not bound to any tests.""" @given('I have a custom bar') def I_have_a_custom_bar(): """I have a custom bar.""" As as side effect, the tool will validate the files for format errors, also some of the logic bugs, for example the ordering of the types of the steps. Migration of your tests from versions 2.x.x ------------------------------------------------ In version 3.0.0, the fixtures ``pytestbdd_feature_base_dir`` and ``pytestbdd_strict_gherkin`` have been removed. If you used ``pytestbdd_feature_base_dir`` fixture to override the path discovery, you can instead configure it in ``pytest.ini``: .. code-block:: ini [pytest] bdd_features_base_dir = features/ For more details, check the `Feature file paths`_ section. If you used ``pytestbdd_strict_gherkin`` fixture to relax the parser, you can instead specify ``strict_gherkin=False`` in the declaration of your scenarios, or change it globally in the pytest configuration file: .. code-block:: ini [pytest] bdd_strict_gherkin = false For more details, check the `Relax strict Gherkin language validation`_ section. Migration of your tests from versions 0.x.x-1.x.x ------------------------------------------------- In version 2.0.0, the backwards-incompartible change was introduced: scenario function can now only be used as a decorator. Reasons for that: * test code readability is much higher using normal python function syntax; * pytest-bdd internals are much cleaner and shorter when using single approach instead of supporting two; * after moving to parsing-on-import-time approach for feature files, it's not possible to detect whether it's a decorator more or not, so to support it along with functional approach there needed to be special parameter for that, which is also a backwards-incompartible change. To help users migrate to newer version, there's migration subcommand of the `pytest-bdd` console script: :: # run migration script pytest-bdd migrate Under the hood the script does the replacement from this: .. code-block:: python test_function = scenario('publish_article.feature', 'Publishing the article') to this: .. code-block:: python @scenario('publish_article.feature', 'Publishing the article') def test_function(): pass License ------- This software is licensed under the `MIT license `_. © 2013-2014 Oleg Pidsadnyi, Anatoly Bubenkov and others pytest-bdd-3.2.1/docs/000077500000000000000000000000001352722444400145335ustar00rootroot00000000000000pytest-bdd-3.2.1/docs/Makefile000066400000000000000000000127141352722444400162000ustar00rootroot00000000000000# Makefile for Sphinx documentation # # You can set these variables from the command line. SPHINXOPTS = SPHINXBUILD = sphinx-build PAPER = BUILDDIR = _build # Internal variables. PAPEROPT_a4 = -D latex_paper_size=a4 PAPEROPT_letter = -D latex_paper_size=letter ALLSPHINXOPTS = -d $(BUILDDIR)/doctrees $(PAPEROPT_$(PAPER)) $(SPHINXOPTS) . # the i18n builder cannot share the environment and doctrees with the others I18NSPHINXOPTS = $(PAPEROPT_$(PAPER)) $(SPHINXOPTS) . .PHONY: help clean html dirhtml singlehtml pickle json htmlhelp qthelp devhelp epub latex latexpdf text man changes linkcheck doctest gettext help: @echo "Please use \`make ' where is one of" @echo " html to make standalone HTML files" @echo " dirhtml to make HTML files named index.html in directories" @echo " singlehtml to make a single large HTML file" @echo " pickle to make pickle files" @echo " json to make JSON files" @echo " htmlhelp to make HTML files and a HTML help project" @echo " qthelp to make HTML files and a qthelp project" @echo " devhelp to make HTML files and a Devhelp project" @echo " epub to make an epub" @echo " latex to make LaTeX files, you can set PAPER=a4 or PAPER=letter" @echo " latexpdf to make LaTeX files and run them through pdflatex" @echo " text to make text files" @echo " man to make manual pages" @echo " texinfo to make Texinfo files" @echo " info to make Texinfo files and run them through makeinfo" @echo " gettext to make PO message catalogs" @echo " changes to make an overview of all changed/added/deprecated items" @echo " linkcheck to check all external links for integrity" @echo " doctest to run all doctests embedded in the documentation (if enabled)" clean: -rm -rf $(BUILDDIR)/* html: $(SPHINXBUILD) -b html $(ALLSPHINXOPTS) $(BUILDDIR)/html @echo @echo "Build finished. The HTML pages are in $(BUILDDIR)/html." dirhtml: $(SPHINXBUILD) -b dirhtml $(ALLSPHINXOPTS) $(BUILDDIR)/dirhtml @echo @echo "Build finished. The HTML pages are in $(BUILDDIR)/dirhtml." singlehtml: $(SPHINXBUILD) -b singlehtml $(ALLSPHINXOPTS) $(BUILDDIR)/singlehtml @echo @echo "Build finished. The HTML page is in $(BUILDDIR)/singlehtml." pickle: $(SPHINXBUILD) -b pickle $(ALLSPHINXOPTS) $(BUILDDIR)/pickle @echo @echo "Build finished; now you can process the pickle files." json: $(SPHINXBUILD) -b json $(ALLSPHINXOPTS) $(BUILDDIR)/json @echo @echo "Build finished; now you can process the JSON files." htmlhelp: $(SPHINXBUILD) -b htmlhelp $(ALLSPHINXOPTS) $(BUILDDIR)/htmlhelp @echo @echo "Build finished; now you can run HTML Help Workshop with the" \ ".hhp project file in $(BUILDDIR)/htmlhelp." qthelp: $(SPHINXBUILD) -b qthelp $(ALLSPHINXOPTS) $(BUILDDIR)/qthelp @echo @echo "Build finished; now you can run "qcollectiongenerator" with the" \ ".qhcp project file in $(BUILDDIR)/qthelp, like this:" @echo "# qcollectiongenerator $(BUILDDIR)/qthelp/Pytest-BDD.qhcp" @echo "To view the help file:" @echo "# assistant -collectionFile $(BUILDDIR)/qthelp/Pytest-BDD.qhc" devhelp: $(SPHINXBUILD) -b devhelp $(ALLSPHINXOPTS) $(BUILDDIR)/devhelp @echo @echo "Build finished." @echo "To view the help file:" @echo "# mkdir -p $$HOME/.local/share/devhelp/Pytest-BDD" @echo "# ln -s $(BUILDDIR)/devhelp $$HOME/.local/share/devhelp/Pytest-BDD" @echo "# devhelp" epub: $(SPHINXBUILD) -b epub $(ALLSPHINXOPTS) $(BUILDDIR)/epub @echo @echo "Build finished. The epub file is in $(BUILDDIR)/epub." latex: $(SPHINXBUILD) -b latex $(ALLSPHINXOPTS) $(BUILDDIR)/latex @echo @echo "Build finished; the LaTeX files are in $(BUILDDIR)/latex." @echo "Run \`make' in that directory to run these through (pdf)latex" \ "(use \`make latexpdf' here to do that automatically)." latexpdf: $(SPHINXBUILD) -b latex $(ALLSPHINXOPTS) $(BUILDDIR)/latex @echo "Running LaTeX files through pdflatex..." $(MAKE) -C $(BUILDDIR)/latex all-pdf @echo "pdflatex finished; the PDF files are in $(BUILDDIR)/latex." text: $(SPHINXBUILD) -b text $(ALLSPHINXOPTS) $(BUILDDIR)/text @echo @echo "Build finished. The text files are in $(BUILDDIR)/text." man: $(SPHINXBUILD) -b man $(ALLSPHINXOPTS) $(BUILDDIR)/man @echo @echo "Build finished. The manual pages are in $(BUILDDIR)/man." texinfo: $(SPHINXBUILD) -b texinfo $(ALLSPHINXOPTS) $(BUILDDIR)/texinfo @echo @echo "Build finished. The Texinfo files are in $(BUILDDIR)/texinfo." @echo "Run \`make' in that directory to run these through makeinfo" \ "(use \`make info' here to do that automatically)." info: $(SPHINXBUILD) -b texinfo $(ALLSPHINXOPTS) $(BUILDDIR)/texinfo @echo "Running Texinfo files through makeinfo..." make -C $(BUILDDIR)/texinfo info @echo "makeinfo finished; the Info files are in $(BUILDDIR)/texinfo." gettext: $(SPHINXBUILD) -b gettext $(I18NSPHINXOPTS) $(BUILDDIR)/locale @echo @echo "Build finished. The message catalogs are in $(BUILDDIR)/locale." changes: $(SPHINXBUILD) -b changes $(ALLSPHINXOPTS) $(BUILDDIR)/changes @echo @echo "The overview file is in $(BUILDDIR)/changes." linkcheck: $(SPHINXBUILD) -b linkcheck $(ALLSPHINXOPTS) $(BUILDDIR)/linkcheck @echo @echo "Link check complete; look for any errors in the above output " \ "or in $(BUILDDIR)/linkcheck/output.txt." doctest: $(SPHINXBUILD) -b doctest $(ALLSPHINXOPTS) $(BUILDDIR)/doctest @echo "Testing of doctests in the sources finished, look at the " \ "results in $(BUILDDIR)/doctest/output.txt." pytest-bdd-3.2.1/docs/conf.py000066400000000000000000000172611352722444400160410ustar00rootroot00000000000000# -*- coding: utf-8 -*- # # Pytest-BDD documentation build configuration file, created by # sphinx-quickstart on Sun Apr 7 21:07:56 2013. # # This file is execfile()d with the current directory set to its containing dir. # # Note that not all possible configuration values are present in this # autogenerated file. # # All configuration values have a default; values that are commented out # serve to show the default. # If extensions (or modules to document with autodoc) are in another directory, # add these directories to sys.path here. If the directory is relative to the # documentation root, use os.path.abspath to make it absolute, like shown here. import sys, os sys.path.insert(0, os.path.abspath('..')) import pytest_bdd # -- General configuration ----------------------------------------------------- # If your documentation needs a minimal Sphinx version, state it here. #needs_sphinx = '1.0' # Add any Sphinx extension module names here, as strings. They can be extensions # coming with Sphinx (named 'sphinx.ext.*') or your custom ones. extensions = ['sphinx.ext.autodoc', 'sphinx.ext.viewcode'] # Add any paths that contain templates here, relative to this directory. templates_path = ['_templates'] # The suffix of source filenames. source_suffix = '.rst' # The encoding of source files. #source_encoding = 'utf-8-sig' # The master toctree document. master_doc = 'index' # General information about the project. project = u'Pytest-BDD' copyright = u'2013, Oleg Pidsadnyi' # The version info for the project you're documenting, acts as replacement for # |version| and |release|, also used in various other places throughout the # built documents. # # The short X.Y version. version = pytest_bdd.__version__ # The full version, including alpha/beta/rc tags. release = pytest_bdd.__version__ # The language for content autogenerated by Sphinx. Refer to documentation # for a list of supported languages. #language = None # There are two options for replacing |today|: either, you set today to some # non-false value, then it is used: #today = '' # Else, today_fmt is used as the format for a strftime call. #today_fmt = '%B %d, %Y' # List of patterns, relative to source directory, that match files and # directories to ignore when looking for source files. exclude_patterns = ['_build'] # The reST default role (used for this markup: `text`) to use for all documents. #default_role = None # If true, '()' will be appended to :func: etc. cross-reference text. #add_function_parentheses = True # If true, the current module name will be prepended to all description # unit titles (such as .. function::). #add_module_names = True # If true, sectionauthor and moduleauthor directives will be shown in the # output. They are ignored by default. #show_authors = False # The name of the Pygments (syntax highlighting) style to use. pygments_style = 'sphinx' # A list of ignored prefixes for module index sorting. #modindex_common_prefix = [] # -- Options for HTML output --------------------------------------------------- # The theme to use for HTML and HTML Help pages. See the documentation for # a list of builtin themes. html_theme = 'default' # Theme options are theme-specific and customize the look and feel of a theme # further. For a list of options available for each theme, see the # documentation. #html_theme_options = {} # Add any paths that contain custom themes here, relative to this directory. #html_theme_path = [] # The name for this set of Sphinx documents. If None, it defaults to # " v documentation". #html_title = None # A shorter title for the navigation bar. Default is the same as html_title. #html_short_title = None # The name of an image file (relative to this directory) to place at the top # of the sidebar. #html_logo = None # The name of an image file (within the static path) to use as favicon of the # docs. This file should be a Windows icon file (.ico) being 16x16 or 32x32 # pixels large. #html_favicon = None # Add any paths that contain custom static files (such as style sheets) here, # relative to this directory. They are copied after the builtin static files, # so a file named "default.css" will overwrite the builtin "default.css". html_static_path = ['_static'] # If not '', a 'Last updated on:' timestamp is inserted at every page bottom, # using the given strftime format. #html_last_updated_fmt = '%b %d, %Y' # If true, SmartyPants will be used to convert quotes and dashes to # typographically correct entities. #html_use_smartypants = True # Custom sidebar templates, maps document names to template names. #html_sidebars = {} # Additional templates that should be rendered to pages, maps page names to # template names. #html_additional_pages = {} # If false, no module index is generated. #html_domain_indices = True # If false, no index is generated. #html_use_index = True # If true, the index is split into individual pages for each letter. #html_split_index = False # If true, links to the reST sources are added to the pages. #html_show_sourcelink = True # If true, "Created using Sphinx" is shown in the HTML footer. Default is True. #html_show_sphinx = True # If true, "(C) Copyright ..." is shown in the HTML footer. Default is True. #html_show_copyright = True # If true, an OpenSearch description file will be output, and all pages will # contain a tag referring to it. The value of this option must be the # base URL from which the finished HTML is served. #html_use_opensearch = '' # This is the file name suffix for HTML files (e.g. ".xhtml"). #html_file_suffix = None # Output file base name for HTML help builder. htmlhelp_basename = 'Pytest-BDDdoc' # -- Options for LaTeX output -------------------------------------------------- latex_elements = { # The paper size ('letterpaper' or 'a4paper'). #'papersize': 'letterpaper', # The font size ('10pt', '11pt' or '12pt'). #'pointsize': '10pt', # Additional stuff for the LaTeX preamble. #'preamble': '', } # Grouping the document tree into LaTeX files. List of tuples # (source start file, target name, title, author, documentclass [howto/manual]). latex_documents = [ ('index', 'Pytest-BDD.tex', u'Pytest-BDD Documentation', u'Oleg Pidsadnyi', 'manual'), ] # The name of an image file (relative to this directory) to place at the top of # the title page. #latex_logo = None # For "manual" documents, if this is true, then toplevel headings are parts, # not chapters. #latex_use_parts = False # If true, show page references after internal links. #latex_show_pagerefs = False # If true, show URL addresses after external links. #latex_show_urls = False # Documents to append as an appendix to all manuals. #latex_appendices = [] # If false, no module index is generated. #latex_domain_indices = True # -- Options for manual page output -------------------------------------------- # One entry per manual page. List of tuples # (source start file, name, description, authors, manual section). man_pages = [ ('index', 'pytest-bdd', u'Pytest-BDD Documentation', [u'Oleg Pidsadnyi'], 1) ] # If true, show URL addresses after external links. #man_show_urls = False # -- Options for Texinfo output ------------------------------------------------ # Grouping the document tree into Texinfo files. List of tuples # (source start file, target name, title, author, # dir menu entry, description, category) texinfo_documents = [ ('index', 'Pytest-BDD', u'Pytest-BDD Documentation', u'Oleg Pidsadnyi', 'Pytest-BDD', 'One line description of project.', 'Miscellaneous'), ] # Documents to append as an appendix to all manuals. #texinfo_appendices = [] # If false, no module index is generated. #texinfo_domain_indices = True # How to display URL addresses: 'footnote', 'no', or 'inline'. #texinfo_show_urls = 'footnote' pytest-bdd-3.2.1/docs/index.rst000066400000000000000000000002631352722444400163750ustar00rootroot00000000000000Welcome to Pytest-BDD's documentation! ====================================== .. contents:: .. include:: ../README.rst .. include:: ../AUTHORS.rst .. include:: ../CHANGES.rst pytest-bdd-3.2.1/pytest.ini000066400000000000000000000001461352722444400156350ustar00rootroot00000000000000[pytest] pep8maxlinelength=120 addopts=-vvl filterwarnings = error ignore::DeprecationWarning pytest-bdd-3.2.1/pytest_bdd/000077500000000000000000000000001352722444400157445ustar00rootroot00000000000000pytest-bdd-3.2.1/pytest_bdd/__init__.py000066400000000000000000000003711352722444400200560ustar00rootroot00000000000000"""pytest-bdd public API.""" from pytest_bdd.steps import given, when, then from pytest_bdd.scenario import scenario, scenarios __version__ = '3.2.1' __all__ = [given.__name__, when.__name__, then.__name__, scenario.__name__, scenarios.__name__] pytest-bdd-3.2.1/pytest_bdd/cucumber_json.py000066400000000000000000000150001352722444400211500ustar00rootroot00000000000000"""Cucumber json output formatter.""" import codecs import json import math import os import sys import time import six from .feature import force_unicode if six.PY3: long = int def add_options(parser): """Add pytest-bdd options.""" group = parser.getgroup("bdd", "Cucumber JSON") group.addoption( "--cucumberjson", "--cucumber-json", action="store", dest="cucumber_json_path", metavar="path", default=None, help="create cucumber json style report file at given path.", ) group._addoption( "--cucumberjson-expanded", "--cucumber-json-expanded", action="store_true", dest="expand", default=False, help="expand scenario outlines into scenarios and fill in the step names", ) def configure(config): cucumber_json_path = config.option.cucumber_json_path # prevent opening json log on slave nodes (xdist) if cucumber_json_path and not hasattr(config, "slaveinput"): config._bddcucumberjson = LogBDDCucumberJSON(cucumber_json_path, expand=config.option.expand) config.pluginmanager.register(config._bddcucumberjson) def unconfigure(config): xml = getattr(config, "_bddcucumberjson", None) if xml is not None: del config._bddcucumberjson config.pluginmanager.unregister(xml) class LogBDDCucumberJSON(object): """Logging plugin for cucumber like json output.""" def __init__(self, logfile, expand=False): logfile = os.path.expanduser(os.path.expandvars(logfile)) self.logfile = os.path.normpath(os.path.abspath(logfile)) self.features = {} self.expand = expand def append(self, obj): self.features[-1].append(obj) def _get_result(self, step, report, error_message=False): """Get scenario test run result. :param step: `Step` step we get result for :param report: pytest `Report` object :return: `dict` in form {"status": "", ["error_message": ""]} """ result = {} if report.passed or not step["failed"]: # ignore setup/teardown result = {"status": "passed"} elif report.failed and step["failed"]: result = { "status": "failed", "error_message": force_unicode(report.longrepr) if error_message else "", } elif report.skipped: result = {"status": "skipped"} result['duration'] = long(math.floor((10 ** 9) * step["duration"])) # nanosec return result def _serialize_tags(self, item): """Serialize item's tags. :param item: json-serialized `Scenario` or `Feature`. :return: `list` of `dict` in the form of: [ { "name": "", "line": 2, } ] """ return [ { "name": tag, "line": item["line_number"] - 1 } for tag in item["tags"] ] def _format_name(self, name, keys, values): for param, value in zip(keys, values): name = name.replace('<{}>'.format(param), value) return name def _format_step_name(self, report, step): examples = report.scenario["examples"] if len(examples) == 0: return step["name"] # we take the keys from the first "examples", but in each table, the keys should # be the same anyway since all the variables need to be filled in. keys, values = examples[0]["rows"] row_index = examples[0]["row_index"] return self._format_name(step["name"], keys, values[row_index]) def pytest_runtest_logreport(self, report): try: scenario = report.scenario except AttributeError: # skip reporting for non-bdd tests return if not scenario["steps"] or report.when != "call": # skip if there isn't a result or scenario has no steps return def stepmap(step): error_message = False if step['failed'] and not scenario.setdefault('failed', False): scenario['failed'] = True error_message = True if self.expand: # XXX The format is already 'expanded' (scenario oultines -> scenarios), # but the step names were not filled in with parameters. To be backwards # compatible, do not fill in the step names unless explicitly asked for. step_name = self._format_step_name(report, step) else: step_name = step["name"] return { "keyword": step['keyword'], "name": step_name, "line": step['line_number'], "match": { "location": "", }, "result": self._get_result(step, report, error_message), } if scenario["feature"]["filename"] not in self.features: self.features[scenario["feature"]["filename"]] = { "keyword": "Feature", "uri": scenario["feature"]["rel_filename"], "name": scenario["feature"]["name"] or scenario["feature"]["rel_filename"], "id": scenario["feature"]["rel_filename"].lower().replace(" ", "-"), "line": scenario['feature']["line_number"], "description": scenario["feature"]["description"], "tags": self._serialize_tags(scenario["feature"]), "elements": [], } self.features[scenario["feature"]["filename"]]["elements"].append({ "keyword": "Scenario", "id": report.item["name"], "name": scenario["name"], "line": scenario["line_number"], "description": "", "tags": self._serialize_tags(scenario), "type": "scenario", "steps": [stepmap(step) for step in scenario["steps"]], }) def pytest_sessionstart(self): self.suite_start_time = time.time() def pytest_sessionfinish(self): if sys.version_info[0] < 3: logfile_open = codecs.open else: logfile_open = open with logfile_open(self.logfile, "w", encoding="utf-8") as logfile: logfile.write(json.dumps(list(self.features.values()))) def pytest_terminal_summary(self, terminalreporter): terminalreporter.write_sep("-", "generated json file: %s" % (self.logfile)) pytest-bdd-3.2.1/pytest_bdd/exceptions.py000066400000000000000000000026631352722444400205060ustar00rootroot00000000000000"""pytest-bdd Exceptions.""" import six class StepError(Exception): """Step declaration error.""" class ScenarioIsDecoratorOnly(Exception): """Scenario can be only used as decorator.""" class ScenarioValidationError(Exception): """Base class for scenario validation.""" class ScenarioNotFound(ScenarioValidationError): """Scenario Not Found.""" class ExamplesNotValidError(ScenarioValidationError): """Example table is not valid.""" class ScenarioExamplesNotValidError(ScenarioValidationError): """Scenario steps parameters do not match declared scenario examples.""" class FeatureExamplesNotValidError(ScenarioValidationError): """Feature example table is not valid.""" class StepTypeError(ScenarioValidationError): """Step definition is not of the type expected in the scenario.""" class GivenAlreadyUsed(ScenarioValidationError): """Fixture that implements the Given has been already used.""" class StepDefinitionNotFoundError(Exception): """Step definition not found.""" class InvalidStepParserError(Exception): """Invalid step parser.""" class NoScenariosFound(Exception): """No scenarios found.""" @six.python_2_unicode_compatible class FeatureError(Exception): """Feature parse error.""" message = u"{0}.\nLine number: {1}.\nLine: {2}.\nFile: {3}" def __str__(self): """String representation.""" return self.message.format(*self.args) pytest-bdd-3.2.1/pytest_bdd/feature.py000066400000000000000000000470351352722444400177620ustar00rootroot00000000000000"""Feature. The way of describing the behavior is based on Gherkin language, but a very limited version. It doesn't support any parameter tables. If the parametrization is needed to generate more test cases it can be done on the fixture level of the pytest. The syntax can be used here to make a connection between steps and it will also validate the parameters mentioned in the steps with ones provided in the pytest parametrization table. Syntax example: 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 :note: The "#" symbol is used for comments. :note: There're no multiline steps, the description of the step must fit in one line. """ from collections import OrderedDict from os import path as op import codecs import re import sys import textwrap import glob2 import six from . import types from . import exceptions # Global features dictionary features = {} STEP_PREFIXES = [ ("Feature: ", types.FEATURE), ("Scenario Outline: ", types.SCENARIO_OUTLINE), ("Examples: Vertical", types.EXAMPLES_VERTICAL), ("Examples:", types.EXAMPLES), ("Scenario: ", types.SCENARIO), ("Background:", types.BACKGROUND), ("Given ", types.GIVEN), ("When ", types.WHEN), ("Then ", types.THEN), ("@", types.TAG), # Continuation of the previously mentioned step type ("And ", None), ("But ", None), ] STEP_PARAM_RE = re.compile(r"\<(.+?)\>") COMMENT_RE = re.compile(r'(^|(?<=\s))#') def get_step_type(line): """Detect step type by the beginning of the line. :param str line: Line of the Feature file. :return: SCENARIO, GIVEN, WHEN, THEN, or `None` if can't be detected. """ for prefix, _type in STEP_PREFIXES: if line.startswith(prefix): return _type def strip_comments(line): """Remove comments. :param str line: Line of the Feature file. :return: Stripped line. """ res = COMMENT_RE.search(line) if res: line = line[:res.start()] return line.strip() def parse_line(line): """Parse step line to get the step prefix (Scenario, Given, When, Then or And) and the actual step name. :param line: Line of the Feature file. :return: `tuple` in form ("", ""). """ for prefix, _ in STEP_PREFIXES: if line.startswith(prefix): return prefix.strip(), line[len(prefix):].strip() return "", line def force_unicode(obj, encoding="utf-8"): """Get the unicode string out of given object (python 2 and python 3). :param obj: An `object`, usually a string. :return: unicode string. """ if sys.version_info < (3, 0): if isinstance(obj, str): return obj.decode(encoding) else: return unicode(obj) else: # pragma: no cover return str(obj) def force_encode(string, encoding="utf-8"): """Force string encoding (Python compatibility function). :param str string: A string value. :param str encoding: Encoding. :return: Encoded string. """ if sys.version_info < (3, 0): if isinstance(string, unicode): string = string.encode(encoding) return string def get_tags(line): """Get tags out of the given line. :param str line: Feature file text line. :return: List of tags. """ if not line or not line.strip().startswith('@'): return set() return ( set((tag.lstrip('@') for tag in line.strip().split(' @') if len(tag) > 1)) ) def get_features(paths, **kwargs): """Get features for given paths. :param list paths: `list` of paths (file or dirs) :return: `list` of `Feature` objects. """ seen_names = set() features = [] for path in paths: if path not in seen_names: seen_names.add(path) if op.isdir(path): features.extend( get_features( glob2.iglob(op.join(path, "**", "*.feature")), **kwargs ) ) else: base, name = op.split(path) feature = Feature.get_feature(base, name, **kwargs) features.append(feature) features.sort(key=lambda feature: feature.name or feature.filename) return features class Examples(object): """Example table.""" def __init__(self): """Initialize examples instance.""" self.example_params = [] self.examples = [] self.vertical_examples = [] self.line_number = None self.name = None def set_param_names(self, keys): """Set parameter names. :param names: `list` of `string` parameter names. """ self.example_params = [str(key) for key in keys] def add_example(self, values): """Add example. :param values: `list` of `string` parameter values. """ self.examples.append(values) def add_example_row(self, param, values): """Add example row. :param param: `str` parameter name :param values: `list` of `string` parameter values """ if param in self.example_params: raise exceptions.ExamplesNotValidError( """Example rows should contain unique parameters. "{0}" appeared more than once""".format( param, ) ) self.example_params.append(param) self.vertical_examples.append(values) def get_params(self, converters, builtin=False): """Get scenario pytest parametrization table. :param converters: `dict` of converter functions to convert parameter values """ param_count = len(self.example_params) if self.vertical_examples and not self.examples: for value_index in range(len(self.vertical_examples[0])): example = [] for param_index in range(param_count): example.append(self.vertical_examples[param_index][value_index]) self.examples.append(example) if self.examples: params = [] for example in self.examples: example = list(example) for index, param in enumerate(self.example_params): raw_value = example[index] if converters and param in converters: value = converters[param](raw_value) if not builtin or value.__class__.__module__ in {'__builtin__', 'builtins'}: example[index] = value params.append(example) return [self.example_params, params] else: return [] def __bool__(self): """Bool comparison.""" return bool(self.vertical_examples or self.examples) if six.PY2: __nonzero__ = __bool__ class Feature(object): """Feature.""" def __init__(self, basedir, filename, encoding="utf-8", strict_gherkin=True): """Parse the feature file. :param str basedir: Feature files base directory. :param str filename: Relative path to the feature file. :param str encoding: Feature file encoding (utf-8 by default). :param bool strict_gherkin: Flag whether it's a strictly gherkin scenario or not (e.g. it will validate correct gherkin language (given-when-then)) """ self.scenarios = OrderedDict() self.rel_filename = op.join(op.basename(basedir), filename) self.filename = filename = op.abspath(op.join(basedir, filename)) self.line_number = 1 self.name = None self.tags = set() self.examples = Examples() scenario = None mode = None prev_mode = None description = [] step = None multiline_step = False prev_line = None self.background = None with codecs.open(filename, encoding=encoding) as f: content = force_unicode(f.read(), encoding) for line_number, line in enumerate(content.splitlines(), start=1): unindented_line = line.lstrip() line_indent = len(line) - len(unindented_line) if step and (step.indent < line_indent or ((not unindented_line) and multiline_step)): multiline_step = True # multiline step, so just add line and continue step.add_line(line) continue else: step = None multiline_step = False stripped_line = line.strip() clean_line = strip_comments(line) if not clean_line and (not prev_mode or prev_mode not in types.FEATURE): continue mode = get_step_type(clean_line) or mode allowed_prev_mode = (types.BACKGROUND, types.GIVEN) if not strict_gherkin: allowed_prev_mode += (types.WHEN, ) if not scenario and prev_mode not in allowed_prev_mode and mode in types.STEP_TYPES: raise exceptions.FeatureError( "Step definition outside of a Scenario or a Background", line_number, clean_line, filename) if strict_gherkin: if (self.background and not scenario and mode not in ( types.SCENARIO, types.SCENARIO_OUTLINE, types.GIVEN, types.TAG)): raise exceptions.FeatureError( "Background section can only contain Given steps", line_number, clean_line, filename) if mode == types.GIVEN and prev_mode not in ( types.GIVEN, types.SCENARIO, types.SCENARIO_OUTLINE, types.BACKGROUND): raise exceptions.FeatureError( "Given steps must be the first within the Scenario", line_number, clean_line, filename) if mode == types.WHEN and prev_mode not in ( types.SCENARIO, types.SCENARIO_OUTLINE, types.GIVEN, types.WHEN): raise exceptions.FeatureError( "When steps must be the first or follow Given steps", line_number, clean_line, filename) if not self.background and mode == types.THEN and prev_mode not in types.STEP_TYPES: raise exceptions.FeatureError( "Then steps must follow Given or When steps", line_number, clean_line, filename) if mode == types.FEATURE: if prev_mode is None or prev_mode == types.TAG: _, self.name = parse_line(clean_line) self.line_number = line_number self.tags = get_tags(prev_line) elif prev_mode == types.FEATURE: description.append(clean_line) else: raise exceptions.FeatureError( "Multiple features are not allowed in a single feature file", line_number, clean_line, filename) prev_mode = mode # Remove Feature, Given, When, Then, And keyword, parsed_line = parse_line(clean_line) if mode in [types.SCENARIO, types.SCENARIO_OUTLINE]: tags = get_tags(prev_line) self.scenarios[parsed_line] = scenario = Scenario(self, parsed_line, line_number, tags=tags) elif mode == types.BACKGROUND: self.background = Background( feature=self, line_number=line_number, ) elif mode == types.EXAMPLES: mode = types.EXAMPLES_HEADERS (scenario or self).examples.line_number = line_number elif mode == types.EXAMPLES_VERTICAL: mode = types.EXAMPLE_LINE_VERTICAL (scenario or self).examples.line_number = line_number elif mode == types.EXAMPLES_HEADERS: (scenario or self).examples.set_param_names( [l.strip() for l in parsed_line.split("|")[1:-1] if l.strip()]) mode = types.EXAMPLE_LINE elif mode == types.EXAMPLE_LINE: (scenario or self).examples.add_example([l.strip() for l in stripped_line.split("|")[1:-1]]) elif mode == types.EXAMPLE_LINE_VERTICAL: param_line_parts = [l.strip() for l in stripped_line.split("|")[1:-1]] try: (scenario or self).examples.add_example_row(param_line_parts[0], param_line_parts[1:]) except exceptions.ExamplesNotValidError as exc: if scenario: raise exceptions.FeatureError( """Scenario has not valid examples. {0}""".format( exc.args[0]), line_number, clean_line, filename) else: raise exceptions.FeatureError( """Feature has not valid examples. {0}""".format( exc.args[0]), line_number, clean_line, filename) elif mode and mode not in (types.FEATURE, types.TAG): step = Step( name=parsed_line, type=mode, indent=line_indent, line_number=line_number, keyword=keyword, ) if self.background and (mode == types.GIVEN or not strict_gherkin) and not scenario: target = self.background else: target = scenario target.add_step(step) prev_line = clean_line self.description = u"\n".join(description).strip() @classmethod def get_feature(cls, base_path, filename, encoding="utf-8", strict_gherkin=True): """Get a feature by the filename. :param str base_path: Base feature directory. :param str filename: Filename of the feature file. :param str encoding: Feature file encoding. :param bool strict_gherkin: Flag whether it's a strictly gherkin scenario or not (e.g. it will validate correct gherkin language (given-when-then)) :return: `Feature` instance from the parsed feature cache. :note: The features are parsed on the execution of the test and stored in the global variable cache to improve the performance when multiple scenarios are referencing the same file. """ full_name = op.abspath(op.join(base_path, filename)) feature = features.get(full_name) if not feature: feature = Feature(base_path, filename, encoding=encoding, strict_gherkin=strict_gherkin) features[full_name] = feature return feature class Scenario(object): """Scenario.""" def __init__(self, feature, name, line_number, example_converters=None, tags=None): """Scenario constructor. :param pytest_bdd.feature.Feature feature: Feature. :param str name: Scenario name. :param int line_number: Scenario line number. :param dict example_converters: Example table parameter converters. :param set tags: Set of tags. """ self.feature = feature self.name = name self._steps = [] self.examples = Examples() self.line_number = line_number self.example_converters = example_converters self.tags = tags or set() self.failed = False self.test_function = None def add_step(self, step): """Add step to the scenario. :param pytest_bdd.feature.Step step: Step. """ step.scenario = self self._steps.append(step) @property def steps(self): """Get scenario steps including background steps. :return: List of steps. """ result = [] if self.feature.background: result.extend(self.feature.background.steps) result.extend(self._steps) return result @property def params(self): """Get parameter names. :return: Parameter names. :rtype: frozenset """ return frozenset(sum((list(step.params) for step in self.steps), [])) def get_example_params(self): """Get example parameter names.""" return set(self.examples.example_params + self.feature.examples.example_params) def get_params(self, builtin=False): """Get converted example params.""" for examples in [self.feature.examples, self.examples]: yield examples.get_params(self.example_converters, builtin=builtin) def validate(self): """Validate the scenario. :raises ScenarioValidationError: when scenario is not valid """ params = self.params example_params = self.get_example_params() if params and example_params and params != example_params: raise exceptions.ScenarioExamplesNotValidError( """Scenario "{0}" in the feature "{1}" has not valid examples. """ """Set of step parameters {2} should match set of example values {3}.""".format( self.name, self.feature.filename, sorted(params), sorted(example_params), ) ) @six.python_2_unicode_compatible class Step(object): """Step.""" def __init__(self, name, type, indent, line_number, keyword): """Step constructor. :param str name: step name. :param str type: step type. :param int indent: step text indent. :param int line_number: line number. :param str keyword: step keyword. """ self.name = name self.keyword = keyword self.lines = [] self.indent = indent self.type = type self.line_number = line_number self.failed = False self.start = 0 self.stop = 0 self.scenario = None self.background = None def add_line(self, line): """Add line to the multiple step. :param str line: Line of text - the continuation of the step name. """ self.lines.append(line) @property def name(self): """Get step name.""" lines = [self._name] + ([textwrap.dedent("\n".join(self.lines))] if self.lines else []) return "\n".join(lines).strip() @name.setter def name(self, value): """Set step name.""" self._name = value def __str__(self): """Full step name including the type.""" return '{type} "{name}"'.format(type=self.type.capitalize(), name=self.name) @property def params(self): """Get step params.""" return tuple(frozenset(STEP_PARAM_RE.findall(self.name))) class Background(object): """Background.""" def __init__(self, feature, line_number): """Background constructor. :param pytest_bdd.feature.Feature feature: Feature. :param int line_number: Line number. """ self.feature = feature self.line_number = line_number self.steps = [] def add_step(self, step): """Add step to the background.""" step.background = self self.steps.append(step) pytest-bdd-3.2.1/pytest_bdd/generation.py000066400000000000000000000142331352722444400204540ustar00rootroot00000000000000"""pytest-bdd missing test code generation.""" import itertools import os.path from mako.lookup import TemplateLookup import py from .scenario import ( find_argumented_step_fixture_name, make_python_name, ) from .steps import get_step_fixture_name from .feature import get_features from .types import STEP_TYPES template_lookup = TemplateLookup(directories=[os.path.join(os.path.dirname(__file__), "templates")]) def add_options(parser): """Add pytest-bdd options.""" group = parser.getgroup("bdd", "Generation") group._addoption( "--generate-missing", action="store_true", dest="generate_missing", default=False, help="Generate missing bdd test code for given feature files and exit.", ) group._addoption( "--feature", metavar="FILE_OR_DIR", action="append", dest="features", help="Feature file or directory to generate missing code for. Multiple allowed.", ) def cmdline_main(config): """Check config option to show missing code.""" if config.option.generate_missing: return show_missing_code(config) def generate_code(features, scenarios, steps): """Generate test code for the given filenames.""" grouped_steps = group_steps(steps) template = template_lookup.get_template("test.py.mak") return template.render( features=features, scenarios=scenarios, steps=grouped_steps, make_python_name=make_python_name) def show_missing_code(config): """Wrap pytest session to show missing code.""" from _pytest.main import wrap_session return wrap_session(config, _show_missing_code_main) def print_missing_code(scenarios, steps): """Print missing code with TerminalWriter.""" tw = py.io.TerminalWriter() scenario = step = None for scenario in scenarios: tw.line() tw.line( 'Scenario "{scenario.name}" is not bound to any test in the feature "{scenario.feature.name}"' " in the file {scenario.feature.filename}:{scenario.line_number}".format(scenario=scenario), red=True, ) if scenario: tw.sep("-", red=True) for step in steps: tw.line() if step.scenario is not None: tw.line( """Step {step} is not defined in the scenario "{step.scenario.name}" in the feature""" """ "{step.scenario.feature.name}" in the file""" """ {step.scenario.feature.filename}:{step.line_number}""".format(step=step), red=True, ) elif step.background is not None: tw.line( """Step {step} is not defined in the background of the feature""" """ "{step.background.feature.name}" in the file""" """ {step.background.feature.filename}:{step.line_number}""".format(step=step), red=True, ) if step: tw.sep("-", red=True) tw.line("Please place the code above to the test file(s):") tw.line() features = sorted( set(scenario.feature for scenario in scenarios), key=lambda feature: feature.name or feature.filename ) code = generate_code(features, scenarios, steps) tw.write(code) def _find_step_fixturedef(fixturemanager, item, name, type_, encoding="utf-8"): """Find step fixturedef. :param request: PyTest Item object. :param step: `Step`. :return: Step function. """ fixturedefs = fixturemanager.getfixturedefs(get_step_fixture_name(name, type_, encoding), item.nodeid) if not fixturedefs: name = find_argumented_step_fixture_name(name, type_, fixturemanager) if name: return _find_step_fixturedef(fixturemanager, item, name, encoding) else: return fixturedefs def parse_feature_files(paths): """Parse feature files of given paths. :param paths: `list` of paths (file or dirs) :return: `list` of `tuple` in form: (`list` of `Feature` objects, `list` of `Scenario` objects, `list` of `Step` objects). """ features = get_features(paths) scenarios = sorted( itertools.chain.from_iterable(feature.scenarios.values() for feature in features), key=lambda scenario: ( scenario.feature.name or scenario.feature.filename, scenario.name)) steps = sorted( set(itertools.chain.from_iterable(scenario.steps for scenario in scenarios)), key=lambda step: step.name, ) return features, scenarios, steps def group_steps(steps): """Group steps by type.""" steps = sorted(steps, key=lambda step: step.type) seen_steps = set() grouped_steps = [] for step in (itertools.chain.from_iterable( sorted(group, key=lambda step: step.name) for _, group in itertools.groupby(steps, lambda step: step.type))): if step.name not in seen_steps: grouped_steps.append(step) seen_steps.add(step.name) grouped_steps.sort(key=lambda step: STEP_TYPES.index(step.type)) return grouped_steps def _show_missing_code_main(config, session): """Preparing fixture duplicates for output.""" tw = py.io.TerminalWriter() session.perform_collect() fm = session._fixturemanager if config.option.features is None: tw.line("The --feature parameter is required.", red=True) session.exitstatus = 100 return features, scenarios, steps = parse_feature_files(config.option.features) for item in session.items: scenario = getattr(item.obj, "__scenario__", None) if scenario: if scenario in scenarios: scenarios.remove(scenario) for step in scenario.steps: fixturedefs = _find_step_fixturedef(fm, item, step.name, step.type) if fixturedefs: try: steps.remove(step) except ValueError: pass for scenario in scenarios: for step in scenario.steps: if step.background is None: steps.remove(step) grouped_steps = group_steps(steps) print_missing_code(scenarios, grouped_steps) if scenarios or steps: session.exitstatus = 100 pytest-bdd-3.2.1/pytest_bdd/gherkin_terminal_reporter.py000066400000000000000000000120451352722444400235640ustar00rootroot00000000000000# -*- encoding: utf-8 -*- from __future__ import unicode_literals import re from _pytest.terminal import TerminalReporter from .feature import STEP_PARAM_RE def add_options(parser): group = parser.getgroup("terminal reporting", "reporting", after="general") group._addoption( '--gherkin-terminal-reporter', action="store_true", dest="gherkin_terminal_reporter", default=False, help=( "enable gherkin output" ) ) group._addoption( "--gherkin-terminal-reporter-expanded", action="store_true", dest="expand", default=False, help="expand scenario outlines into scenarios and fill in the step names", ) def configure(config): if config.option.gherkin_terminal_reporter: # Get the standard terminal reporter plugin and replace it with our current_reporter = config.pluginmanager.getplugin('terminalreporter') if current_reporter.__class__ != TerminalReporter: raise Exception("gherkin-terminal-reporter is not compatible with any other terminal reporter." "You can use only one terminal reporter." "Currently '{0}' is used." "Please decide to use one by deactivating {0} or gherkin-terminal-reporter." .format(current_reporter.__class__)) gherkin_reporter = GherkinTerminalReporter(config) config.pluginmanager.unregister(current_reporter) config.pluginmanager.register(gherkin_reporter, 'terminalreporter') if config.pluginmanager.getplugin("dsession"): raise Exception("gherkin-terminal-reporter is not compatible with 'xdist' plugin.") class GherkinTerminalReporter(TerminalReporter): def __init__(self, config): TerminalReporter.__init__(self, config) def pytest_runtest_logstart(self, nodeid, location): # Prevent locationline from being printed since we already # show the module_name & in verbose mode the test name. pass def pytest_runtest_logreport(self, report): rep = report res = self.config.hook.pytest_report_teststatus(report=rep, config=self.config) cat, letter, word = res if not letter and not word: # probably passed setup/teardown return if isinstance(word, tuple): word, word_markup = word else: if rep.passed: word_markup = {'green': True} elif rep.failed: word_markup = {'red': True} elif rep.skipped: word_markup = {'yellow': True} feature_markup = {'blue': True} scenario_markup = word_markup if self.verbosity <= 0: return TerminalReporter.pytest_runtest_logreport(self, rep) elif self.verbosity == 1: if hasattr(report, 'scenario'): self.ensure_newline() self._tw.write('Feature: ', **feature_markup) self._tw.write(report.scenario['feature']['name'], **feature_markup) self._tw.write('\n') self._tw.write(' Scenario: ', **scenario_markup) self._tw.write(report.scenario['name'], **scenario_markup) self._tw.write(' ') self._tw.write(word, **word_markup) self._tw.write('\n') else: return TerminalReporter.pytest_runtest_logreport(self, rep) elif self.verbosity > 1: if hasattr(report, 'scenario'): self.ensure_newline() self._tw.write('Feature: ', **feature_markup) self._tw.write(report.scenario['feature']['name'], **feature_markup) self._tw.write('\n') self._tw.write(' Scenario: ', **scenario_markup) self._tw.write(report.scenario['name'], **scenario_markup) self._tw.write('\n') for step in report.scenario['steps']: if self.config.option.expand: step_name = self._format_step_name(step['name'], **report.scenario['example_kwargs']) else: step_name = step['name'] self._tw.write(' {} {}\n'.format(step['keyword'], step_name), **scenario_markup) self._tw.write(' ' + word, **word_markup) self._tw.write('\n\n') else: return TerminalReporter.pytest_runtest_logreport(self, rep) self.stats.setdefault(cat, []).append(rep) def _format_step_name(self, step_name, **example_kwargs): while True: param_match = re.search(STEP_PARAM_RE, step_name) if not param_match: break param_token = param_match.group(0) param_name = param_match.group(1) param_value = example_kwargs[param_name] step_name = step_name.replace(param_token, param_value) return step_name pytest-bdd-3.2.1/pytest_bdd/hooks.py000066400000000000000000000027101352722444400174410ustar00rootroot00000000000000import pytest """Pytest-bdd pytest hooks.""" def pytest_bdd_before_scenario(request, feature, scenario): """Called before scenario is executed.""" def pytest_bdd_after_scenario(request, feature, scenario): """Called after scenario is executed.""" def pytest_bdd_before_step(request, feature, scenario, step, step_func): """Called before step function is set up.""" def pytest_bdd_before_step_call(request, feature, scenario, step, step_func, step_func_args): """Called before step function is executed.""" def pytest_bdd_after_step(request, feature, scenario, step, step_func, step_func_args): """Called after step function is successfully executed.""" def pytest_bdd_step_error(request, feature, scenario, step, step_func, step_func_args, exception): """Called when step function failed to execute.""" def pytest_bdd_step_validation_error(request, feature, scenario, step, step_func, step_func_args, exception): """Called when step failed to validate.""" def pytest_bdd_step_func_lookup_error(request, feature, scenario, step, exception): """Called when step lookup failed.""" @pytest.hookspec(firstresult=True) def pytest_bdd_apply_tag(tag, function): """Apply a tag (from a ``.feature`` file) to the given scenario. The default implementation does the equivalent of ``getattr(pytest.mark, tag)(function)``, but you can override this hook and return ``True`` to do more sophisticated handling of tags. """ pytest-bdd-3.2.1/pytest_bdd/parsers.py000066400000000000000000000056511352722444400200040ustar00rootroot00000000000000"""Step parsers.""" from __future__ import absolute_import import re as base_re import parse as base_parse import six from parse_type import cfparse as base_cfparse from .exceptions import InvalidStepParserError class StepParser(object): """Parser of the individual step.""" def __init__(self, name): self.name = name def parse_arguments(self, name): """Get step arguments from the given step name. :return: `dict` of step arguments """ raise NotImplementedError() def is_matching(self, name): """Match given name with the step name.""" raise NotImplementedError() class re(StepParser): """Regex step parser.""" def __init__(self, name, *args, **kwargs): """Compile regex.""" super(re, self).__init__(name) self.regex = base_re.compile(self.name, *args, **kwargs) def parse_arguments(self, name): """Get step arguments. :return: `dict` of step arguments """ return self.regex.match(name).groupdict() def is_matching(self, name): """Match given name with the step name.""" return bool(self.regex.match(name)) class parse(StepParser): """parse step parser.""" def __init__(self, name, *args, **kwargs): """Compile parse expression.""" super(parse, self).__init__(name) self.parser = base_parse.compile(self.name, *args, **kwargs) def parse_arguments(self, name): """Get step arguments. :return: `dict` of step arguments """ return self.parser.parse(name).named def is_matching(self, name): """Match given name with the step name.""" try: return bool(self.parser.parse(name)) except ValueError: return False class cfparse(parse): """cfparse step parser.""" def __init__(self, name, *args, **kwargs): """Compile parse expression.""" super(parse, self).__init__(name) self.parser = base_cfparse.Parser(self.name, *args, **kwargs) class string(StepParser): """Exact string step parser.""" def parse_arguments(self, name): """No parameters are available for simple string step. :return: `dict` of step arguments """ return {} def is_matching(self, name): """Match given name with the step name.""" return self.name == name def get_parser(step_name): """Get parser by given name. :param step_name: name of the step to parse :return: step parser object :rtype: StepArgumentParser """ if isinstance(step_name, six.string_types): if isinstance(step_name, six.binary_type): # Python 2 compatibility step_name = step_name.decode('utf-8') return string(step_name) elif not hasattr(step_name, 'is_matching') or not hasattr(step_name, 'parse_arguments'): raise InvalidStepParserError(step_name) else: return step_name pytest-bdd-3.2.1/pytest_bdd/plugin.py000066400000000000000000000063701352722444400176220ustar00rootroot00000000000000"""Pytest plugin entry point. Used for any fixtures needed.""" import pytest from . import given, when, then from . import cucumber_json from . import generation from . import reporting from . import gherkin_terminal_reporter from .utils import CONFIG_STACK def pytest_addhooks(pluginmanager): """Register plugin hooks.""" from pytest_bdd import hooks pluginmanager.add_hookspecs(hooks) @given('trace') @when('trace') @then('trace') def trace(): """Enter pytest's pdb trace.""" pytest.set_trace() def pytest_addoption(parser): """Add pytest-bdd options.""" add_bdd_ini(parser) cucumber_json.add_options(parser) generation.add_options(parser) gherkin_terminal_reporter.add_options(parser) def add_bdd_ini(parser): parser.addini('bdd_features_base_dir', 'Base features directory.') parser.addini('bdd_strict_gherkin', 'Parse features to be strict gherkin.', type='bool', default=True) @pytest.mark.trylast def pytest_configure(config): """Configure all subplugins.""" CONFIG_STACK.append(config) cucumber_json.configure(config) gherkin_terminal_reporter.configure(config) def pytest_unconfigure(config): """Unconfigure all subplugins.""" CONFIG_STACK.pop() cucumber_json.unconfigure(config) @pytest.mark.hookwrapper def pytest_runtest_makereport(item, call): outcome = yield reporting.runtest_makereport(item, call, outcome.get_result()) @pytest.mark.tryfirst def pytest_bdd_before_scenario(request, feature, scenario): reporting.before_scenario(request, feature, scenario) @pytest.mark.tryfirst def pytest_bdd_step_error(request, feature, scenario, step, step_func, step_func_args, exception): reporting.step_error(request, feature, scenario, step, step_func, step_func_args, exception) @pytest.mark.tryfirst def pytest_bdd_before_step(request, feature, scenario, step, step_func): reporting.before_step(request, feature, scenario, step, step_func) @pytest.mark.tryfirst def pytest_bdd_after_step(request, feature, scenario, step, step_func, step_func_args): reporting.after_step(request, feature, scenario, step, step_func, step_func_args) def pytest_cmdline_main(config): generation.cmdline_main(config) def pytest_bdd_apply_tag(tag, function): mark = getattr(pytest.mark, tag) return mark(function) @pytest.mark.tryfirst def pytest_collection_modifyitems(session, config, items): """Re-order items using the creation counter as fallback. Pytest has troubles to correctly order the test items for python < 3.6. For this reason, we have to apply some better ordering for pytest_bdd scenario-decorated test functions. This is not needed for python 3.6+, but this logic is safe to apply in that case as well. """ # TODO: Try to only re-sort the items that have __pytest_bdd_counter__, and not the others, # since there may be other hooks that are executed before this and that want to reorder item as well def item_key(item): if isinstance(item, pytest.Function): declaration_order = getattr(item.function, '__pytest_bdd_counter__', 0) else: declaration_order = 0 return (item.reportinfo()[:2], declaration_order) items.sort(key=item_key) pytest-bdd-3.2.1/pytest_bdd/reporting.py000066400000000000000000000134321352722444400203320ustar00rootroot00000000000000"""Reporting functionality. Collection of the scenario excecution statuses, timing and other information that enriches the pytest test reporting. """ import time from .feature import force_unicode from .utils import get_parametrize_markers_args class StepReport(object): """Step excecution report.""" failed = False stopped = None def __init__(self, step): """Step report constructor. :param pytest_bdd.feature.Step step: Step. """ self.step = step self.started = time.time() def serialize(self): """Serialize the step excecution report. :return: Serialized step excecution report. :rtype: dict """ return { "name": self.step.name, "type": self.step.type, "keyword": self.step.keyword, "line_number": self.step.line_number, "failed": self.failed, "duration": self.duration, } def finalize(self, failed): """Stop collecting information and finalize the report. :param bool failed: Wheither the step excecution is failed. """ self.stopped = time.time() self.failed = failed @property def duration(self): """Step excecution duration. :return: Step excecution duration. :rtype: float """ if self.stopped is None: return 0 return self.stopped - self.started class ScenarioReport(object): """Scenario execution report.""" def __init__(self, scenario, node): """Scenario report constructor. :param pytest_bdd.feature.Scenario scenario: Scenario. :param node: pytest test node object """ self.scenario = scenario self.step_reports = [] self.param_index = None parametrize_args = get_parametrize_markers_args(node) if parametrize_args and scenario.examples: param_names = parametrize_args[0] if isinstance(parametrize_args[0], (tuple, list)) else [ parametrize_args[0]] param_values = parametrize_args[1] node_param_values = [node.funcargs[param_name] for param_name in param_names] if node_param_values in param_values: self.param_index = param_values.index(node_param_values) elif tuple(node_param_values) in param_values: self.param_index = param_values.index(tuple(node_param_values)) self.example_kwargs = { example_param: force_unicode(node.funcargs[example_param]) for example_param in scenario.get_example_params() } @property def current_step_report(self): """Get current step report. :return: Last or current step report. :rtype: pytest_bdd.reporting.StepReport """ return self.step_reports[-1] def add_step_report(self, step_report): """Add new step report. :param step_report: New current step report. :type step_report: pytest_bdd.reporting.StepReport """ self.step_reports.append(step_report) def serialize(self): """Serialize scenario excecution report in order to transfer reportin from nodes in the distributed mode. :return: Serialized report. :rtype: dict """ scenario = self.scenario feature = scenario.feature params = sum(scenario.get_params(builtin=True), []) if scenario.examples else None return { "steps": [step_report.serialize() for step_report in self.step_reports], "name": scenario.name, "line_number": scenario.line_number, "tags": sorted(scenario.tags), "feature": { "name": feature.name, "filename": feature.filename, "rel_filename": feature.rel_filename, "line_number": feature.line_number, "description": feature.description, "tags": sorted(feature.tags), }, "examples": [ { "name": scenario.examples.name, "line_number": scenario.examples.line_number, "rows": params, "row_index": self.param_index, } ] if scenario.examples else [], "example_kwargs": self.example_kwargs, } def fail(self): """Stop collecting information and finalize the report as failed.""" self.current_step_report.finalize(failed=True) remaining_steps = self.scenario.steps[len(self.step_reports):] # Fail the rest of the steps and make reports. for step in remaining_steps: report = StepReport(step=step) report.finalize(failed=True) self.add_step_report(report) def runtest_makereport(item, call, rep): """Store item in the report object.""" try: scenario_report = item.__scenario_report__ except AttributeError: pass else: rep.scenario = scenario_report.serialize() rep.item = {"name": item.name} def before_scenario(request, feature, scenario): """Create scenario report for the item.""" request.node.__scenario_report__ = ScenarioReport(scenario=scenario, node=request.node) def step_error(request, feature, scenario, step, step_func, step_func_args, exception): """Finalize the step report as failed.""" request.node.__scenario_report__.fail() def before_step(request, feature, scenario, step, step_func): """Store step start time.""" request.node.__scenario_report__.add_step_report(StepReport(step=step)) def after_step(request, feature, scenario, step, step_func, step_func_args): """Finalize the step report as successful.""" request.node.__scenario_report__.current_step_report.finalize(failed=False) pytest-bdd-3.2.1/pytest_bdd/scenario.py000066400000000000000000000322431352722444400201250ustar00rootroot00000000000000"""Scenario implementation. The pytest will collect the test case and the steps will be executed line by line. Example: test_publish_article = scenario( feature_name="publish_article.feature", scenario_name="Publishing the article", ) """ import collections import inspect import os import re import pytest try: from _pytest import fixtures as pytest_fixtures except ImportError: from _pytest import python as pytest_fixtures from . import exceptions from .feature import ( Feature, force_unicode, get_features, ) from .steps import ( get_caller_module, get_step_fixture_name, inject_fixture, ) from .types import GIVEN from .utils import CONFIG_STACK, get_args PYTHON_REPLACE_REGEX = re.compile(r"\W") ALPHA_REGEX = re.compile(r"^\d+_*") # We have to keep track of the invocation of @scenario() so that we can reorder test item accordingly. # In python 3.6+ this is no longer necessary, as the order is automatically retained. _py2_scenario_creation_counter = 0 def find_argumented_step_fixture_name(name, type_, fixturemanager, request=None): """Find argumented step fixture name.""" # happens to be that _arg2fixturedefs is changed during the iteration so we use a copy for fixturename, fixturedefs in list(fixturemanager._arg2fixturedefs.items()): for fixturedef in fixturedefs: parser = getattr(fixturedef.func, "parser", None) match = parser.is_matching(name) if parser else None if match: converters = getattr(fixturedef.func, "converters", {}) for arg, value in parser.parse_arguments(name).items(): if arg in converters: value = converters[arg](value) if request: inject_fixture(request, arg, value) parser_name = get_step_fixture_name(parser.name, type_) if request: try: request.getfixturevalue(parser_name) except pytest_fixtures.FixtureLookupError: continue return parser_name def _find_step_function(request, step, scenario, encoding): """Match the step defined by the regular expression pattern. :param request: PyTest request object. :param step: Step. :param scenario: Scenario. :return: Function of the step. :rtype: function """ name = step.name try: # Simple case where no parser is used for the step return request.getfixturevalue(get_step_fixture_name(name, step.type, encoding)) except pytest_fixtures.FixtureLookupError: try: # Could not find a fixture with the same name, let's see if there is a parser involved name = find_argumented_step_fixture_name(name, step.type, request._fixturemanager, request) if name: return request.getfixturevalue(name) raise except pytest_fixtures.FixtureLookupError: raise exceptions.StepDefinitionNotFoundError( u"""Step definition is not found: {step}.""" """ Line {step.line_number} in scenario "{scenario.name}" in the feature "{feature.filename}""".format( step=step, scenario=scenario, feature=scenario.feature, ) ) def _execute_step_function(request, scenario, step, step_func): """Execute step function. :param request: PyTest request. :param scenario: Scenario. :param step: Step. :param function step_func: Step function. :param example: Example table. """ kw = dict( request=request, feature=scenario.feature, scenario=scenario, step=step, step_func=step_func, ) request.config.hook.pytest_bdd_before_step(**kw) kw["step_func_args"] = {} try: # Get the step argument values. kwargs = dict((arg, request.getfixturevalue(arg)) for arg in get_args(step_func)) kw["step_func_args"] = kwargs request.config.hook.pytest_bdd_before_step_call(**kw) # Execute the step. step_func(**kwargs) request.config.hook.pytest_bdd_after_step(**kw) except Exception as exception: request.config.hook.pytest_bdd_step_error(exception=exception, **kw) raise def _execute_scenario(feature, scenario, request, encoding): """Execute the scenario. :param feature: Feature. :param scenario: Scenario. :param request: request. :param encoding: Encoding. """ request.config.hook.pytest_bdd_before_scenario( request=request, feature=feature, scenario=scenario, ) try: givens = set() # Execute scenario steps for step in scenario.steps: try: step_func = _find_step_function(request, step, scenario, encoding=encoding) except exceptions.StepDefinitionNotFoundError as exception: request.config.hook.pytest_bdd_step_func_lookup_error( request=request, feature=feature, scenario=scenario, step=step, exception=exception, ) raise try: # Check if the fixture that implements given step has not been yet used by another given step if step.type == GIVEN: if step_func.fixture in givens: raise exceptions.GivenAlreadyUsed( u'Fixture "{0}" that implements this "{1}" given step has been already used.'.format( step_func.fixture, step.name, ) ) givens.add(step_func.fixture) except exceptions.ScenarioValidationError as exception: request.config.hook.pytest_bdd_step_validation_error( request=request, feature=feature, scenario=scenario, step=step, step_func=step_func, exception=exception, step_func_args=dict((arg, request.getfixturevalue(arg)) for arg in get_args(step_func)), ) raise _execute_step_function(request, scenario, step, step_func) finally: request.config.hook.pytest_bdd_after_scenario( request=request, feature=feature, scenario=scenario, ) FakeRequest = collections.namedtuple("FakeRequest", ["module"]) def _get_scenario_decorator(feature, feature_name, scenario, scenario_name, encoding): global _py2_scenario_creation_counter counter = _py2_scenario_creation_counter _py2_scenario_creation_counter += 1 # HACK: Ideally we would use `def decorator(fn)`, but we want to return a custom exception # when the decorator is misused. # Pytest inspect the signature to determine the required fixtures, and in that case it would look # for a fixture called "fn" that doesn't exist (if it exists then it's even worse). # It will error with a "fixture 'fn' not found" message instead. # We can avoid this hack by using a pytest hook and check for misuse instead. def decorator(*args): if not args: raise exceptions.ScenarioIsDecoratorOnly( "scenario function can only be used as a decorator. Refer to the documentation.", ) [fn] = args args = get_args(fn) function_args = list(args) for arg in scenario.get_example_params(): if arg not in function_args: function_args.append(arg) @pytest.mark.usefixtures(*function_args) def scenario_wrapper(request): _execute_scenario(feature, scenario, request, encoding) return fn(*[request.getfixturevalue(arg) for arg in args]) for param_set in scenario.get_params(): if param_set: scenario_wrapper = pytest.mark.parametrize(*param_set)(scenario_wrapper) for tag in scenario.tags.union(feature.tags): config = CONFIG_STACK[-1] config.hook.pytest_bdd_apply_tag(tag=tag, function=scenario_wrapper) scenario_wrapper.__doc__ = u"{feature_name}: {scenario_name}".format( feature_name=feature_name, scenario_name=scenario_name) scenario_wrapper.__scenario__ = scenario scenario_wrapper.__pytest_bdd_counter__ = counter scenario.test_function = scenario_wrapper return scenario_wrapper return decorator def scenario(feature_name, scenario_name, encoding="utf-8", example_converters=None, caller_module=None, features_base_dir=None, strict_gherkin=None): """Scenario decorator. :param str feature_name: Feature file name. Absolute or relative to the configured feature base path. :param str scenario_name: Scenario name. :param str encoding: Feature file encoding. :param dict example_converters: optional `dict` of example converter function, where key is the name of the example parameter, and value is the converter function. """ scenario_name = force_unicode(scenario_name, encoding) caller_module = caller_module or get_caller_module() # Get the feature if features_base_dir is None: features_base_dir = get_features_base_dir(caller_module) if strict_gherkin is None: strict_gherkin = get_strict_gherkin() feature = Feature.get_feature(features_base_dir, feature_name, encoding=encoding, strict_gherkin=strict_gherkin) # Get the scenario try: scenario = feature.scenarios[scenario_name] except KeyError: raise exceptions.ScenarioNotFound( u'Scenario "{scenario_name}" in feature "{feature_name}" in {feature_filename} is not found.'.format( scenario_name=scenario_name, feature_name=feature.name or "[Empty]", feature_filename=feature.filename, ) ) scenario.example_converters = example_converters # Validate the scenario scenario.validate() return _get_scenario_decorator( feature=feature, feature_name=feature_name, scenario=scenario, scenario_name=scenario_name, encoding=encoding, ) def get_features_base_dir(caller_module): default_base_dir = os.path.dirname(caller_module.__file__) return get_from_ini('bdd_features_base_dir', default_base_dir) def get_from_ini(key, default): """Get value from ini config. Return default if value has not been set. Use if the default value is dynamic. Otherwise set default on addini call. """ config = CONFIG_STACK[-1] value = config.getini(key) return value if value != '' else default def get_strict_gherkin(): config = CONFIG_STACK[-1] return config.getini('bdd_strict_gherkin') def make_python_name(string): """Make python attribute name out of a given string.""" string = re.sub(PYTHON_REPLACE_REGEX, "", string.replace(" ", "_")) return re.sub(ALPHA_REGEX, "", string).lower() def get_python_name_generator(name): """Generate a sequence of suitable python names out of given arbitrary string name.""" python_name = make_python_name(name) suffix = '' index = 0 def get_name(): return 'test_{0}{1}'.format(python_name, suffix) while True: yield get_name() index += 1 suffix = '_{0}'.format(index) def scenarios(*feature_paths, **kwargs): """Parse features from the paths and put all found scenarios in the caller module. :param *feature_paths: feature file paths to use for scenarios """ frame = inspect.stack()[1] module = inspect.getmodule(frame[0]) features_base_dir = kwargs.get('features_base_dir') if features_base_dir is None: features_base_dir = get_features_base_dir(module) strict_gherkin = kwargs.get('strict_gherkin') if strict_gherkin is None: strict_gherkin = get_strict_gherkin() abs_feature_paths = [] for path in feature_paths: if not os.path.isabs(path): path = os.path.abspath(os.path.join(features_base_dir, path)) abs_feature_paths.append(path) found = False module_scenarios = frozenset( (attr.__scenario__.feature.filename, attr.__scenario__.name) for name, attr in module.__dict__.items() if hasattr(attr, '__scenario__')) for feature in get_features(abs_feature_paths, strict_gherkin=strict_gherkin): for scenario_name, scenario_object in feature.scenarios.items(): # skip already bound scenarios if (scenario_object.feature.filename, scenario_name) not in module_scenarios: @scenario(feature.filename, scenario_name, **kwargs) def _scenario(): pass # pragma: no cover for test_name in get_python_name_generator(scenario_name): if test_name not in module.__dict__: # found an unique test name module.__dict__[test_name] = _scenario break found = True if not found: raise exceptions.NoScenariosFound(abs_feature_paths) pytest-bdd-3.2.1/pytest_bdd/scripts.py000066400000000000000000000050161352722444400200070ustar00rootroot00000000000000"""pytest-bdd scripts.""" import argparse import os.path import re import glob2 from .generation import ( generate_code, parse_feature_files, ) MIGRATE_REGEX = re.compile(r"\s?(\w+)\s\=\sscenario\((.+)\)", flags=re.MULTILINE) def migrate_tests(args): """Migrate outdated tests to the most recent form.""" path = args.path for file_path in glob2.iglob(os.path.join(os.path.abspath(path), "**", "*.py")): migrate_tests_in_file(file_path) def migrate_tests_in_file(file_path): """Migrate all bdd-based tests in the given test file.""" try: with open(file_path, "r+") as fd: content = fd.read() new_content = MIGRATE_REGEX.sub(r"\n@scenario(\2)\ndef \1():\n pass\n", content) if new_content != content: # the regex above potentially causes the end of the file to # have an extra newline new_content = new_content.rstrip('\n') + '\n' fd.seek(0) fd.write(new_content) print("migrated: {0}".format(file_path)) else: print("skipped: {0}".format(file_path)) except IOError: pass def check_existense(file_name): """Check file or directory name for existence.""" if not os.path.exists(file_name): raise argparse.ArgumentTypeError("{0} is an invalid file or directory name".format(file_name)) return file_name def print_generated_code(args): """Print generated test code for the given filenames.""" features, scenarios, steps = parse_feature_files(args.files) code = generate_code(features, scenarios, steps) print(code) def main(): """Main entry point.""" parser = argparse.ArgumentParser(prog="pytest-bdd") subparsers = parser.add_subparsers(help="sub-command help", dest='command') subparsers.required = True parser_generate = subparsers.add_parser("generate", help="generate help") parser_generate.add_argument( "files", metavar="FEATURE_FILE", type=check_existense, nargs="+", help="Feature files to generate test code with", ) parser_generate.set_defaults(func=print_generated_code) parser_migrate = subparsers.add_parser("migrate", help="migrate help") parser_migrate.add_argument( "path", metavar="PATH", help="Migrate outdated tests to the most recent form", ) parser_migrate.set_defaults(func=migrate_tests) args = parser.parse_args() if hasattr(args, 'func'): args.func(args) pytest-bdd-3.2.1/pytest_bdd/steps.py000066400000000000000000000167611352722444400174670ustar00rootroot00000000000000"""Step decorators. Example: @given("I have an article") def article(author): return create_test_article(author=author) @when("I go to the article page") def go_to_the_article_page(browser, article): browser.visit(urljoin(browser.url, "/articles/{0}/".format(article.id))) @then("I should not see the error message") def no_error_message(browser): with pytest.raises(ElementDoesNotExist): browser.find_by_css(".message.error").first Multiple names for the steps: @given("I have an article") @given("there is an article") def article(author): return create_test_article(author=author) Reusing existing fixtures for a different step name: given("I have a beautiful article", fixture="article") """ from __future__ import absolute_import import inspect import sys import pytest try: from _pytest import fixtures as pytest_fixtures except ImportError: from _pytest import python as pytest_fixtures from .feature import parse_line, force_encode from .types import GIVEN, WHEN, THEN from .exceptions import ( StepError, ) from .parsers import get_parser from .utils import get_args def get_step_fixture_name(name, type_, encoding=None): """Get step fixture name. :param name: unicode string :param type: step type :param encoding: encoding :return: step fixture name :rtype: string """ return "pytestbdd_{type}_{name}".format( type=type_, name=force_encode(name, **(dict(encoding=encoding) if encoding else {}))) def given(name, fixture=None, converters=None, scope='function', target_fixture=None): """Given step decorator. :param name: Given step name. :param fixture: Optional name of the fixture to reuse. :param converters: Optional `dict` of the argument or parameter converters in form {: }. :scope: Optional fixture scope :param target_fixture: Target fixture name to replace by steps definition function :raises: StepError in case of wrong configuration. :note: Can't be used as a decorator when the fixture is specified. """ if fixture is not None: module = get_caller_module() def step_func(request): return request.getfixturevalue(fixture) step_func.step_type = GIVEN step_func.converters = converters step_func.__name__ = force_encode(name, 'ascii') step_func.fixture = fixture func = pytest.fixture(scope=scope)(lambda: step_func) func.__doc__ = 'Alias for the "{0}" fixture.'.format(fixture) _, name = parse_line(name) setattr(module, get_step_fixture_name(name, GIVEN), func) return _not_a_fixture_decorator return _step_decorator(GIVEN, name, converters=converters, scope=scope, target_fixture=target_fixture) def when(name, converters=None): """When step decorator. :param name: Step name. :param converters: Optional `dict` of the argument or parameter converters in form {: }. :param parser: name of the step parser to use :param parser_args: optional `dict` of arguments to pass to step parser :raises: StepError in case of wrong configuration. """ return _step_decorator(WHEN, name, converters=converters) def then(name, converters=None): """Then step decorator. :param name: Step name. :param converters: Optional `dict` of the argument or parameter converters in form {: }. :param parser: name of the step parser to use :param parser_args: optional `dict` of arguments to pass to step parser :raises: StepError in case of wrong configuration. """ return _step_decorator(THEN, name, converters=converters) def _not_a_fixture_decorator(func): """Function that prevents the decoration. :param func: Function that is going to be decorated. :raises: `StepError` if was used as a decorator. """ raise StepError('Cannot be used as a decorator when the fixture is specified') def _step_decorator(step_type, step_name, converters=None, scope='function', target_fixture=None): """Step decorator for the type and the name. :param str step_type: Step type (GIVEN, WHEN or THEN). :param str step_name: Step name as in the feature file. :param dict converters: Optional step arguments converters mapping :param str scope: Optional step definition fixture scope :param target_fixture: Optional fixture name to replace by step definition :return: Decorator function for the step. :raise: StepError if the function doesn't take group names as parameters. :note: If the step type is GIVEN it will automatically apply the pytest fixture decorator to the step function. """ def decorator(func): step_func = func parser_instance = get_parser(step_name) parsed_step_name = parser_instance.name if step_type == GIVEN: if not hasattr(func, "_pytestfixturefunction"): # Avoid multiple wrapping of a fixture func = pytest.fixture(scope=scope)(func) def step_func(request): result = request.getfixturevalue(func.__name__) if target_fixture: inject_fixture(request, target_fixture, result) return result step_func.__doc__ = func.__doc__ step_func.fixture = func.__name__ step_func.__name__ = force_encode(parsed_step_name) def lazy_step_func(): return step_func step_func.step_type = step_type lazy_step_func.step_type = step_type # Preserve the docstring lazy_step_func.__doc__ = func.__doc__ step_func.parser = lazy_step_func.parser = parser_instance if converters: step_func.converters = lazy_step_func.converters = converters lazy_step_func = pytest.fixture(scope=scope)(lazy_step_func) setattr(get_caller_module(), get_step_fixture_name(parsed_step_name, step_type), lazy_step_func) return func return decorator def get_caller_module(depth=2): """Return the module of the caller.""" frame = sys._getframe(depth) module = inspect.getmodule(frame) if module is None: return get_caller_module(depth=depth) return module def inject_fixture(request, arg, value): """Inject fixture into pytest fixture request. :param request: pytest fixture request :param arg: argument name :param value: argument value """ fd_kwargs = { 'fixturemanager': request._fixturemanager, 'baseid': None, 'argname': arg, 'func': lambda: value, 'scope': "function", 'params': None, } if 'yieldctx' in get_args(pytest_fixtures.FixtureDef.__init__): fd_kwargs['yieldctx'] = False fd = pytest_fixtures.FixtureDef(**fd_kwargs) fd.cached_result = (value, 0, None) old_fd = request._fixture_defs.get(arg) add_fixturename = arg not in request.fixturenames def fin(): request._fixturemanager._arg2fixturedefs[arg].remove(fd) request._fixture_defs[arg] = old_fd if add_fixturename: request._pyfuncitem._fixtureinfo.names_closure.remove(arg) request.addfinalizer(fin) # inject fixture definition request._fixturemanager._arg2fixturedefs.setdefault(arg, []).insert(0, fd) # inject fixture value in request cache request._fixture_defs[arg] = fd if add_fixturename: request._pyfuncitem._fixtureinfo.names_closure.append(arg) pytest-bdd-3.2.1/pytest_bdd/templates/000077500000000000000000000000001352722444400177425ustar00rootroot00000000000000pytest-bdd-3.2.1/pytest_bdd/templates/test.py.mak000066400000000000000000000011211352722444400220350ustar00rootroot00000000000000% if features: # coding=utf-8 """${ features[0].name or features[0].rel_filename } feature tests.""" from pytest_bdd import ( given, scenario, then, when, ) % endif % for scenario in sorted(scenarios, key=lambda scenario: scenario.name): @scenario('${scenario.feature.rel_filename}', '${scenario.name}') def test_${ make_python_name(scenario.name)}(): """${scenario.name}.""" % endfor % for step in steps: @${step.type}('${step.name}') def ${ make_python_name(step.name)}(): """${step.name}.""" raise NotImplementedError % if not loop.last: % endif % endfor pytest-bdd-3.2.1/pytest_bdd/types.py000066400000000000000000000006251352722444400174650ustar00rootroot00000000000000"""Common type definitions.""" FEATURE = "feature" SCENARIO_OUTLINE = "scenario outline" EXAMPLES = "examples" EXAMPLES_VERTICAL = "examples vertical" EXAMPLES_HEADERS = "example headers" EXAMPLE_LINE = "example line" EXAMPLE_LINE_VERTICAL = "example line vertical" SCENARIO = "scenario" BACKGROUND = "background" GIVEN = "given" WHEN = "when" THEN = "then" TAG = "tag" STEP_TYPES = (GIVEN, WHEN, THEN) pytest-bdd-3.2.1/pytest_bdd/utils.py000066400000000000000000000027131352722444400174610ustar00rootroot00000000000000"""Various utility functions.""" import inspect CONFIG_STACK = [] def get_args(func): """Get a list of argument names for a function. This is a wrapper around inspect.getargspec/inspect.signature because getargspec got deprecated in Python 3.5 and signature isn't available on Python 2. :param func: The function to inspect. :return: A list of argument names. :rtype: list """ if hasattr(inspect, 'signature'): params = inspect.signature(func).parameters.values() return [param.name for param in params if param.kind == param.POSITIONAL_OR_KEYWORD] else: return inspect.getargspec(func).args def get_parametrize_markers_args(node): """In pytest 3.6 new API to access markers has been introduced and it deprecated MarkInfo objects. This function uses that API if it is available otherwise it uses MarkInfo objects. """ mark_name = 'parametrize' try: return get_markers_args_using_iter_markers(node, mark_name) except AttributeError: return get_markers_args_using_get_marker(node, mark_name) def get_markers_args_using_iter_markers(node, mark_name): """Recommended on pytest>=3.6""" args = [] for mark in node.iter_markers(mark_name): args += mark.args return tuple(args) def get_markers_args_using_get_marker(node, mark_name): """Deprecated on pytest>=3.6""" return getattr(node.get_marker(mark_name), 'args', ()) pytest-bdd-3.2.1/requirements-testing.txt000066400000000000000000000000551352722444400205420ustar00rootroot00000000000000mock coverage<4.0a1 pytest-cache pycodestyle pytest-bdd-3.2.1/setup.cfg000066400000000000000000000000731352722444400154240ustar00rootroot00000000000000[wheel] universal = 1 [pycodestyle] max-line-length = 120 pytest-bdd-3.2.1/setup.py000077500000000000000000000055561352722444400153330ustar00rootroot00000000000000#!/usr/bin/env python """pytest-bdd package config.""" import codecs import os import re import sys from setuptools import setup from setuptools.command.test import test as TestCommand class ToxTestCommand(TestCommand): """Test command which runs tox under the hood.""" user_options = [('tox-args=', 'a', "Arguments to pass to tox")] def initialize_options(self): """Initialize options and set their defaults.""" TestCommand.initialize_options(self) self.tox_args = '--recreate' def finalize_options(self): """Add options to the test runner (tox).""" TestCommand.finalize_options(self) self.test_args = [] self.test_suite = True def run_tests(self): """Invoke the test runner (tox).""" # import here, cause outside the eggs aren't loaded import tox import shlex errno = tox.cmdline(args=shlex.split(self.tox_args)) sys.exit(errno) dirname = os.path.dirname(__file__) long_description = ( codecs.open(os.path.join(dirname, "README.rst"), encoding="utf-8").read() + "\n" + codecs.open(os.path.join(dirname, "AUTHORS.rst"), encoding="utf-8").read() + "\n" + codecs.open(os.path.join(dirname, "CHANGES.rst"), encoding="utf-8").read() ) with codecs.open(os.path.join(dirname, 'pytest_bdd', '__init__.py'), encoding='utf-8') as fd: VERSION = re.compile(r".*__version__ = '(.*?)'", re.S).match(fd.read()).group(1) setup( name="pytest-bdd", description="BDD for pytest", long_description=long_description, author="Oleg Pidsadnyi, Anatoly Bubenkov and others", license="MIT license", author_email="oleg.pidsadnyi@gmail.com", url="https://github.com/pytest-dev/pytest-bdd", version=VERSION, classifiers=[ "Development Status :: 6 - Mature", "Intended Audience :: Developers", "License :: OSI Approved :: MIT License", "Operating System :: POSIX", "Operating System :: Microsoft :: Windows", "Operating System :: MacOS :: MacOS X", "Topic :: Software Development :: Testing", "Topic :: Software Development :: Libraries", "Topic :: Utilities", "Programming Language :: Python :: 2", "Programming Language :: Python :: 3" ] + [("Programming Language :: Python :: %s" % x) for x in "2.7 3.4 3.5 3.6 3.7".split()], cmdclass={"test": ToxTestCommand}, install_requires=[ "glob2", "Mako", "parse", "parse_type", "py", "pytest>=3.0.0", "six>=1.9.0", ], # the following makes a plugin available to py.test entry_points={ "pytest11": [ "pytest-bdd = pytest_bdd.plugin", ], "console_scripts": [ "pytest-bdd = pytest_bdd.scripts:main", ] }, tests_require=["tox"], packages=["pytest_bdd"], include_package_data=True, ) pytest-bdd-3.2.1/tests/000077500000000000000000000000001352722444400147455ustar00rootroot00000000000000pytest-bdd-3.2.1/tests/__init__.py000066400000000000000000000000001352722444400170440ustar00rootroot00000000000000pytest-bdd-3.2.1/tests/args/000077500000000000000000000000001352722444400157015ustar00rootroot00000000000000pytest-bdd-3.2.1/tests/args/__init__.py000066400000000000000000000000001352722444400200000ustar00rootroot00000000000000pytest-bdd-3.2.1/tests/args/args_steps.feature000066400000000000000000000004641352722444400214340ustar00rootroot00000000000000Scenario: Every step takes a parameter with the same name Given I have 1 Euro When I pay 2 Euro And I pay 1 Euro Then I should have 0 Euro And I should have 999999 Euro # In my dream... Scenario: Using the same given fixture raises an error Given I have 1 Euro And I have 2 Euro pytest-bdd-3.2.1/tests/args/cfparse/000077500000000000000000000000001352722444400173245ustar00rootroot00000000000000pytest-bdd-3.2.1/tests/args/cfparse/__init__.py000066400000000000000000000000001352722444400214230ustar00rootroot00000000000000pytest-bdd-3.2.1/tests/args/cfparse/test_args.py000066400000000000000000000032471352722444400216770ustar00rootroot00000000000000"""Step arguments tests.""" import functools from pytest_bdd import ( given, parsers, scenario, then, when, ) import pytest from pytest_bdd import exceptions scenario_when = functools.partial(scenario, '../when_arguments.feature') scenario_args = functools.partial(scenario, '../args_steps.feature') @scenario_args('Every step takes a parameter with the same name') def test_steps(): pass @scenario_when('Argument in when, step 1') def test_argument_in_when_step_1(): pass @scenario_when('Argument in when, step 2') def test_argument_in_when_step_2(): pass def test_multiple_given(request): """Using the same given fixture raises an error.""" @scenario_args('Using the same given fixture raises an error') def test(): pass with pytest.raises(exceptions.GivenAlreadyUsed): test(request) @given(parsers.cfparse('I have {euro:d} Euro')) def i_have(euro, values): assert euro == values.pop(0) @when(parsers.cfparse('I pay {euro:d} Euro')) def i_pay(euro, values, request): assert euro == values.pop(0) @then(parsers.cfparse('I should have {euro:d} Euro')) def i_should_have(euro, values): assert euro == values.pop(0) @given(parsers.cfparse('I have an argument {arg:Number}', extra_types=dict(Number=int))) def argument(arg): """I have an argument.""" return dict(arg=arg) @when(parsers.cfparse('I get argument {arg:d}')) def get_argument(argument, arg): """Getting argument.""" argument['arg'] = arg @then(parsers.cfparse('My argument should be {arg:d}')) def assert_that_my_argument_is_arg(argument, arg): """Assert that arg from when equals arg.""" assert argument['arg'] == arg pytest-bdd-3.2.1/tests/args/conftest.py000066400000000000000000000002511352722444400200760ustar00rootroot00000000000000"""Step arguments test configuration.""" import pytest @pytest.fixture def values(): """List to ensure that steps are executed.""" return [1, 2, 1, 0, 999999] pytest-bdd-3.2.1/tests/args/parse/000077500000000000000000000000001352722444400170135ustar00rootroot00000000000000pytest-bdd-3.2.1/tests/args/parse/__init__.py000066400000000000000000000000001352722444400211120ustar00rootroot00000000000000pytest-bdd-3.2.1/tests/args/parse/test_args.py000066400000000000000000000032331352722444400213610ustar00rootroot00000000000000"""Step arguments tests.""" import functools from pytest_bdd import ( given, parsers, scenario, then, when, ) import pytest from pytest_bdd import exceptions scenario_when = functools.partial(scenario, '../when_arguments.feature') scenario_args = functools.partial(scenario, '../args_steps.feature') @scenario_args('Every step takes a parameter with the same name') def test_steps(): pass @scenario_when('Argument in when, step 1') def test_argument_in_when_step_1(): pass @scenario_when('Argument in when, step 2') def test_argument_in_when_step_2(): pass def test_multiple_given(request): """Using the same given fixture raises an error.""" @scenario_args('Using the same given fixture raises an error') def test(): pass with pytest.raises(exceptions.GivenAlreadyUsed): test(request) @given(parsers.parse('I have {euro:d} Euro')) def i_have(euro, values): assert euro == values.pop(0) @when(parsers.parse('I pay {euro:d} Euro')) def i_pay(euro, values, request): assert euro == values.pop(0) @then(parsers.parse('I should have {euro:d} Euro')) def i_should_have(euro, values): assert euro == values.pop(0) @given(parsers.parse('I have an argument {arg:Number}', extra_types=dict(Number=int))) def argument(arg): """I have an argument.""" return dict(arg=arg) @when(parsers.parse('I get argument {arg:d}')) def get_argument(argument, arg): """Getting argument.""" argument['arg'] = arg @then(parsers.parse('My argument should be {arg:d}')) def assert_that_my_argument_is_arg(argument, arg): """Assert that arg from when equals arg.""" assert argument['arg'] == arg pytest-bdd-3.2.1/tests/args/regex/000077500000000000000000000000001352722444400170135ustar00rootroot00000000000000pytest-bdd-3.2.1/tests/args/regex/__init__.py000066400000000000000000000000001352722444400211120ustar00rootroot00000000000000pytest-bdd-3.2.1/tests/args/regex/test_args.py000066400000000000000000000033451352722444400213650ustar00rootroot00000000000000"""Step arguments tests.""" import functools import re from pytest_bdd import ( given, parsers, scenario, then, when, ) import pytest from pytest_bdd import exceptions scenario_when = functools.partial(scenario, '../when_arguments.feature') scenario_args = functools.partial(scenario, '../args_steps.feature') @scenario_args('Every step takes a parameter with the same name') def test_steps(): pass @scenario_when('Argument in when, step 1') def test_argument_in_when_step_1(): pass @scenario_when('Argument in when, step 2') def test_argument_in_when_step_2(): pass def test_multiple_given(request): """Using the same given fixture raises an error.""" @scenario_args('Using the same given fixture raises an error') def test(): pass with pytest.raises(exceptions.GivenAlreadyUsed): test(request) @given(parsers.re(r'I have (?P\d+) Euro'), converters=dict(euro=int)) def i_have(euro, values): assert euro == values.pop(0) @when(parsers.re(r'I pay (?P\d+) Euro'), converters=dict(euro=int)) def i_pay(euro, values, request): assert euro == values.pop(0) @then(parsers.re(r'I should have (?P\d+) Euro'), converters=dict(euro=int)) def i_should_have(euro, values): assert euro == values.pop(0) @given(parsers.re(r'I have an argument (?P\d+)')) def argument(arg): """I have an argument.""" return dict(arg=arg) @when(parsers.re(r'I get argument (?P\d+)')) def get_argument(argument, arg): """Getting argument.""" argument['arg'] = arg @then(parsers.re(r'My argument should be (?P\d+)')) def assert_that_my_argument_is_arg(argument, arg): """Assert that arg from when equals arg.""" assert argument['arg'] == arg pytest-bdd-3.2.1/tests/args/subfolder/000077500000000000000000000000001352722444400176665ustar00rootroot00000000000000pytest-bdd-3.2.1/tests/args/subfolder/__init__.py000066400000000000000000000000001352722444400217650ustar00rootroot00000000000000pytest-bdd-3.2.1/tests/args/subfolder/args.feature000066400000000000000000000004651352722444400222040ustar00rootroot00000000000000Scenario: Executed with steps matching step definitons with arguments Given I have a foo fixture with value "foo" And there is a list When I append 1 to the list And I append 2 to the list And I append 3 to the list Then foo should have value "foo" And the list should be [1, 2, 3]pytest-bdd-3.2.1/tests/args/subfolder/test_args.py000066400000000000000000000012721352722444400222350ustar00rootroot00000000000000"""Test step arguments with complex folder structure.""" from pytest_bdd import ( given, parsers, scenario, then, when, ) @scenario( 'args.feature', 'Executed with steps matching step definitons with arguments', ) def test_steps(): pass @given('I have a foo fixture with value "foo"') def foo(): return 'foo' @given('there is a list') def results(): return [] @when(parsers.parse('I append {n:d} to the list')) def append_to_list(results, n): results.append(n) @then('foo should have value "foo"') def foo_is_foo(foo): assert foo == 'foo' @then('the list should be [1, 2, 3]') def check_results(results): assert results == [1, 2, 3] pytest-bdd-3.2.1/tests/args/test_arg_fixture_mix.py000066400000000000000000000041261352722444400225110ustar00rootroot00000000000000import textwrap def test_arg_fixture_mix(testdir): subdir = testdir.mkpydir("arg_fixture_mix") subdir.join("test_a.py").write(textwrap.dedent(""" import re import pytest from pytest_bdd import scenario, given, then, parsers @pytest.fixture def foo(): return "fine" @scenario( 'arg_and_fixture_mix.feature', 'Use the step argument with the same name as fixture of another test', ) def test_args(): pass @given(parsers.parse('foo is "{foo}"')) def foo1(foo): pass @then(parsers.parse('foo should be "{foo_value}"')) def foo_should_be(foo, foo_value): assert foo == foo_value @scenario( 'arg_and_fixture_mix.feature', 'Everything is fine', ) def test_bar(): pass @given('it is all fine') def fine(): return "fine" @then('foo should be fine') def foo_should_be_fine(foo): assert foo == "fine" """)) subdir.join("test_b.py").write(textwrap.dedent(""" import re import pytest from pytest_bdd import scenario, given, then @scenario( 'arg_and_fixture_mix.feature', 'Everything is fine', ) def test_args(): pass @pytest.fixture def foo(): return "fine" @given('it is all fine') def fine(): return "fine" @then('foo should be fine') def foo_should_be(foo): assert foo == "fine" def test_bar(foo): assert foo == 'fine' """)) subdir.join("arg_and_fixture_mix.feature").write(""" Scenario: Use the step argument with the same name as fixture of another test Given foo is "Hello" Then foo should be "Hello" Scenario: Everything is fine Given it is all fine Then foo should be fine """) result = testdir.runpytest("-k arg_fixture_mix") assert result.ret == 0 pytest-bdd-3.2.1/tests/args/when_arguments.feature000066400000000000000000000004021352722444400223000ustar00rootroot00000000000000Scenario: Argument in when, step 1 Given I have an argument 1 When I get argument 5 Then My argument should be 5 Scenario: Argument in when, step 2 Given I have an argument 1 When I get argument 10 Then My argument should be 10 pytest-bdd-3.2.1/tests/conftest.py000066400000000000000000000003721352722444400171460ustar00rootroot00000000000000"""Configuration for pytest runner.""" from pytest_bdd import given, when pytest_plugins = "pytester" @given("I have a root fixture") def root(): return "root" @when("I use a when step from the parent conftest") def global_when(): pass pytest-bdd-3.2.1/tests/feature/000077500000000000000000000000001352722444400164005ustar00rootroot00000000000000pytest-bdd-3.2.1/tests/feature/__init__.py000066400000000000000000000000001352722444400204770ustar00rootroot00000000000000pytest-bdd-3.2.1/tests/feature/alias.feature000066400000000000000000000004361352722444400210510ustar00rootroot00000000000000Scenario: Multiple given alias is not evaluated multiple times Given I have an empty list # Alias of the "I have foo (which is 1) in my list" And I have bar (alias of foo) in my list When I do crash (which is 2) And I do boom (alias of crash) Then my list should be [1, 2, 2] pytest-bdd-3.2.1/tests/feature/background.feature000066400000000000000000000006751352722444400221040ustar00rootroot00000000000000Feature: Background support Background: Given foo has a value "bar" And a background step with multiple lines: one two Scenario: Basic usage Then foo should have value "bar" Scenario: Background steps are executed first Given foo has no value "bar" And foo has a value "dummy" Then foo should have value "dummy" And foo should not have value "bar" pytest-bdd-3.2.1/tests/feature/comments.feature000066400000000000000000000003231352722444400216000ustar00rootroot00000000000000Scenario: Comments # Comment Given I have a bar Scenario: Strings that are not comments Given comments should be at the start of words Then this is not a#comment And this is not "#acomment" pytest-bdd-3.2.1/tests/feature/conftest.py000066400000000000000000000002541352722444400206000ustar00rootroot00000000000000from 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' pytest-bdd-3.2.1/tests/feature/description.feature000066400000000000000000000003021352722444400222730ustar00rootroot00000000000000Feature: Description In order to achieve something I want something Because it will be cool Some description goes here. Scenario: Description Given I have a bar pytest-bdd-3.2.1/tests/feature/gherkin_terminal_reporter.feature000066400000000000000000000043241352722444400252240ustar00rootroot00000000000000Feature: Gherkin terminal reporter Scenario: Should default output be the same as regular terminal reporter Given there is gherkin scenario implemented When I run tests Then output must be formatted the same way as regular one Scenario: Should verbose mode enable displaying feature and scenario names rather than test names in a single line Given there is gherkin scenario implemented When I run tests with verbose mode Then output should contain single line feature description And output should contain single line scenario description Scenario: Should verbose mode preserve displaying of regular tests as usual Given there is non-gherkin scenario implemented When I run tests with verbose mode Then output must be formatted the same way as regular one Scenario: Should double verbose mode enable displaying of full gherkin scenario description Given there is gherkin scenario implemented When I run tests with very verbose mode Then output must contain full gherkin scenario description Scenario: Should error message be displayed when no scenario is found Given there is gherkin scenario without implementation When I run tests with any verbosity mode Then output contains error about missing scenario implementation Scenario: Should error message be displayed when no step is found Given there is gherkin scenario partially implemented When I run tests with any verbosity mode Then output contains error about missing step implementation Scenario: Should error message be displayed when error occurs during test execution Given there is gherkin scenario with broken implementation When I run tests with any verbosity mode Then output contains error about missing scenario implementation Scenario: Should local variables be displayed when --showlocals option is used Given there is gherkin scenario with broken implementation When I run tests with --showlocals Then error traceback contains local variable descriptions Scenario: Should step parameters be replaced by their values Given there is gherkin scenario outline implemented When I run tests with step expanded mode Then output must contain parameters values pytest-bdd-3.2.1/tests/feature/given_after_then.feature000066400000000000000000000001431352722444400232620ustar00rootroot00000000000000Scenario: Given after Then Given something When something else Then nevermind Given won't work pytest-bdd-3.2.1/tests/feature/given_after_when.feature000066400000000000000000000001231352722444400232630ustar00rootroot00000000000000Scenario: Given after When Given something When something else Given won't work pytest-bdd-3.2.1/tests/feature/multiline.feature000066400000000000000000000002701352722444400217560ustar00rootroot00000000000000Scenario: Multiline step using sub indentation wrong indent Given I have a step with: Some Extra Lines Then the text should be parsed with correct indentation pytest-bdd-3.2.1/tests/feature/not_found.feature000066400000000000000000000000711352722444400217460ustar00rootroot00000000000000Scenario: Some scenario Given 1 When 2 Then 3pytest-bdd-3.2.1/tests/feature/outline.feature000066400000000000000000000027521352722444400214420ustar00rootroot00000000000000Scenario Outline: Outlined given, when, thens Given there are cucumbers When I eat cucumbers Then I should have cucumbers Examples: | start | eat | left | | 12 | 5 | 7 | | 5 | 4 | 1 | Scenario Outline: Outlined with wrong examples Given there are cucumbers When I eat cucumbers Then I should have cucumbers Examples: | start | eat | left | unknown_param | | 12 | 5 | 7 | value | Scenario Outline: Outlined with some examples failing Given there are cucumbers When I eat cucumbers Then I should have cucumbers Examples: | start | eat | left | | 0 | 5 | 5 | | 12 | 5 | 7 | Scenario Outline: Outlined with vertical example table Given there are cucumbers When I eat cucumbers Then I should have cucumbers Examples: Vertical | start | 12 | 2 | | eat | 5 | 1 | | left | 7 | 1 | Scenario Outline: Outlined with empty example values vertical Given there are cucumbers When I eat cucumbers Then I should have cucumbers Examples: Vertical | start | # | | eat | | | left | | Scenario Outline: Outlined with empty example values Given there are cucumbers When I eat cucumbers Then I should have cucumbers Examples: | start | eat | left | | # | | | pytest-bdd-3.2.1/tests/feature/outline_feature.feature000066400000000000000000000005511352722444400231500ustar00rootroot00000000000000Feature: Outline Examples: | start | eat | left | | 12 | 5 | 7 | | 5 | 4 | 1 | Scenario Outline: Outlined given, when, thens Given there are When I eat Then I should have Examples: | fruits | | oranges | | apples | pytest-bdd-3.2.1/tests/feature/parametrized.feature000066400000000000000000000002271352722444400224450ustar00rootroot00000000000000Scenario: Parametrized given, when, thens Given there are cucumbers When I eat cucumbers Then I should have cucumbers pytest-bdd-3.2.1/tests/feature/reuse.feature000066400000000000000000000004271352722444400211030ustar00rootroot00000000000000Scenario: Given and when using the same fixture should not evaluate it twice Given I have an empty list # Alias of the "I have a fixture (appends 1 to a list)" And I have a fixture (appends 1 to a list) in reuse syntax When I use this fixture Then my list should be [1] pytest-bdd-3.2.1/tests/feature/same_function_name.feature000066400000000000000000000001631352722444400236070ustar00rootroot00000000000000Feature: Function name same as step name Scenario: When function name same as step name When something pytest-bdd-3.2.1/tests/feature/steps.feature000066400000000000000000000014731352722444400211200ustar00rootroot00000000000000Feature: Steps are executed one by one Steps are executed one by one. Given and When sections are not mandatory in some cases. Scenario: Executed step by step Given I have a foo fixture with value "foo" And there is a list When I append 1 to the list And I append 2 to the list And I append 3 to the list Then foo should have value "foo" But the list should be [1, 2, 3] Scenario: When step can be the first When I do nothing Then I make no mistakes Scenario: Then step can follow Given step Given I have a foo fixture with value "foo" Then foo should have value "foo" Scenario: All steps are declared in the conftest Given I have a bar Then bar should have value "bar" Scenario: Using the same given fixture raises an error Given I have a bar And I have a bar pytest-bdd-3.2.1/tests/feature/tags.feature000066400000000000000000000003261352722444400207140ustar00rootroot00000000000000@feature_tag_1 @feature_tag_2 Feature: Tags @scenario_tag_1 @scenario_tag_2 Scenario: Tags Given I have a bar @scenario_tag_10 @scenario_tag_20 Scenario: Tags 2 Given I have a bar pytest-bdd-3.2.1/tests/feature/test_alias.py000066400000000000000000000013041352722444400211000ustar00rootroot00000000000000"""Test step alias when decorated multiple times.""" from pytest_bdd import scenario, given, when, then @scenario('alias.feature', 'Multiple given alias is not evaluated multiple times') def test_steps(): pass @given('I have an empty list') def results(): return [] @given('I have foo (which is 1) in my list') @given('I have bar (alias of foo) in my list') def foo(results): results.append(1) @when('I do crash (which is 2)') @when('I do boom (alias of crash)') def crash(results): results.append(2) @then('my list should be [1, 2, 2]') def check_results(results): """Fixtures are not evaluated multiple times, so the list will be [1, 2, 2]""" assert results == [1, 2, 2] pytest-bdd-3.2.1/tests/feature/test_background.py000066400000000000000000000025351352722444400221350ustar00rootroot00000000000000"""Test feature background.""" import re import pytest from pytest_bdd import ( given, parsers, scenario, then, ) def test_background_basic(request): """Test feature background.""" @scenario( "background.feature", "Basic usage", ) def test(): pass test(request) def test_background_check_order(request): """Test feature background to ensure that backound steps are executed first.""" @scenario( "background.feature", "Background steps are executed first", ) def test(): pass test(request) @pytest.fixture def foo(): return {} @given(parsers.re(r'a background step with multiple lines:\n(?P.+)', flags=re.DOTALL)) def multi_line(foo, data): assert data == "one\ntwo" @given('foo has a value "bar"') def bar(foo): foo["bar"] = "bar" return foo["bar"] @given('foo has a value "dummy"') def dummy(foo): foo["dummy"] = "dummy" return foo["dummy"] @given('foo has no value "bar"') def no_bar(foo): assert foo["bar"] del foo["bar"] @then('foo should have value "bar"') def foo_has_bar(foo): assert foo["bar"] == "bar" @then('foo should have value "dummy"') def foo_has_dummy(foo): assert foo['dummy'] == "dummy" @then('foo should not have value "bar"') def foo_has_no_bar(foo): assert "bar" not in foo pytest-bdd-3.2.1/tests/feature/test_cucumber_json.py000066400000000000000000000246671352722444400226660ustar00rootroot00000000000000"""Test cucumber json output.""" import json import os.path import textwrap def runandparse(testdir, *args): """Run tests in testdir and parse json output.""" resultpath = testdir.tmpdir.join("cucumber.json") result = testdir.runpytest('--cucumberjson={0}'.format(resultpath), '-s', *args) jsonobject = json.load(resultpath.open()) return result, jsonobject class equals_any(object): """Helper object comparison to which is always 'equal'.""" def __init__(self, type=None): self.type = type def __eq__(self, other): return isinstance(other, self.type) if self.type else True def __cmp__(self, other): return 0 if (isinstance(other, self.type) if self.type else False) else -1 string = type(u'') def test_step_trace(testdir): """Test step trace.""" testdir.makefile(".ini", pytest=textwrap.dedent(""" [pytest] markers = scenario-passing-tag scenario-failing-tag scenario-outline-passing-tag feature-tag """)) testdir.makefile('.feature', test=textwrap.dedent(""" @feature-tag Feature: One passing scenario, one failing scenario @scenario-passing-tag Scenario: Passing Given a passing step And some other passing step @scenario-failing-tag Scenario: Failing Given a passing step And a failing step @scenario-outline-passing-tag Scenario: Passing outline Given type and value Examples: example1 | type | value | | str | hello | | int | 42 | | float | 1.0 | """)) testdir.makepyfile(textwrap.dedent(""" import pytest from pytest_bdd import given, when, scenario @given('a passing step') def a_passing_step(): return 'pass' @given('some other passing step') def some_other_passing_step(): return 'pass' @given('a failing step') def a_failing_step(): raise Exception('Error') @given('type and value ') def type_type_and_value_value(): return 'pass' @scenario('test.feature', 'Passing') def test_passing(): pass @scenario('test.feature', 'Failing') def test_failing(): pass @scenario('test.feature', 'Passing outline') def test_passing_outline(): pass """)) result, jsonobject = runandparse(testdir) assert result.ret expected = [ { "description": "", "elements": [ { "description": "", "id": "test_passing", "keyword": "Scenario", "line": 5, "name": "Passing", "steps": [ { "keyword": "Given", "line": 6, "match": { "location": "" }, "name": "a passing step", "result": { "status": "passed", "duration": equals_any(int) } }, { "keyword": "And", "line": 7, "match": { "location": "" }, "name": "some other passing step", "result": { "status": "passed", "duration": equals_any(int) } } ], "tags": [ { 'name': 'scenario-passing-tag', 'line': 4, } ], "type": "scenario" }, { "description": "", "id": "test_failing", "keyword": "Scenario", "line": 10, "name": "Failing", "steps": [ { "keyword": "Given", "line": 11, "match": { "location": "" }, "name": "a passing step", "result": { "status": "passed", "duration": equals_any(int) } }, { "keyword": "And", "line": 12, "match": { "location": "" }, "name": "a failing step", "result": { "error_message": equals_any(string), "status": "failed", "duration": equals_any(int) } } ], "tags": [ { 'name': 'scenario-failing-tag', 'line': 9, } ], "type": "scenario" }, { "description": "", "keyword": "Scenario", "tags": [ { "line": 14, "name": "scenario-outline-passing-tag" } ], "steps": [ { "line": 16, "match": {"location": ""}, "result": { "status": "passed", "duration": equals_any(int) }, "keyword": "Given", "name": "type and value " } ], "line": 15, "type": "scenario", "id": "test_passing_outline[str-hello]", "name": "Passing outline" }, { "description": "", "keyword": "Scenario", "tags": [ { "line": 14, "name": "scenario-outline-passing-tag" } ], "steps": [ { "line": 16, "match": {"location": ""}, "result": { "status": "passed", "duration": equals_any(int) }, "keyword": "Given", "name": "type and value " } ], "line": 15, "type": "scenario", "id": "test_passing_outline[int-42]", "name": "Passing outline" }, { "description": "", "keyword": "Scenario", "tags": [ { "line": 14, "name": "scenario-outline-passing-tag" } ], "steps": [ { "line": 16, "match": {"location": ""}, "result": { "status": "passed", "duration": equals_any(int) }, "keyword": "Given", "name": "type and value " } ], "line": 15, "type": "scenario", "id": "test_passing_outline[float-1.0]", "name": "Passing outline" } ], "id": os.path.join("test_step_trace0", "test.feature"), "keyword": "Feature", "line": 2, "name": "One passing scenario, one failing scenario", "tags": [ { 'name': 'feature-tag', 'line': 1, } ], "uri": os.path.join(testdir.tmpdir.basename, 'test.feature'), } ] assert jsonobject == expected def test_step_trace_with_expand_option(testdir): """Test step trace.""" testdir.makefile(".ini", pytest=textwrap.dedent(""" [pytest] markers = feature-tag scenario-outline-passing-tag """)) testdir.makefile('.feature', test=textwrap.dedent(""" @feature-tag Feature: One scenario outline, expanded to multiple scenarios @scenario-outline-passing-tag Scenario: Passing outline Given type and value Examples: example1 | type | value | | str | hello | | int | 42 | | float | 1.0 | """)) testdir.makepyfile(textwrap.dedent(""" import pytest from pytest_bdd import given, scenario @given('type and value ') def type_type_and_value_value(): return 'pass' @scenario('test.feature', 'Passing outline') def test_passing_outline(): pass """)) result, jsonobject = runandparse(testdir, '--cucumber-json-expanded') assert result.ret == 0 assert jsonobject[0]["elements"][0]["steps"][0]["name"] == "type str and value hello" assert jsonobject[0]["elements"][1]["steps"][0]["name"] == "type int and value 42" assert jsonobject[0]["elements"][2]["steps"][0]["name"] == "type float and value 1.0" pytest-bdd-3.2.1/tests/feature/test_description.py000066400000000000000000000006401352722444400223340ustar00rootroot00000000000000"""Test descriptions.""" from pytest_bdd import scenario def test_description(request): """Test description for the feature.""" @scenario( 'description.feature', 'Description' ) def test(): pass assert test.__scenario__.feature.description == """In order to achieve something I want something Because it will be cool Some description goes here.""" test(request) pytest-bdd-3.2.1/tests/feature/test_feature_base_dir.py000066400000000000000000000067301352722444400233020ustar00rootroot00000000000000"""Test feature base dir.""" import pytest NOT_EXISTING_FEATURE_PATHS = [ '.', '/does/not/exist/', ] @pytest.mark.parametrize( 'base_dir', NOT_EXISTING_FEATURE_PATHS ) def test_feature_path_not_found(testdir, base_dir): """Test feature base dir.""" prepare_testdir(testdir, base_dir) result = testdir.runpytest('-k', 'test_not_found_by_ini') result.assert_outcomes(passed=2) def test_feature_path_ok(testdir): base_dir = 'features' prepare_testdir(testdir, base_dir) result = testdir.runpytest('-k', 'test_ok_by_ini') result.assert_outcomes(passed=2) def test_feature_path_by_param_not_found(testdir): """As param takes precendence even if ini config is correct it should fail if passed param is incorrect""" base_dir = 'features' prepare_testdir(testdir, base_dir) result = testdir.runpytest('-k', 'test_not_found_by_param') result.assert_outcomes(passed=4) @pytest.mark.parametrize( 'base_dir', NOT_EXISTING_FEATURE_PATHS ) def test_feature_path_by_param_ok(testdir, base_dir): """If ini config is incorrect but param path is fine it should be able to find features""" prepare_testdir(testdir, base_dir) result = testdir.runpytest('-k', 'test_ok_by_param') result.assert_outcomes(passed=2) def prepare_testdir(testdir, ini_base_dir): testdir.makeini(""" [pytest] bdd_features_base_dir={} """.format(ini_base_dir)) feature_file = testdir.mkdir('features').join('steps.feature') feature_file.write(""" Scenario: When scenario found Given found """) testdir.makepyfile(""" import os.path import pytest from pytest_bdd import scenario, scenarios FEATURE = 'steps.feature' @pytest.fixture(params=[ 'When scenario found', ]) def scenario_name(request): return request.param @pytest.mark.parametrize( 'multiple', [True, False] ) def test_not_found_by_ini(scenario_name, multiple): with pytest.raises(IOError) as exc: if multiple: scenarios(FEATURE) else: scenario(FEATURE, scenario_name) assert os.path.abspath(os.path.join('{}', FEATURE)) in str(exc.value) @pytest.mark.parametrize( 'multiple', [True, False] ) def test_ok_by_ini(scenario_name, multiple): # Shouldn't raise any exception if multiple: scenarios(FEATURE) else: scenario(FEATURE, scenario_name) @pytest.mark.parametrize( 'multiple', [True, False] ) @pytest.mark.parametrize( 'param_base_dir', [ '.', '/does/not/exist/', ] ) def test_not_found_by_param(scenario_name, param_base_dir, multiple): with pytest.raises(IOError) as exc: if multiple: scenarios(FEATURE, features_base_dir=param_base_dir) else: scenario(FEATURE, scenario_name, features_base_dir=param_base_dir) assert os.path.abspath(os.path.join(param_base_dir, FEATURE)) in str(exc.value) @pytest.mark.parametrize( 'multiple', [True, False] ) def test_ok_by_param(scenario_name, multiple): # Shouldn't raise any exception no matter of bdd_features_base_dir in ini if multiple: scenarios(FEATURE, features_base_dir='features') else: scenario(FEATURE, scenario_name, features_base_dir='features') """.format(ini_base_dir)) pytest-bdd-3.2.1/tests/feature/test_gherkin_terminal_reporter.py000066400000000000000000000300711352722444400252560ustar00rootroot00000000000000import re import pytest from pytest_bdd import scenario, given, when, then from tests.utils import get_test_filepath, prepare_feature_and_py_files @scenario('gherkin_terminal_reporter.feature', 'Should default output be the same as regular terminal reporter') def test_Should_default_output_be_the_same_as_regular_terminal_reporter(): pass @scenario('gherkin_terminal_reporter.feature', 'Should verbose mode enable displaying feature and scenario names rather than test names in a single line') def test_Should_verbose_mode_enable_displaying_feature_and_scenario_names_rather_than_test_names_in_a_single_line(): pass @scenario('gherkin_terminal_reporter.feature', 'Should verbose mode preserve displaying of regular tests as usual') def test_Should_verbose_mode_preserve_displaying_of_regular_tests_as_usual(): pass @scenario('gherkin_terminal_reporter.feature', 'Should double verbose mode enable displaying of full gherkin scenario description') def test_Should_double_verbose_mode_enable_displaying_of_full_gherkin_scenario_description(): pass @scenario('gherkin_terminal_reporter.feature', 'Should error message be displayed when no scenario is found') def test_Should_error_message_be_displayed_when_no_scenario_is_found(verbosity_mode): pass @scenario('gherkin_terminal_reporter.feature', 'Should error message be displayed when no step is found') def test_Should_error_message_be_displayed_when_no_step_is_found(verbosity_mode): pass @scenario('gherkin_terminal_reporter.feature', 'Should error message be displayed when error occurs during test execution') def test_Should_error_message_be_displayed_when_error_occurs_during_test_execution(verbosity_mode): pass @scenario('gherkin_terminal_reporter.feature', 'Should local variables be displayed when --showlocals option is used') def test_Should_local_variables_be_displayed_when___showlocals_option_is_used(): pass @scenario('gherkin_terminal_reporter.feature', 'Should step parameters be replaced by their values') def test_Should_step_parameters_be_replaced_by_their_values(): pass @pytest.fixture(params=[0, 1, 2], ids=['compact mode', 'line per test', 'verbose']) def verbosity_mode(request): return request.param, '-' + 'v' * request.param if request.param else '' @pytest.fixture def test_execution(): return {} @given("there is non-gherkin scenario implemented") def non_gherkin_test(testdir): testdir.makepyfile(test_regular=""" def test_1(): pass """) @given("there is gherkin scenario implemented") def gherkin_scenario(testdir): testdir.makefile('.feature', test=""" Feature: Gherkin terminal output feature Scenario: Scenario example 1 Given there is a bar When the bar is accessed Then world explodes """) testdir.makepyfile(test_gherkin=""" import pytest from pytest_bdd import given, when, scenario, then @given('there is a bar') def a_bar(): return 'bar' @when('the bar is accessed') def the_bar_is_accessed(): pass @then('world explodes') def world_explodes(): pass @scenario('test.feature', 'Scenario example 1') def test_scenario_1(): pass """) @given("there is gherkin scenario outline implemented") def gherkin_scenario_outline(testdir): example = { 'start': 12, 'eat': 5, 'left': 7, } testdir.makefile('.feature', test=""" Feature: Gherkin terminal output feature Scenario Outline: Scenario example 2 Given there are cucumbers When I eat cucumbers Then I should have cucumbers Examples: | start | eat | left | |{start}|{eat}|{left}| """.format(**example)) testdir.makepyfile(test_gherkin=""" import pytest from pytest_bdd import given, when, scenario, then @given('there are cucumbers') def start_cucumbers(start): return start @when('I eat cucumbers') def eat_cucumbers(start_cucumbers, eat): pass @then('I should have cucumbers') def should_have_left_cucumbers(start_cucumbers, start, eat, left): pass @scenario('test.feature', 'Scenario example 2') def test_scenario_2(): pass """) return example @when("I run tests") def run_tests(testdir, test_execution): test_execution['regular'] = testdir.runpytest() test_execution['gherkin'] = testdir.runpytest('--gherkin-terminal-reporter') @then("output must be formatted the same way as regular one") def output_must_be_the_same_as_regular_reporter(test_execution): reg = test_execution['regular'] ghe = test_execution['gherkin'] assert reg.ret == 0 assert ghe.ret == 0 # last line can be different because of test execution time is printed reg_lines = reg.stdout.lines if reg.stdout.lines[-1] else reg.stdout.lines[:-2] reg_lines[-1] = re.sub(r' \d+\.\d+ ', ' X ', reg_lines[-1]) ghe_lines = ghe.stdout.lines if ghe.stdout.lines[-1] else ghe.stdout.lines[:-2] ghe_lines[-1] = re.sub(r' \d+\.\d+ ', ' X ', ghe_lines[-1]) for l1, l2 in zip(reg_lines, ghe_lines): assert l1 == l2 @when("I run tests with verbose mode") def run_tests_with_verbose_mode(testdir, test_execution): test_execution['regular'] = testdir.runpytest('-v') test_execution['gherkin'] = testdir.runpytest('--gherkin-terminal-reporter', '-v') @when("I run tests with very verbose mode") def run_tests_with_very_verbose_mode(testdir, test_execution): test_execution['regular'] = testdir.runpytest('-vv') test_execution['gherkin'] = testdir.runpytest('--gherkin-terminal-reporter', '-vv') @when("I run tests with step expanded mode") def run_tests_with_step_expanded_mode(testdir, test_execution): test_execution['regular'] = testdir.runpytest('-vv') test_execution['gherkin'] = testdir.runpytest( '--gherkin-terminal-reporter', '--gherkin-terminal-reporter-expanded', '-vv', ) @then("output should contain single line feature description") def output_should_contain_single_line_feature_description(test_execution): ghe = test_execution['gherkin'] assert ghe.ret == 0 ghe.stdout.fnmatch_lines('Feature: Gherkin terminal output feature') @then("output should contain single line scenario description") def output_should_contain_single_line_scenario_description(test_execution): ghe = test_execution['gherkin'] assert ghe.ret == 0 ghe.stdout.fnmatch_lines('*Scenario: Scenario example 1 PASSED') @then("output must contain full gherkin scenario description") def output_should_contain_full_gherkin_scenario_description(test_execution): ghe = test_execution['gherkin'] assert ghe.ret == 0 ghe.stdout.fnmatch_lines('*Scenario: Scenario example 1') ghe.stdout.fnmatch_lines('*Given there is a bar') ghe.stdout.fnmatch_lines('*When the bar is accessed') ghe.stdout.fnmatch_lines('*Then world explodes') ghe.stdout.fnmatch_lines('*PASSED') @given('there is gherkin scenario without implementation') def gherkin_scenario_without_implementation(testdir): testdir.makefile('.feature', test=""" Feature: Gherkin terminal output feature Scenario: Scenario example 1 Given there is a bar When the bar is accessed Then world explodes """) testdir.makepyfile(test_gherkin=""" import pytest from pytest_bdd import scenarios scenarios('.') """) @when('I run tests with any verbosity mode') def run_tests_with_any_verbosity_mode( test_execution, verbosity_mode, testdir, gherkin_scenario_without_implementation): # test_execution['gherkin'] = testdir.runpytest( # '--gherkin-terminal-reporter', '-vv') if verbosity_mode[1]: test_execution['gherkin'] = testdir.runpytest( '--gherkin-terminal-reporter', verbosity_mode[1]) else: test_execution['gherkin'] = testdir.runpytest( '--gherkin-terminal-reporter') @then('output contains error about missing scenario implementation') def output_contains_error_about_missing_scenario_implementation(test_execution): ghe = test_execution['gherkin'] assert ghe.ret ghe.stdout.fnmatch_lines('''*StepDefinitionNotFoundError: Step definition is not found: Given "there is a bar". ''' '''Line 3 in scenario "Scenario example 1"*''') @given('there is gherkin scenario partially implemented') def partially_implemented_gherkin_scenario(testdir): testdir.makefile('.feature', test=""" Feature: Gherkin terminal output feature Scenario: Scenario example 1 Given there is a bar When the bar is accessed Then world explodes """) testdir.makepyfile(test_gherkin=""" import pytest from pytest_bdd import given, when, scenario, then @given('there is a bar') def a_bar(): return 'bar' @when('the bar is accessed') def the_bar_is_accessed(): pass @scenario('test.feature', 'Scenario example 1') def test_scenario_1(): pass """) @then('output contains error about missing step implementation') def output_contains_error_about_missing_step_implementation(test_execution): ghe = test_execution['gherkin'] assert ghe.ret ghe.stdout.fnmatch_lines('''*StepDefinitionNotFoundError: Step definition is not found: Given "there is a bar". ''' '''Line 3 in scenario "Scenario example 1"*''') @given('there is gherkin scenario with broken implementation') def there_is_gherkin_scenario_with_broken_implementation(testdir): testdir.makefile('.feature', test=""" Feature: Gherkin terminal output feature Scenario: Scenario example 1 Given there is a bar When the bar is accessed Then world explodes """) testdir.makepyfile(test_gherkin=""" import pytest from pytest_bdd import given, when, scenario, then @given('there is a bar') def a_bar(request): return 'bar' @when('the bar is accessed') def the_bar_is_accessed(request): local_var = 'value2' raise Exception("ERROR") @scenario('test.feature', 'Scenario example 1') def test_scenario_1(): pass """) @when('I run tests with --showlocals') def run_tests_with___showlocals(test_execution, testdir): test_execution['gherkin'] = testdir.runpytest('--gherkin-terminal-reporter', '--showlocals') @then('error traceback contains local variable descriptions') def error_traceback_contains_local_variable_descriptions(test_execution): ghe = test_execution['gherkin'] assert ghe.ret ghe.stdout.fnmatch_lines('''request*=* cucumbers') def start_cucumbers(start): assert isinstance(start, int) return dict(start=start) @when('I eat cucumbers') def eat_cucumbers(start_cucumbers, eat): assert isinstance(eat, float) start_cucumbers['eat'] = eat @then('I should have cucumbers') def should_have_left_cucumbers(start_cucumbers, start, eat, left): assert isinstance(left, str) assert start - eat == int(left) assert start_cucumbers['start'] == start assert start_cucumbers['eat'] == eat def test_wrongly_outlined(request): """Test parametrized scenario when the test function lacks parameters.""" with pytest.raises(exceptions.ScenarioExamplesNotValidError) as exc: @scenario( 'outline.feature', 'Outlined with wrong examples', ) def wrongly_outlined(): pass assert re.match( r"""Scenario \"Outlined with wrong examples\" in the feature \"(.+)\" has not valid examples\. """ r"""Set of step parameters (.+) should match set of example values """ r"""(.+)\.""", exc.value.args[0] ) def test_wrong_vertical_examples_scenario(testdir): """Test parametrized scenario vertical example table has wrong format.""" features = testdir.mkdir('features') feature = features.join('test.feature') feature.write_text(textwrap.dedent(u""" Scenario Outline: Outlined with wrong vertical example table Given there are cucumbers When I eat cucumbers Then I should have cucumbers Examples: Vertical | start | 12 | 2 | | start | 10 | 1 | | left | 7 | 1 | """), 'utf-8', ensure=True) with pytest.raises(exceptions.FeatureError) as exc: @scenario( feature.strpath, 'Outlined with wrong vertical example table', ) def wrongly_outlined(): pass assert exc.value.args[0] == ( 'Scenario has not valid examples. Example rows should contain unique parameters.' ' "start" appeared more than once') def test_wrong_vertical_examples_feature(testdir): """Test parametrized feature vertical example table has wrong format.""" features = testdir.mkdir('features') feature = features.join('test.feature') feature.write_text(textwrap.dedent(u""" Feature: Outlines Examples: Vertical | start | 12 | 2 | | start | 10 | 1 | | left | 7 | 1 | Scenario Outline: Outlined with wrong vertical example table Given there are cucumbers When I eat cucumbers Then I should have cucumbers """), 'utf-8', ensure=True) with pytest.raises(exceptions.FeatureError) as exc: @scenario( feature.strpath, 'Outlined with wrong vertical example table', ) def wrongly_outlined(): pass assert exc.value.args[0] == ( 'Feature has not valid examples. Example rows should contain unique parameters.' ' "start" appeared more than once') @pytest.fixture(params=[1, 2, 3]) def other_fixture(request): return request.param @scenario( 'outline.feature', 'Outlined given, when, thens', example_converters=dict(start=int, eat=float, left=str) ) def test_outlined_with_other_fixtures(other_fixture): """Test outlined scenario also using other parametrized fixture.""" @scenario( 'outline.feature', 'Outlined with vertical example table', example_converters=dict(start=int, eat=float, left=str) ) def test_vertical_example(request): """Test outlined scenario with vertical examples table.""" assert get_parametrize_markers_args(request.node) == ( [u'start', u'eat', u'left'], [[12, 5.0, '7'], [2, 1.0, '1']]) @given('there are ') def start_fruits(start, fruits): assert isinstance(start, int) return {fruits: dict(start=start)} @when('I eat ') def eat_fruits(start_fruits, eat, fruits): assert isinstance(eat, float) start_fruits[fruits]['eat'] = eat @then('I should have ') def should_have_left_fruits(start_fruits, start, eat, left, fruits): assert isinstance(left, str) assert start - eat == int(left) assert start_fruits[fruits]['start'] == start assert start_fruits[fruits]['eat'] == eat @scenario( 'outline_feature.feature', 'Outlined given, when, thens', example_converters=dict(start=int, eat=float, left=str) ) def test_outlined_feature(request): assert get_parametrize_markers_args(request.node) == ( ['start', 'eat', 'left'], [[12, 5.0, '7'], [5, 4.0, '1']], ['fruits'], [[u'oranges'], [u'apples']] ) pytest-bdd-3.2.1/tests/feature/test_outline_empty_values.py000066400000000000000000000016151352722444400242700ustar00rootroot00000000000000"""Scenario Outline with empty example values tests.""" from pytest_bdd import given, scenario, then, when from pytest_bdd.utils import get_parametrize_markers_args @given('there are cucumbers') def start_cucumbers(start): pass @when('I eat cucumbers') def eat_cucumbers(eat): pass @then('I should have cucumbers') def should_have_left_cucumbers(left): pass @scenario( 'outline.feature', 'Outlined with empty example values', ) def test_scenario_with_empty_example_values(request): assert get_parametrize_markers_args(request.node) == ( [u'start', u'eat', u'left'], [['#', '', '']]) @scenario( 'outline.feature', 'Outlined with empty example values vertical', ) def test_scenario_with_empty_example_values_vertical(request): assert get_parametrize_markers_args(request.node) == ( [u'start', u'eat', u'left'], [['#', '', '']]) pytest-bdd-3.2.1/tests/feature/test_parametrized.py000066400000000000000000000021771352722444400225070ustar00rootroot00000000000000import pytest from pytest_bdd import given, when, then, scenario @pytest.mark.parametrize( ['start', 'eat', 'left'], [(12, 5, 7)]) @scenario( 'parametrized.feature', 'Parametrized given, when, thens', ) def test_parametrized(request, start, eat, left): """Test parametrized scenario.""" @pytest.fixture(params=[1, 2]) def foo_bar(request): return 'bar' * request.param @pytest.mark.parametrize( ['start', 'eat', 'left'], [(12, 5, 7)]) @scenario( 'parametrized.feature', 'Parametrized given, when, thens', ) def test_parametrized_with_other_fixtures(request, start, eat, left, foo_bar): """Test parametrized scenario, but also with other parametrized fixtures.""" @given('there are cucumbers') def start_cucumbers(start): return dict(start=start) @when('I eat cucumbers') def eat_cucumbers(start_cucumbers, start, eat): start_cucumbers['eat'] = eat @then('I should have 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 pytest-bdd-3.2.1/tests/feature/test_report.py000066400000000000000000000236771352722444400213430ustar00rootroot00000000000000"""Test scenario reporting.""" import textwrap import execnet.gateway_base class equals_any(object): """Helper object comparison to which is always 'equal'.""" def __init__(self, type=None): self.type = type def __eq__(self, other): return isinstance(other, self.type) if self.type else True def __cmp__(self, other): return 0 if (isinstance(other, self.type) if self.type else False) else -1 string = type(u'') def test_step_trace(testdir): """Test step trace.""" testdir.makefile(".ini", pytest=textwrap.dedent(""" [pytest] markers = feature-tag scenario-passing-tag scenario-failing-tag """)) feature = testdir.makefile('.feature', test=textwrap.dedent(""" @feature-tag Feature: One passing scenario, one failing scenario @scenario-passing-tag Scenario: Passing Given a passing step And some other passing step @scenario-failing-tag Scenario: Failing Given a passing step And a failing step Scenario Outline: Outlined Given there are cucumbers When I eat cucumbers Then I should have cucumbers Examples: | start | eat | left | | 12 | 5 | 7 | | 5 | 4 | 1 | """)) relpath = feature.relto(testdir.tmpdir.dirname) testdir.makepyfile(textwrap.dedent(""" import pytest from pytest_bdd import given, when, then, scenarios @given('a passing step') def a_passing_step(): return 'pass' @given('some other passing step') def some_other_passing_step(): return 'pass' @given('a failing step') def a_failing_step(): raise Exception('Error') @given('there are cucumbers') def start_cucumbers(start): assert isinstance(start, int) return dict(start=start) @when('I eat cucumbers') def eat_cucumbers(start_cucumbers, eat): assert isinstance(eat, float) start_cucumbers['eat'] = eat @then('I should have cucumbers') def should_have_left_cucumbers(start_cucumbers, start, eat, left): assert isinstance(left, str) assert start - eat == int(left) assert start_cucumbers['start'] == start assert start_cucumbers['eat'] == eat scenarios('test.feature', example_converters=dict(start=int, eat=float, left=str)) """)) result = testdir.inline_run('-vvl') assert result.ret report = result.matchreport('test_passing', when='call').scenario expected = {'feature': {'description': u'', 'filename': feature.strpath, 'line_number': 2, 'name': u'One passing scenario, one failing scenario', 'rel_filename': relpath, 'tags': [u'feature-tag']}, 'line_number': 5, 'name': u'Passing', 'steps': [{'duration': equals_any(float), 'failed': False, 'keyword': 'Given', 'line_number': 6, 'name': u'a passing step', 'type': 'given'}, {'duration': equals_any(float), 'failed': False, 'keyword': 'And', 'line_number': 7, 'name': u'some other passing step', 'type': 'given'}], 'tags': [u'scenario-passing-tag'], 'examples': [], 'example_kwargs': {}} assert report == expected report = result.matchreport('test_failing', when='call').scenario expected = {'feature': {'description': u'', 'filename': feature.strpath, 'line_number': 2, 'name': u'One passing scenario, one failing scenario', 'rel_filename': relpath, 'tags': [u'feature-tag']}, 'line_number': 10, 'name': u'Failing', 'steps': [{'duration': equals_any(float), 'failed': False, 'keyword': 'Given', 'line_number': 11, 'name': u'a passing step', 'type': 'given'}, {'duration': equals_any(float), 'failed': True, 'keyword': 'And', 'line_number': 12, 'name': u'a failing step', 'type': 'given'}], 'tags': [u'scenario-failing-tag'], 'examples': [], 'example_kwargs': {}} assert report == expected report = result.matchreport('test_outlined[12-5.0-7]', when='call').scenario expected = {'feature': {'description': u'', 'filename': feature.strpath, 'line_number': 2, 'name': u'One passing scenario, one failing scenario', 'rel_filename': relpath, 'tags': [u'feature-tag']}, 'line_number': 14, 'name': u'Outlined', 'steps': [{'duration': equals_any(float), 'failed': False, 'keyword': 'Given', 'line_number': 15, 'name': u'there are cucumbers', 'type': 'given'}, {'duration': equals_any(float), 'failed': False, 'keyword': 'When', 'line_number': 16, 'name': u'I eat cucumbers', 'type': 'when'}, {'duration': equals_any(float), 'failed': False, 'keyword': 'Then', 'line_number': 17, 'name': u'I should have cucumbers', 'type': 'then'}], 'tags': [], 'examples': [{'line_number': 19, 'name': None, 'row_index': 0, 'rows': [['start', 'eat', 'left'], [[12, 5.0, '7'], [5, 4.0, '1']]]}], 'example_kwargs': {'eat': '5.0', 'left': '7', 'start': '12'}, } assert report == expected report = result.matchreport('test_outlined[5-4.0-1]', when='call').scenario expected = {'feature': {'description': u'', 'filename': feature.strpath, 'line_number': 2, 'name': u'One passing scenario, one failing scenario', 'rel_filename': relpath, 'tags': [u'feature-tag']}, 'line_number': 14, 'name': u'Outlined', 'steps': [{'duration': equals_any(float), 'failed': False, 'keyword': 'Given', 'line_number': 15, 'name': u'there are cucumbers', 'type': 'given'}, {'duration': equals_any(float), 'failed': False, 'keyword': 'When', 'line_number': 16, 'name': u'I eat cucumbers', 'type': 'when'}, {'duration': equals_any(float), 'failed': False, 'keyword': 'Then', 'line_number': 17, 'name': u'I should have cucumbers', 'type': 'then'}], 'tags': [], 'examples': [{'line_number': 19, 'name': None, 'row_index': 1, 'rows': [['start', 'eat', 'left'], [[12, 5.0, '7'], [5, 4.0, '1']]]}], 'example_kwargs': {'eat': '4.0', 'left': '1', 'start': '5'}, } assert report == expected def test_complex_types(testdir): """Test serialization of the complex types.""" testdir.makefile('.feature', test=textwrap.dedent(""" Feature: Report serialization containing parameters of complex types Scenario: Complex Given there is a coordinate Examples: | point | | 10,20 | """)) testdir.makepyfile(textwrap.dedent(""" import pytest from pytest_bdd import given, when, then, scenario class Point(object): def __init__(self, x, y): self.x = x self.y = y @classmethod def parse(cls, value): return cls(*(int(x) for x in value.split(','))) class Alien(object): pass @given('there is a coordinate ') def point(point): assert isinstance(point, Point) return point @pytest.mark.parametrize('alien', [Alien()]) @scenario('test.feature', 'Complex', example_converters=dict(point=Point.parse)) def test_complex(alien): pass """)) result = testdir.inline_run('-vvl') report = result.matchreport('test_complex[point0-alien0]', when='call') assert execnet.gateway_base.dumps(report.item) assert execnet.gateway_base.dumps(report.scenario) pytest-bdd-3.2.1/tests/feature/test_reuse.py000066400000000000000000000012171352722444400211350ustar00rootroot00000000000000from pytest_bdd.steps import when from pytest_bdd import given, then, scenario @scenario( 'reuse.feature', 'Given and when using the same fixture should not evaluate it twice', ) def test_reuse(): pass @given('I have an empty list') def empty_list(): return [] @given('I have a fixture (appends 1 to a list)') def appends_1(empty_list): empty_list.append(1) return empty_list given('I have a fixture (appends 1 to a list) in reuse syntax', fixture='appends_1') @when('I use this fixture') def use_fixture(appends_1): pass @then('my list should be [1]') def list_should_be_1(appends_1): assert appends_1 == [1] pytest-bdd-3.2.1/tests/feature/test_same_function_name.py000066400000000000000000000004441352722444400236450ustar00rootroot00000000000000"""Function name same as step name.""" from pytest_bdd import ( scenario, when, ) @scenario('same_function_name.feature', 'When function name same as step name') def test_when_function_name_same_as_step_name(): pass @when('something') def something(): return 'something' pytest-bdd-3.2.1/tests/feature/test_scenario.py000066400000000000000000000032511352722444400216150ustar00rootroot00000000000000"""Test scenario decorator.""" import pytest import re import six from pytest_bdd import ( scenario, given, then, parsers, exceptions, ) def test_scenario_not_found(request): """Test the situation when scenario is not found.""" with pytest.raises(exceptions.ScenarioNotFound) as exc_info: scenario( 'not_found.feature', 'NOT FOUND' ) assert six.text_type(exc_info.value).startswith( 'Scenario "NOT FOUND" in feature "[Empty]" in {feature_path}' .format(feature_path=request.fspath.join('..', 'not_found.feature'))) @given('comments should be at the start of words') def comments(): """Comments.""" pass @then(parsers.parse('this is not {acomment}')) def a_comment(acomment): """A comment.""" assert re.search('a.*comment', acomment) def test_scenario_comments(request): """Test comments inside scenario.""" @scenario( 'comments.feature', 'Comments' ) def test(): pass @scenario( 'comments.feature', 'Strings that are not comments' ) def test2(): pass test(request) test2(request) def test_scenario_not_decorator(testdir): """Test scenario function is used not as decorator.""" testdir.makefile('.feature', foo=""" Scenario: Foo Given I have a bar """) testdir.makepyfile(""" from pytest_bdd import scenario test_foo = scenario('foo.feature', 'Foo') """) result = testdir.runpytest() result.assert_outcomes(failed=1) result.stdout.fnmatch_lines("*ScenarioIsDecoratorOnly: scenario function can only be used as a decorator*") pytest-bdd-3.2.1/tests/feature/test_scenarios.py000066400000000000000000000041341352722444400220010ustar00rootroot00000000000000"""Test scenarios shortcut.""" import textwrap def test_scenarios(testdir): """Test scenarios shortcut.""" testdir.makeini(""" [pytest] console_output_style=classic """) testdir.makeconftest(""" import pytest from pytest_bdd import given @given('I have a bar') def i_have_bar(): print('bar!') return 'bar' """) features = testdir.mkdir('features') features.join('test.feature').write_text(textwrap.dedent(u""" Scenario: Test scenario Given I have a bar """), 'utf-8', ensure=True) features.join('subfolder', 'test.feature').write_text(textwrap.dedent(u""" Scenario: Test subfolder scenario Given I have a bar Scenario: Test failing subfolder scenario Given I have a failing bar Scenario: Test already bound scenario Given I have a bar Scenario: Test scenario Given I have a bar """), 'utf-8', ensure=True) testdir.makepyfile(""" import pytest from pytest_bdd import scenarios, scenario @scenario('features/subfolder/test.feature', 'Test already bound scenario') def test_already_bound(): pass scenarios('features') """) result = testdir.runpytest('-v', '-s') result.stdout.fnmatch_lines(['*collected 5 items']) result.stdout.fnmatch_lines(['*test_test_subfolder_scenario *bar!', 'PASSED']) result.stdout.fnmatch_lines(['*test_test_scenario *bar!', 'PASSED']) result.stdout.fnmatch_lines(['*test_test_failing_subfolder_scenario *FAILED']) result.stdout.fnmatch_lines(['*test_already_bound *bar!', 'PASSED']) result.stdout.fnmatch_lines(['*test_test_scenario_1 *bar!', 'PASSED']) def test_scenarios_none_found(testdir): """Test scenarios shortcut when no scenarios found.""" testpath = testdir.makepyfile(""" import pytest from pytest_bdd import scenarios scenarios('.') """) reprec = testdir.inline_run(testpath) reprec.assertoutcome(failed=1) assert 'NoScenariosFound' in str(reprec.getreports()[1].longrepr) pytest-bdd-3.2.1/tests/feature/test_steps.py000066400000000000000000000145511352722444400211550ustar00rootroot00000000000000import pytest from pytest_bdd import scenario, given, when, then from pytest_bdd import exceptions @scenario('steps.feature', 'Executed step by step') def test_steps(): pass @given('I have a foo fixture with value "foo"') def foo(): return 'foo' @given('there is a list') def results(): return [] @when('I append 1 to the list') def append_1(results): results.append(1) @when('I append 2 to the list') def append_2(results): results.append(2) @when('I append 3 to the list') def append_3(results): results.append(3) @then('foo should have value "foo"') def foo_is_foo(foo): assert foo == 'foo' @then('the list should be [1, 2, 3]') def check_results(results): assert results == [1, 2, 3] @scenario('steps.feature', 'When step can be the first') def test_when_first(): pass @when('I do nothing') def do_nothing(): pass @then('I make no mistakes') def no_errors(): assert True @scenario('steps.feature', 'Then step can follow Given step') def test_then_after_given(): pass @given('xyz') def xyz(): """Used in the test_same_step_name.""" return @scenario('steps.feature', 'All steps are declared in the conftest') def test_conftest(): pass def test_multiple_given(request): """Using the same given fixture raises an error.""" @scenario( 'steps.feature', 'Using the same given fixture raises an error', ) def test(): pass with pytest.raises(exceptions.GivenAlreadyUsed): test(request) def test_step_hooks(testdir): """When step fails.""" testdir.makefile(".feature", test=""" Scenario: When step has hook on failure Given I have a bar When it fails Scenario: When step's dependency a has failure Given I have a bar When it's dependency fails Scenario: When step is not found Given not found Scenario: When step validation error happens Given foo And foo """) testdir.makepyfile(""" import pytest from pytest_bdd import given, when, scenario @given('I have a bar') def i_have_bar(): return 'bar' @when('it fails') def when_it_fails(): raise Exception('when fails') @given('I have a bar') def i_have_bar(): return 'bar' @pytest.fixture def dependency(): raise Exception('dependency fails') @when("it's dependency fails") def when_dependency_fails(dependency): pass @scenario('test.feature', "When step's dependency a has failure") def test_when_dependency_fails(): pass @scenario('test.feature', 'When step has hook on failure') def test_when_fails(): pass @scenario('test.feature', 'When step is not found') def test_when_not_found(): pass @when('foo') def foo(): return 'foo' @scenario('test.feature', 'When step validation error happens') def test_when_step_validation_error(): pass """) reprec = testdir.inline_run("-k test_when_fails") assert reprec.ret == 1 calls = reprec.getcalls("pytest_bdd_before_scenario") assert calls[0].request calls = reprec.getcalls("pytest_bdd_after_scenario") assert calls[0].request calls = reprec.getcalls("pytest_bdd_before_step") assert calls[0].request calls = reprec.getcalls("pytest_bdd_before_step_call") assert calls[0].request calls = reprec.getcalls("pytest_bdd_after_step") assert calls[0].request calls = reprec.getcalls("pytest_bdd_step_error") assert calls[0].request reprec = testdir.inline_run("-k test_when_not_found") assert reprec.ret == 1 calls = reprec.getcalls("pytest_bdd_step_func_lookup_error") assert calls[0].request reprec = testdir.inline_run("-k test_when_step_validation_error") assert reprec.ret == 1 reprec = testdir.inline_run("-k test_when_dependency_fails", '-vv') assert reprec.ret == 1 calls = reprec.getcalls("pytest_bdd_before_step") assert len(calls) == 2 calls = reprec.getcalls("pytest_bdd_before_step_call") assert len(calls) == 1 calls = reprec.getcalls("pytest_bdd_step_error") assert calls[0].request def test_step_trace(testdir): """Test step trace.""" testdir.makeini(""" [pytest] console_output_style=classic """) testdir.makefile('.feature', test=""" Scenario: When step has failure Given I have a bar When it fails Scenario: When step is not found Given not found Scenario: When step validation error happens Given foo And foo """) testdir.makepyfile(""" import pytest from pytest_bdd import given, when, scenario @given('I have a bar') def i_have_bar(): return 'bar' @when('it fails') def when_it_fails(): raise Exception('when fails') @scenario('test.feature', 'When step has failure') def test_when_fails_inline(): pass @scenario('test.feature', 'When step has failure') def test_when_fails_decorated(): pass @scenario('test.feature', 'When step is not found') def test_when_not_found(): pass @when('foo') def foo(): return 'foo' @scenario('test.feature', 'When step validation error happens') def test_when_step_validation_error(): pass """) result = testdir.runpytest('-k test_when_fails_inline', '-vv') assert result.ret == 1 result.stdout.fnmatch_lines(['*test_when_fails_inline*FAILED']) assert 'INTERNALERROR' not in result.stdout.str() result = testdir.runpytest('-k test_when_fails_decorated', '-vv') assert result.ret == 1 result.stdout.fnmatch_lines(['*test_when_fails_decorated*FAILED']) assert 'INTERNALERROR' not in result.stdout.str() result = testdir.runpytest('-k test_when_not_found', '-vv') assert result.ret == 1 result.stdout.fnmatch_lines(['*test_when_not_found*FAILED']) assert 'INTERNALERROR' not in result.stdout.str() result = testdir.runpytest('-k test_when_step_validation_error', '-vv') assert result.ret == 1 result.stdout.fnmatch_lines(['*test_when_step_validation_error*FAILED']) assert 'INTERNALERROR' not in result.stdout.str() pytest-bdd-3.2.1/tests/feature/test_tags.py000066400000000000000000000123351352722444400207530ustar00rootroot00000000000000"""Test tags.""" import textwrap import pytest from pytest_bdd import feature def test_tags_selector(testdir): """Test tests selection by tags.""" testdir.makefile(".ini", pytest=textwrap.dedent(""" [pytest] markers = feature_tag_1 feature_tag_2 scenario_tag_01 scenario_tag_02 scenario_tag_10 scenario_tag_20 """)) testdir.makefile('.feature', test=""" @feature_tag_1 @feature_tag_2 Feature: Tags @scenario_tag_01 @scenario_tag_02 Scenario: Tags Given I have a bar @scenario_tag_10 @scenario_tag_20 Scenario: Tags 2 Given I have a bar """) testdir.makepyfile(""" import pytest from pytest_bdd import given, scenarios @given('I have a bar') def i_have_bar(): return 'bar' scenarios('test.feature') """) result = testdir.runpytest('-m', 'scenario_tag_10 and not scenario_tag_01', '-vv') outcomes = result.parseoutcomes() assert outcomes['passed'] == 1 assert outcomes['deselected'] == 1 result = testdir.runpytest('-m', 'scenario_tag_01 and not scenario_tag_10', '-vv').parseoutcomes() assert result['passed'] == 1 assert result['deselected'] == 1 result = testdir.runpytest('-m', 'feature_tag_1', '-vv').parseoutcomes() assert result['passed'] == 2 result = testdir.runpytest('-m', 'feature_tag_10', '-vv').parseoutcomes() assert result['deselected'] == 2 def test_tags_after_background_issue_160(testdir): """Make sure using a tag after background works.""" testdir.makefile(".ini", pytest=textwrap.dedent(""" [pytest] markers = tag """)) testdir.makefile('.feature', test=""" Feature: Tags after background Background: Given I have a bar @tag Scenario: Tags Given I have a baz Scenario: Tags 2 Given I have a baz """) testdir.makepyfile(""" import pytest from pytest_bdd import given, scenarios @given('I have a bar') def i_have_bar(): return 'bar' @given('I have a baz') def i_have_baz(): return 'baz' scenarios('test.feature') """) result = testdir.runpytest('-m', 'tag', '-vv').parseoutcomes() assert result['passed'] == 1 assert result['deselected'] == 1 def test_apply_tag_hook(testdir): testdir.makeconftest(""" import pytest @pytest.hookimpl(tryfirst=True) def pytest_bdd_apply_tag(tag, function): if tag == 'todo': marker = pytest.mark.skipif(True, reason="Not implemented yet") marker(function) return True else: # Fall back to pytest-bdd's default behavior return None """) testdir.makefile('.feature', test=""" Feature: Customizing tag handling @todo Scenario: Tags Given I have a bar @xfail Scenario: Tags 2 Given I have a bar """) testdir.makepyfile(""" from pytest_bdd import given, scenarios @given('I have a bar') def i_have_bar(): return 'bar' scenarios('test.feature') """) result = testdir.runpytest('-rsx') result.stdout.fnmatch_lines(["SKIP*: Not implemented yet"]) result.stdout.fnmatch_lines(["*= 1 skipped, 1 xpassed * =*"]) def test_tag_with_spaces(testdir): testdir.makefile(".ini", pytest=textwrap.dedent(""" [pytest] markers = test with spaces """)) testdir.makeconftest(""" import pytest @pytest.hookimpl(tryfirst=True) def pytest_bdd_apply_tag(tag, function): assert tag == 'test with spaces' """) testdir.makefile('.feature', test=""" Feature: Tag with spaces @test with spaces Scenario: Tags Given I have a bar """) testdir.makepyfile(""" from pytest_bdd import given, scenarios @given('I have a bar') def i_have_bar(): return 'bar' scenarios('test.feature') """) result = testdir.runpytest_subprocess() result.stdout.fnmatch_lines( [ "*= 1 passed * =*", ], ) def test_at_in_scenario(testdir): testdir.makefile('.feature', test=""" Feature: At sign in a scenario Scenario: Tags Given I have a foo@bar Scenario: Second Given I have a baz """) testdir.makepyfile(""" from pytest_bdd import given, scenarios @given('I have a foo@bar') def i_have_at(): return 'foo@bar' @given('I have a baz') def i_have_baz(): return 'baz' scenarios('test.feature') """) result = testdir.runpytest_subprocess('--strict') result.stdout.fnmatch_lines( [ "*= 2 passed * =*", ], ) @pytest.mark.parametrize('line, expected', [ ('@foo @bar', {'foo', 'bar'}), ('@with spaces @bar', {'with spaces', 'bar'}), ('@double @double', {'double'}), (' @indented', {'indented'}), (None, set()), ('foobar', set()), ('', set()), ]) def test_get_tags(line, expected): assert feature.get_tags(line) == expected pytest-bdd-3.2.1/tests/feature/test_wrong.py000066400000000000000000000055641352722444400211570ustar00rootroot00000000000000"""Test wrong feature syntax.""" import os.path import re import sys import mock import pytest from pytest_bdd import scenario, scenarios, given, when, then from pytest_bdd.feature import features from pytest_bdd import exceptions @given('something') def given_something(): pass @when('something else') def when_something_else(): pass @then('nevermind') def then_nevermind(): pass @pytest.mark.parametrize( ('feature', 'scenario_name'), [ ('when_in_background.feature', 'When in background'), ('when_after_then.feature', 'When after then'), ('then_first.feature', 'Then first'), ('given_after_when.feature', 'Given after When'), ('given_after_then.feature', 'Given after Then'), ] ) @pytest.mark.parametrize('strict_gherkin', [True, False]) @pytest.mark.parametrize('multiple', [True, False]) def test_wrong(request, feature, scenario_name, strict_gherkin, multiple): """Test wrong feature scenarios.""" def declare_scenario(): if multiple: scenarios(feature, strict_gherkin=strict_gherkin) else: @scenario(feature, scenario_name, strict_gherkin=strict_gherkin) def test_scenario(): pass if strict_gherkin: with pytest.raises(exceptions.FeatureError): declare_scenario() # TODO: assert the exception args from parameters else: declare_scenario() def clean_cache(): features.clear() request.addfinalizer(clean_cache) @pytest.mark.parametrize( 'scenario_name', [ 'When in Given', 'When in Then', 'Then in Given', 'Given in When', 'Given in Then', 'Then in When', ] ) def test_wrong_type_order(request, scenario_name): """Test wrong step type order.""" @scenario('wrong_type_order.feature', scenario_name) def test_wrong_type_order(request): pass with pytest.raises(exceptions.StepDefinitionNotFoundError) as excinfo: test_wrong_type_order(request) assert re.match(r'Step definition is not found: (.+)', excinfo.value.args[0]) def test_verbose_output(): """Test verbose output of failed feature scenario.""" with pytest.raises(exceptions.FeatureError) as excinfo: scenario('when_after_then.feature', 'When after then') msg, line_number, line, file = excinfo.value.args assert line_number == 5 assert line == 'When I do it again' assert file == os.path.join(os.path.dirname(__file__), 'when_after_then.feature') assert line in str(excinfo.value) def test_multiple_features_single_file(): """Test validation error when multiple features are placed in a single file.""" with pytest.raises(exceptions.FeatureError) as excinfo: scenarios('wrong_multiple_features.feature') assert excinfo.value.args[0] == 'Multiple features are not allowed in a single feature file' pytest-bdd-3.2.1/tests/feature/then_first.feature000066400000000000000000000000511352722444400221160ustar00rootroot00000000000000Scenario: Then first Then it won't work pytest-bdd-3.2.1/tests/feature/when_after_then.feature000066400000000000000000000002111352722444400231070ustar00rootroot00000000000000Scenario: When after then Given I don't always write when after then, but When I do Then its fine When I do it again Then its wrong pytest-bdd-3.2.1/tests/feature/when_in_background.feature000066400000000000000000000003301352722444400235770ustar00rootroot00000000000000Feature: When in background Background: Given I don't always write when after then, but When I do Scenario: When in background Then its fine When I do it again Then its wrong pytest-bdd-3.2.1/tests/feature/wrong_multiple_features.feature000066400000000000000000000007171352722444400247270ustar00rootroot00000000000000Feature: Feature One Background: Given I have A And I have B Scenario: Do something with A When I do something with A Then something about B Feature: Feature Two Background: Given I have A Scenario: Something that just needs A When I do something else with A Then something else about B Scenario: Something that needs B again Given I have B When I do something else with B Then something else about A and B pytest-bdd-3.2.1/tests/feature/wrong_type_order.feature000066400000000000000000000004561352722444400233520ustar00rootroot00000000000000Scenario: When in Given Given something else Scenario: When in Then When something else Then something else Scenario: Then in Given Given nevermind Scenario: Given in When When something Scenario: Given in Then When something else Then something Scenario: Then in When When nevermind pytest-bdd-3.2.1/tests/generation/000077500000000000000000000000001352722444400171005ustar00rootroot00000000000000pytest-bdd-3.2.1/tests/generation/__init__.py000066400000000000000000000000001352722444400211770ustar00rootroot00000000000000pytest-bdd-3.2.1/tests/generation/generation.feature000066400000000000000000000006431352722444400226130ustar00rootroot00000000000000Feature: Missing code generation Background: Given I have a foobar Scenario: Scenario tests which are already bound to the tests stay as is Given I have a bar Scenario: Code is generated for scenarios which are not bound to any tests Given I have a bar Scenario: Code is generated for scenario steps which are not yet defined(implemented) Given I have a custom bar pytest-bdd-3.2.1/tests/generation/test_generate_missing.py000066400000000000000000000036321352722444400240400ustar00rootroot00000000000000"""Code generation and assertion tests.""" import itertools import os.path import textwrap from pytest_bdd.scenario import get_python_name_generator def test_python_name_generator(): """Test python name generator function.""" itertools.islice(get_python_name_generator('Some name'), 2) == ['some_name', 'some_name_2'] def test_generate_missing(testdir): """Test generate missing command.""" dirname = "test_generate_missing" tests = testdir.mkpydir(dirname) with open(os.path.join(os.path.dirname(__file__), "generation.feature")) as fd: tests.join('generation.feature').write(fd.read()) tests.join("test_foo.py").write(textwrap.dedent(""" import functools from pytest_bdd import scenario, given scenario = functools.partial(scenario, "generation.feature") @given("I have a bar") def i_have_a_bar(): return "bar" @scenario("Scenario tests which are already bound to the tests stay as is") def test_foo(): pass @scenario("Code is generated for scenario steps which are not yet defined(implemented)") def test_missing_steps(): pass """)) result = testdir.runpytest(dirname, "--generate-missing", "--feature", tests.join('generation.feature').strpath) result.stdout.fnmatch_lines([ 'Scenario "Code is generated for scenarios which are not bound to any tests" is not bound to any test *'] ) result.stdout.fnmatch_lines( [ 'Step Given "I have a custom bar" is not defined in the scenario ' '"Code is generated for scenario steps which are not yet defined(implemented)" *', ] ) result.stdout.fnmatch_lines( ['Step Given "I have a foobar" is not defined in the background of the feature "Missing code generation" *'] ) result.stdout.fnmatch_lines(["Please place the code above to the test file(s):"]) pytest-bdd-3.2.1/tests/library/000077500000000000000000000000001352722444400164115ustar00rootroot00000000000000pytest-bdd-3.2.1/tests/library/__init__.py000066400000000000000000000000001352722444400205100ustar00rootroot00000000000000pytest-bdd-3.2.1/tests/library/child/000077500000000000000000000000001352722444400174745ustar00rootroot00000000000000pytest-bdd-3.2.1/tests/library/child/__init__.py000066400000000000000000000000001352722444400215730ustar00rootroot00000000000000pytest-bdd-3.2.1/tests/library/child/conftest.py000066400000000000000000000001541352722444400216730ustar00rootroot00000000000000from pytest_bdd import given @given('I have the overriden fixture') def overridable(): return 'child' pytest-bdd-3.2.1/tests/library/child/test_local_override.py000066400000000000000000000020251352722444400240750ustar00rootroot00000000000000"""Test givens declared in the parent conftest and plugin files. Check the parent given steps are collected, override them locally. """ from pytest_bdd import given from pytest_bdd.steps import get_step_fixture_name, GIVEN @given('I have locally overriden fixture') def overridable(): return 'local' @given('I have locally overriden parent fixture') def parent(): return 'local' def test_override(request, overridable): """Test locally overriden fixture.""" # Test the fixture is also collected by the text name fixture = request.getfixturevalue(get_step_fixture_name('I have locally overriden fixture', GIVEN)) assert fixture(request) == 'local' # 'I have the overriden fixture' stands for overridable and is overriden locally fixture = request.getfixturevalue(get_step_fixture_name('I have the overriden fixture', GIVEN)) assert fixture(request) == 'local' assert overridable == 'local' def test_parent(parent): """Test locally overriden parent fixture.""" assert parent == 'local' pytest-bdd-3.2.1/tests/library/child/test_parent_override.py000066400000000000000000000005621352722444400243000ustar00rootroot00000000000000"""Test givens declared in the parent conftest and plugin files. Check the parent givens are collected and overriden in the local conftest. """ def test_parent(parent): """Test parent given is collected.""" assert parent == 'parent' def test_override(overridable): """Test the child conftest overriding the fixture.""" assert overridable == 'child' pytest-bdd-3.2.1/tests/library/conftest.py000066400000000000000000000002661352722444400206140ustar00rootroot00000000000000from pytest_bdd import given @given('I have parent fixture') def parent(): return 'parent' @given('I have overridable parent fixture') def overridable(): return 'parent' pytest-bdd-3.2.1/tests/library/test_parent.py000066400000000000000000000011321352722444400213100ustar00rootroot00000000000000"""Test givens declared in the parent conftest and plugin files. Check the parent givens are collected and overriden in the local conftest. """ from pytest_bdd.steps import get_step_fixture_name, WHEN def test_parent(parent, overridable): """Test parent given is collected. Both fixtures come from the parent conftest. """ assert parent == 'parent' assert overridable == 'parent' def test_global_when_step(request): """Test when step defined in the parent conftest.""" request.getfixturevalue(get_step_fixture_name('I use a when step from the parent conftest', WHEN)) pytest-bdd-3.2.1/tests/scripts/000077500000000000000000000000001352722444400164345ustar00rootroot00000000000000pytest-bdd-3.2.1/tests/scripts/__init__.py000066400000000000000000000000001352722444400205330ustar00rootroot00000000000000pytest-bdd-3.2.1/tests/scripts/generate.feature000066400000000000000000000004261352722444400216050ustar00rootroot00000000000000Feature: Code generation Scenario: Given and when using the same fixture should not evaluate it twice Given I have an empty list And 1 have a fixture (appends 1 to a list) in reuse syntax When I use this fixture Then my list should be [1] pytest-bdd-3.2.1/tests/scripts/test_generate.py000066400000000000000000000030061352722444400216360ustar00rootroot00000000000000"""Test code generation command.""" import os import sys import textwrap from pytest_bdd.scripts import main PATH = os.path.dirname(__file__) def test_generate(monkeypatch, capsys): """Test if the code is generated by a given feature.""" monkeypatch.setattr(sys, 'argv', ['', 'generate', os.path.join(PATH, 'generate.feature')]) main() out, err = capsys.readouterr() assert out == textwrap.dedent(''' # coding=utf-8 """Code generation feature tests.""" from pytest_bdd import ( given, scenario, then, when, ) @scenario('scripts/generate.feature', 'Given and when using the same fixture should not evaluate it twice') def test_given_and_when_using_the_same_fixture_should_not_evaluate_it_twice(): """Given and when using the same fixture should not evaluate it twice.""" @given('1 have a fixture (appends 1 to a list) in reuse syntax') def have_a_fixture_appends_1_to_a_list_in_reuse_syntax(): """1 have a fixture (appends 1 to a list) in reuse syntax.""" raise NotImplementedError @given('I have an empty list') def i_have_an_empty_list(): """I have an empty list.""" raise NotImplementedError @when('I use this fixture') def i_use_this_fixture(): """I use this fixture.""" raise NotImplementedError @then('my list should be [1]') def my_list_should_be_1(): """my list should be [1].""" raise NotImplementedError '''[1:].replace(u"'", u"'")) pytest-bdd-3.2.1/tests/scripts/test_main.py000066400000000000000000000007141352722444400207730ustar00rootroot00000000000000"""Main command.""" import os import sys from pytest_bdd.scripts import main PATH = os.path.dirname(__file__) def test_main(monkeypatch, capsys): """Test if main commmand shows help when called without the subcommand.""" monkeypatch.setattr(sys, 'argv', ['pytest-bdd']) monkeypatch.setattr(sys, 'exit', lambda x: x) main() out, err = capsys.readouterr() assert 'usage: pytest-bdd [-h]' in err assert 'pytest-bdd: error:' in err pytest-bdd-3.2.1/tests/scripts/test_migrate.py000066400000000000000000000017671352722444400215100ustar00rootroot00000000000000"""Test code generation command.""" import os import sys import textwrap from pytest_bdd.scripts import main PATH = os.path.dirname(__file__) def test_migrate(monkeypatch, capsys, testdir): """Test if the code is migrated by a given file mask.""" tests = testdir.mkpydir('tests') tests.join("test_foo.py").write(textwrap.dedent(''' """Foo bar tests.""" from pytest_bdd import scenario test_foo = scenario('foo_bar.feature', 'Foo bar') ''')) monkeypatch.setattr(sys, 'argv', ['', 'migrate', tests.strpath]) main() out, err = capsys.readouterr() out = '\n'.join(sorted(out.splitlines())) expected = textwrap.dedent(''' migrated: {0}/test_foo.py skipped: {0}/__init__.py'''.format(tests.strpath)[1:]) assert out == expected assert tests.join("test_foo.py").read() == textwrap.dedent(''' """Foo bar tests.""" from pytest_bdd import scenario @scenario('foo_bar.feature', 'Foo bar') def test_foo(): pass ''') pytest-bdd-3.2.1/tests/steps/000077500000000000000000000000001352722444400161035ustar00rootroot00000000000000pytest-bdd-3.2.1/tests/steps/__init__.py000066400000000000000000000000001352722444400202020ustar00rootroot00000000000000pytest-bdd-3.2.1/tests/steps/given.feature000066400000000000000000000006411352722444400205710ustar00rootroot00000000000000Scenario: Test reusing root fixture Given I have an alias to the root fixture Then root should be "root" Scenario: Test reusing local fixture Given I have alias for foo Then foo should be "foo" Scenario: Test session given Given I have session foo Then session foo should be "session foo" Scenario: Test given fixture injection Given I have injecting given Then foo should be "injected foo" pytest-bdd-3.2.1/tests/steps/test_given.py000066400000000000000000000026501352722444400206270ustar00rootroot00000000000000"""Given tests.""" import pytest from pytest_bdd import given, then, scenario from pytest_bdd.steps import StepError @given("I have foo") def foo(): return "foo" given("I have alias for foo", fixture="foo") given("I have an alias to the root fixture", fixture="root") @given("I have session foo", scope='session') def session_foo(): return "session foo" @scenario('given.feature', 'Test reusing local fixture') def test_given_with_fixture(): pass @scenario('given.feature', 'Test reusing root fixture') def test_root_alias(): pass @scenario('given.feature', 'Test session given') def test_session_given(): pass @scenario('given.feature', 'Test given fixture injection') def test_given_injection(): pass @given("I have injecting given", target_fixture='foo') def injecting_given(): return "injected foo" @then('foo should be "injected foo"') def foo_is_foo(foo): assert foo == 'injected foo' @then('foo should be "foo"') def foo_is_foo(foo): assert foo == 'foo' @then('session foo should be "session foo"') def session_foo_is_foo(session_foo): assert session_foo == 'session foo' @then('root should be "root"') def root_is_root(root): assert root == 'root' def test_decorate_with_fixture(): """Test given can't be used as decorator when the fixture is specified.""" with pytest.raises(StepError): @given('Foo', fixture='foo') def bla(): pass pytest-bdd-3.2.1/tests/steps/test_steps.py000066400000000000000000000021521352722444400206520ustar00rootroot00000000000000"""Test when and then steps are callables.""" import pytest from pytest_bdd import given, when, then from pytest_bdd.steps import get_step_fixture_name, WHEN, THEN @when('I do stuff') def do_stuff(): pass @then('I check stuff') def check_stuff(): pass def test_when_then(request): """Test when and then steps are callable functions. This test checks that when and then are not evaluated during fixture collection that might break the scenario. """ do_stuff_ = request.getfixturevalue(get_step_fixture_name('I do stuff', WHEN)) assert callable(do_stuff_) check_stuff_ = request.getfixturevalue(get_step_fixture_name('I check stuff', THEN)) assert callable(check_stuff_) @pytest.mark.parametrize( ('step', 'keyword'), [ (given, 'Given'), (when, 'When'), (then, 'Then')]) def test_preserve_decorator(step, keyword): """Check that we preserve original function attributes after decorating it.""" @step(keyword) def func(): """Doc string.""" assert globals()[get_step_fixture_name(keyword, step.__name__)].__doc__ == 'Doc string.' pytest-bdd-3.2.1/tests/steps/test_unicode.py000066400000000000000000000033321352722444400211430ustar00rootroot00000000000000# coding: utf-8 """Tests for testing cases when we have unicode in feature file.""" import sys import pytest import functools from pytest_bdd import ( given, parsers, scenario, then, ) scenario = functools.partial(scenario, 'unicode.feature') @scenario('Кроки в .feature файлі містять юнікод') def test_steps_in_feature_file_have_unicode(): pass @scenario(u'Steps in .py file have unicode') def test_steps_in_py_file_have_unicode(): pass pattern = r"(?P'\w+')" @pytest.fixture def string(): """String fixture.""" return {'content': ''} given(u"I have an alias with a unicode type for foo", fixture="foo") @given(parsers.parse(u"у мене є рядок який містить '{content}'")) def there_is_a_string_with_content(content, string): """Create string with unicode content.""" string['content'] = content @given("there is an other string with content 'якийсь контент'") def there_is_an_other_string_with_content(string): """Create other string with unicode content.""" string['content'] = u"с каким-то контентом" @then("I should see that the other string equals to content 'якийсь контент'") def assert_that_the_other_string_equals_to_content(string): """Assert that the other string equals to content.""" assert string['content'] == u"с каким-то контентом" @then(parsers.parse("I should see that the string equals to content '{content}'")) def assert_that_the_string_equals_to_content(content, string): """Assert that the string equals to content.""" assert string['content'] == content if sys.version_info < (3, 0): assert isinstance(content, unicode) pytest-bdd-3.2.1/tests/steps/unicode.feature000066400000000000000000000012271352722444400211100ustar00rootroot00000000000000Feature: Юнікодні символи Scenario: Кроки в .feature файлі містять юнікод Given у мене є рядок який містить 'якийсь контент' Then I should see that the string equals to content 'якийсь контент' Scenario: Steps in .py file have unicode Given there is an other string with content 'якийсь контент' Then I should see that the other string equals to content 'якийсь контент' Scenario: Given names have unicode types Given I have an alias with a unicode type for foo Then foo should be "foo" pytest-bdd-3.2.1/tests/test_hooks.py000066400000000000000000000030041352722444400174760ustar00rootroot00000000000000import textwrap def test_hooks(testdir): testdir.makeconftest("") subdir = testdir.mkpydir("subdir") subdir.join("conftest.py").write(textwrap.dedent(r""" def pytest_pyfunc_call(pyfuncitem): print('\npytest_pyfunc_call hook') def pytest_generate_tests(metafunc): print('\npytest_generate_tests hook') """)) subdir.join("test_foo.py").write(textwrap.dedent(r""" from pytest_bdd import scenario @scenario('foo.feature', 'Some scenario') def test_foo(): pass """)) subdir.join("foo.feature").write(textwrap.dedent(r""" Feature: The feature Scenario: Some scenario """)) result = testdir.runpytest("-s") assert result.stdout.lines.count('pytest_pyfunc_call hook') == 1 assert result.stdout.lines.count('pytest_generate_tests hook') == 1 def test_item_collection_does_not_break_on_non_function_items(testdir): """Regression test for https://github.com/pytest-dev/pytest-bdd/issues/317""" testdir.makeconftest(""" import pytest @pytest.mark.tryfirst def pytest_collection_modifyitems(session, config, items): items[:] = [CustomItem(name=item.name, parent=item.parent) for item in items] class CustomItem(pytest.Item): def runtest(self): assert True """) testdir.makepyfile(""" def test_convert_me_to_custom_item_and_assert_true(): assert False """) result = testdir.runpytest() result.assert_outcomes(passed=1) pytest-bdd-3.2.1/tests/utils.py000066400000000000000000000011431352722444400164560ustar00rootroot00000000000000import os def get_test_filepath(filepath): curr_file_dirpath = os.path.dirname(os.path.realpath(__file__)) return os.path.join(curr_file_dirpath, filepath) def prepare_feature_and_py_files(testdir, feature_file, py_file): feature_filepath = get_test_filepath(feature_file) with open(feature_filepath) as feature_file: feature_content = feature_file.read() testdir.makefile('.feature', unicode=feature_content) py_filepath = get_test_filepath(py_file) with open(py_filepath) as py_file: py_content = py_file.read() testdir.makepyfile(test_gherkin=py_content) pytest-bdd-3.2.1/tox.ini000066400000000000000000000024311352722444400151160ustar00rootroot00000000000000[tox] distshare = {homedir}/.tox/distshare envlist = py27-pytestlatest-linters, py27-pytest{36,37,38,39,310,4,41,42,43,44,45,46}, py37-pytest{36,37,38,39,310,4,41,42,43,44,45,46,5,latest}, py{35,36,38}-pytestlatest, py27-pytestlatest-xdist skip_missing_interpreters = true [testenv] deps = pytestlatest: pytest pytest5: pytest~=5.0.0 pytest46: pytest~=4.6.0 pytest45: pytest~=4.5.0 pytest44: pytest~=4.4.0 pytest43: pytest~=4.3.0 pytest42: pytest~=4.2.0 pytest41: pytest~=4.1.0 pytest4: pytest~=4.0.0 pytest310: pytest~=3.10.0 pytest39: pytest~=3.9.0 pytest38: pytest~=3.8.0 pytest37: pytest~=3.7.0 pytest36: pytest~=3.6.0 -r{toxinidir}/requirements-testing.txt commands = py.test tests --junitxml={envlogdir}/junit-{envname}.xml [testenv:py27-pytestlatest-linters] commands = pycodestyle pytest_bdd tests [testenv:py27-pytestlatest-xdist] deps = pytest-xdist {[testenv]deps} commands = {[testenv]commands} -n3 -rfsxX [testenv:py27-pytestlatest-coveralls] passenv = TRAVIS TRAVIS_JOB_ID TRAVIS_BRANCH usedevelop = True changedir = . deps = {[testenv]deps} coveralls commands = coverage run --branch --source=pytest_bdd {envbindir}/pytest tests coverage report -m coveralls