├── test ├── __init__.py ├── reporters │ └── __init__.py ├── test_log_capture.py ├── test_configuration.py ├── test_formatter_progress.py ├── test_formatter_tags.py ├── test_ansi_escapes.py ├── test_tag_expression.py ├── test_step_registry.py └── test_formatter_rerun.py ├── VERSION.txt ├── behave ├── formatter │ ├── __init__.py │ ├── null.py │ ├── filters.py │ ├── ansi_escapes.py │ └── formatters.py ├── reporter │ ├── __init__.py │ ├── base.py │ └── summary.py ├── compat │ ├── __init__.py │ ├── collections.py │ ├── os_path.py │ └── importlib.py ├── __init__.py ├── textutil.py ├── tag_expression.py └── importer.py ├── features ├── feature.description.feature ├── steps │ ├── use_steplib_behave4cmd.py │ ├── context_steps.py │ └── behave_select_files_steps.py ├── README.txt ├── environment.py ├── runner.unknown_formatter.feature ├── context.local_params.feature ├── formatter.help.feature ├── step.use_step_library.feature ├── context.global_params.feature ├── step.execute_steps.feature ├── runner.select_files_by_regexp.example.feature ├── directory_layout.basic.feature ├── step_dialect.given_when_then.feature ├── step.execute_steps.with_table.feature ├── runner.select_files_by_regexp.feature ├── directory_layout.basic2.feature ├── scenario_outline.basics.feature ├── step.import_other_step_module.feature └── step.duplicated_step.feature ├── docs ├── requirements.txt ├── _static │ ├── behave_logo.png │ ├── behave_logo1.png │ ├── behave_logo2.png │ └── behave_logo3.png ├── _themes │ ├── kr │ │ ├── theme.conf │ │ ├── relations.html │ │ ├── layout.html │ │ └── static │ │ │ └── small_flask.css │ ├── kr_small │ │ ├── theme.conf │ │ └── layout.html │ └── LICENSE ├── appendix.rst ├── related.rst ├── install.rst ├── test_domains.rst ├── index.rst ├── behave.rst-template ├── update_behave_rst.py ├── parse_builtin_types.rst ├── formatters.rst └── context_attributes.rst ├── setup.cfg ├── behave4cmd0 ├── __init__.py ├── __all_steps__.py ├── __setup.py ├── passing_steps.py ├── note_steps.py └── failing_steps.py ├── requirements ├── README.txt ├── docs.txt ├── more_py25.txt ├── json.txt ├── optional.txt ├── all.txt ├── develop.txt └── basic.txt ├── more.features ├── steps │ └── use_steplib_behave4cmd.py └── formatter.json.validate_output.feature ├── issue.features ├── steps │ ├── use_steplib_behave4cmd.py │ └── behave_hooks_steps.py ├── requirements.txt ├── README.txt ├── issue0186.feature ├── issue0065.feature ├── issue0031.feature ├── issue0030.feature ├── issue0171.feature ├── issue0075.feature ├── issue0059.feature ├── issue0032.feature ├── issue0072.feature ├── issue0142.feature ├── issue0080.feature ├── issue0044.feature ├── issue0125.feature ├── issue0111.feature ├── issue0172.feature ├── issue0188.feature ├── issue0052.feature ├── issue0152.feature ├── issue0159.feature ├── issue0116.feature ├── issue0139.feature ├── issue0069.feature ├── issue0145.feature ├── issue0092.feature ├── issue0127.feature ├── issue0083.feature ├── issue0109.feature ├── issue0112.feature ├── issue0046.feature ├── issue0084.feature ├── issue0162.feature ├── issue0035.feature ├── issue0066.feature ├── issue0064.feature ├── issue0067.feature ├── issue0077.feature └── issue0148.feature ├── tools ├── test-features │ ├── environment.py │ ├── parse.feature │ ├── background.feature │ ├── french.feature │ ├── tags.feature │ ├── outline.feature │ └── step-data.feature └── convert_i18n_yaml.py ├── .gitignore ├── bin ├── behave.cmd └── behave ├── .travis.yml ├── behave.ini ├── MANIFEST.in ├── PROJECT_INFO.rst ├── .coveragerc ├── LICENSE └── setup.py /test/__init__.py: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /VERSION.txt: -------------------------------------------------------------------------------- 1 | 1.2.4a1 2 | -------------------------------------------------------------------------------- /behave/formatter/__init__.py: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /behave/reporter/__init__.py: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /test/reporters/__init__.py: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /features/feature.description.feature: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /docs/requirements.txt: -------------------------------------------------------------------------------- 1 | sphinxcontrib-cheeseshop 2 | -------------------------------------------------------------------------------- /setup.cfg: -------------------------------------------------------------------------------- 1 | [upload_docs] 2 | upload-dir = build/docs/html 3 | -------------------------------------------------------------------------------- /docs/_static/behave_logo.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ramiro/behave/master/docs/_static/behave_logo.png -------------------------------------------------------------------------------- /docs/_static/behave_logo1.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ramiro/behave/master/docs/_static/behave_logo1.png -------------------------------------------------------------------------------- /docs/_static/behave_logo2.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ramiro/behave/master/docs/_static/behave_logo2.png -------------------------------------------------------------------------------- /docs/_static/behave_logo3.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ramiro/behave/master/docs/_static/behave_logo3.png -------------------------------------------------------------------------------- /behave4cmd0/__init__.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | """ 3 | Predecessor of behave4cmd library. 4 | Currently used to provide self-tests for behave. 5 | """ 6 | -------------------------------------------------------------------------------- /requirements/README.txt: -------------------------------------------------------------------------------- 1 | This directory contains python package requirements for behave. 2 | These requirement files are used by: 3 | 4 | * pip 5 | * tox 6 | -------------------------------------------------------------------------------- /behave/compat/__init__.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | """ 3 | Used for behave as compatibility layer between different Python versions 4 | and implementations. 5 | """ -------------------------------------------------------------------------------- /docs/_themes/kr/theme.conf: -------------------------------------------------------------------------------- 1 | [theme] 2 | inherit = basic 3 | stylesheet = flasky.css 4 | pygments_style = flask_theme_support.FlaskyStyle 5 | 6 | [options] 7 | touch_icon = 8 | -------------------------------------------------------------------------------- /more.features/steps/use_steplib_behave4cmd.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | """ 3 | Use behave4cmd0 step library (predecessor of behave4cmd). 4 | """ 5 | 6 | # -- REGISTER-STEPS: 7 | import behave4cmd0.__all_steps__ 8 | -------------------------------------------------------------------------------- /issue.features/steps/use_steplib_behave4cmd.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | """ 3 | Use behave4cmd0 step library (predecessor of behave4cmd). 4 | """ 5 | 6 | # -- REGISTER-STEPS: 7 | import behave4cmd0.command_steps 8 | -------------------------------------------------------------------------------- /tools/test-features/environment.py: -------------------------------------------------------------------------------- 1 | 2 | def before_all(context): 3 | context.testing_stuff = False 4 | context.stuff_set_up = False 5 | 6 | def before_feature(context, feature): 7 | context.is_spammy = 'spam' in feature.tags 8 | 9 | -------------------------------------------------------------------------------- /docs/_themes/kr_small/theme.conf: -------------------------------------------------------------------------------- 1 | [theme] 2 | inherit = basic 3 | stylesheet = flasky.css 4 | nosidebar = true 5 | pygments_style = flask_theme_support.FlaskyStyle 6 | 7 | [options] 8 | index_logo = '' 9 | index_logo_height = 120px 10 | github_fork = '' 11 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | *.egg-info 2 | *.log 3 | *.pyc 4 | *.pyo 5 | *$py.class 6 | build/ 7 | dist/ 8 | __pycache__/ 9 | __WORKDIR__/ 10 | _build/ 11 | _WORKSPACE/ 12 | .cache/ 13 | .idea/ 14 | .tox/ 15 | .DS_Store 16 | .coverage 17 | .ropeproject 18 | nosetests.xml 19 | tools/virtualenvs 20 | -------------------------------------------------------------------------------- /features/steps/use_steplib_behave4cmd.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | """ 3 | Use behave4cmd0 step library (predecessor of behave4cmd). 4 | """ 5 | 6 | # -- REGISTER-STEPS: 7 | import behave4cmd0.__all_steps__ 8 | import behave4cmd0.passing_steps 9 | import behave4cmd0.failing_steps 10 | -------------------------------------------------------------------------------- /requirements/docs.txt: -------------------------------------------------------------------------------- 1 | # ============================================================================ 2 | # BEHAVE: PYTHON PACKAGE REQUIREMENTS: For documentation generation 3 | # ============================================================================ 4 | 5 | sphinx >= 1.1 6 | sphinxcontrib-cheeseshop >= 0.2 7 | -------------------------------------------------------------------------------- /tools/test-features/parse.feature: -------------------------------------------------------------------------------- 1 | Feature: parse stuff out of steps 2 | 3 | Scenario: basic parsing 4 | Given a string with an argument 5 | Then we get "with" parsed 6 | 7 | Scenario: custom type parsing 8 | Given a string with a custom type 9 | Then we get "WITH" parsed 10 | 11 | -------------------------------------------------------------------------------- /behave4cmd0/__all_steps__.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | """ 3 | Import all step definitions of this step-library. 4 | Step definitions are automatically registered in "behave.step_registry". 5 | """ 6 | 7 | # -- IMPORT STEP-LIBRARY: behave4cmd0 8 | import behave4cmd0.command_steps 9 | import behave4cmd0.note_steps 10 | -------------------------------------------------------------------------------- /tools/test-features/background.feature: -------------------------------------------------------------------------------- 1 | Feature: features may have backgrounds 2 | 3 | Background: some background stuff to run 4 | Given I am testing stuff 5 | and some stuff is set up 6 | 7 | Scenario: the stuff should be set up 8 | Given stuff has been set up 9 | Then it will work 10 | -------------------------------------------------------------------------------- /bin/behave.cmd: -------------------------------------------------------------------------------- 1 | @echo off 2 | REM ========================================================================== 3 | REM BEHAVE 4 | REM ========================================================================== 5 | 6 | setlocal 7 | set HERE=%~dp0 8 | if not defined PYTHON set PYTHON=python 9 | 10 | %PYTHON% %HERE%behave %* 11 | -------------------------------------------------------------------------------- /issue.features/steps/behave_hooks_steps.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | from behave import then 3 | 4 | @then('the behave hook "{hook}" was called') 5 | def step_behave_hook_was_called(context, hook): 6 | substeps = u'Then the command output should contain "hooks.{0}: "'.format(hook) 7 | context.execute_steps(substeps) 8 | 9 | -------------------------------------------------------------------------------- /tools/test-features/french.feature: -------------------------------------------------------------------------------- 1 | # language: fr 2 | Fonctionnalité: testing stuff 3 | 4 | Scénario: test stuff 5 | Etant donné I am testing stuff 6 | Quand I exercise it work 7 | Alors it will work 8 | 9 | Scénario: test more stuff 10 | Etant donné I am testing stuff 11 | Alors it will work 12 | -------------------------------------------------------------------------------- /requirements/more_py25.txt: -------------------------------------------------------------------------------- 1 | # ============================================================================ 2 | # BEHAVE: PYTHON PACKAGE REQUIREMENTS: More packages for Python2.5 3 | # ============================================================================ 4 | # Python2.5: json module was introduced in python2.6 5 | 6 | simplejson 7 | -------------------------------------------------------------------------------- /requirements/json.txt: -------------------------------------------------------------------------------- 1 | # ============================================================================ 2 | # PYTHON PACKAGE REQUIREMENTS FOR: behave -- For development only 3 | # ============================================================================ 4 | 5 | # -- OPTIONAL: For JSON validation 6 | # REQUIRES: python >= 2.6 7 | jsonschema >= 1.3.0 8 | -------------------------------------------------------------------------------- /behave4cmd0/__setup.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | 3 | import os.path 4 | 5 | # ----------------------------------------------------------------------------- 6 | # CONSTANTS: 7 | # ----------------------------------------------------------------------------- 8 | HERE = os.path.dirname(__file__) 9 | TOP = os.path.join(HERE, "..") 10 | TOPA = os.path.abspath(TOP) 11 | -------------------------------------------------------------------------------- /behave/formatter/null.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | 3 | from behave.formatter.base import Formatter 4 | 5 | class NullFormatter(Formatter): 6 | """ 7 | Provides formatter that does not output anything. 8 | Implements the NULL pattern for a formatter (similar like: /dev/null). 9 | """ 10 | name = "null" 11 | description = "Provides formatter that does not output anything." 12 | -------------------------------------------------------------------------------- /requirements/optional.txt: -------------------------------------------------------------------------------- 1 | # ============================================================================ 2 | # BEHAVE: PYTHON PACKAGE REQUIREMENTS: Optional packages 3 | # ============================================================================ 4 | # Python2.5: importlib module was introduced in python2.7, python3.2... 5 | # Python2.6: importlib module was introduced in python2.7, python3.2... 6 | 7 | importlib 8 | -------------------------------------------------------------------------------- /docs/appendix.rst: -------------------------------------------------------------------------------- 1 | .. _id.appendix: 2 | 3 | ============================================================================== 4 | Appendix 5 | ============================================================================== 6 | 7 | **Contents:** 8 | 9 | .. toctree:: 10 | :maxdepth: 1 11 | 12 | test_domains 13 | formatters 14 | context_attributes 15 | parse_builtin_types 16 | regular_expressions 17 | related 18 | 19 | -------------------------------------------------------------------------------- /.travis.yml: -------------------------------------------------------------------------------- 1 | language: python 2 | python: 3 | - 2.6 4 | - 2.7 5 | - pypy 6 | 7 | install: 8 | - pip install --use-mirrors -q mock nose PyHamcrest 9 | - python setup.py -q install 10 | script: 11 | - python --version 12 | - nosetests 13 | - behave -f progress --junit --tags=~@xfail features/ 14 | - behave -f progress --junit --tags=~@xfail tools/test-features/ 15 | - behave -f progress --junit --tags=~@xfail issue.features/ 16 | -------------------------------------------------------------------------------- /docs/related.rst: -------------------------------------------------------------------------------- 1 | .. _id.appendix.related: 2 | 3 | =============================== 4 | Software that Enhances *behave* 5 | =============================== 6 | 7 | * Mock 8 | * nose.tools and nose.twistedtools 9 | * mechanize for pretending to be a browser 10 | * selenium webdriver for actually driving a browser 11 | * wsgi_intercept for providing more easily testable WSGI servers 12 | * BeautifulSoup, lxml and html5lib for parsing HTML 13 | * ... 14 | 15 | -------------------------------------------------------------------------------- /issue.features/requirements.txt: -------------------------------------------------------------------------------- 1 | # ============================================================================ 2 | # PYTHON PACKAGE REQUIREMENTS: For running issue.features/ 3 | # ============================================================================ 4 | # REQUIRES: Python >= 2.5 5 | # REQUIRES: Python >= 3.2 6 | # DESCRIPTION: 7 | # pip install -r 8 | # 9 | # ============================================================================ 10 | 11 | PyHamcrest >= 1.6 12 | 13 | -------------------------------------------------------------------------------- /features/README.txt: -------------------------------------------------------------------------------- 1 | behave features: Self-Tests 2 | =============================================================================== 3 | 4 | This directory contains feature tests that are executed with behave. 5 | These self-tests are used to: 6 | 7 | * ensure that ``behave`` reacts as expected 8 | * define the common expected behaviour for behave 9 | * support "Acceptance Test Driven-Design" (ATDD) 10 | 11 | RELATED: 12 | * Cucumber Technology Compatibility Kit: https://github.com/cucumber/cucumber-tck -------------------------------------------------------------------------------- /tools/test-features/tags.feature: -------------------------------------------------------------------------------- 1 | @spam 2 | Feature: handle tags 3 | Tags may be set at various levels in various ways. 4 | 5 | Scenario: nothing changes 6 | Given the tag "spam" is set 7 | 8 | @ham @cram 9 | Scenario: adding the ham 10 | Given the tag "spam" is set 11 | and the tag "ham" is set 12 | and the tag "cram" is set 13 | 14 | @ham 15 | Scenario: adding the ham 16 | Given the tag "spam" is set 17 | and the tag "ham" is set 18 | and the tag "cram" is not set 19 | -------------------------------------------------------------------------------- /issue.features/README.txt: -------------------------------------------------------------------------------- 1 | issue.features: 2 | =============================================================================== 3 | 4 | :Status: PREPARED (fixes are being applied). 5 | :Requires: Python >= 2.6 (due to step implementations) 6 | 7 | This directory contains behave self-tests to ensure that behave related 8 | issues are fixed. 9 | 10 | PROCEDURE: 11 | 12 | * ONCE: Install python requirements ("requirements.txt") 13 | * Run the tests with behave 14 | 15 | :: 16 | 17 | bin/behave -f progress issue.features/ 18 | -------------------------------------------------------------------------------- /requirements/all.txt: -------------------------------------------------------------------------------- 1 | # ============================================================================ 2 | # BEHAVE: PYTHON PACKAGE REQUIREMENTS: All requirements 3 | # ============================================================================ 4 | # DESCRIPTION: 5 | # pip install -r 6 | # 7 | # SEE ALSO: 8 | # * http://www.pip-installer.org/ 9 | # ============================================================================ 10 | 11 | -r basic.txt 12 | -r develop.txt 13 | -r docs.txt 14 | -r more_py25.txt 15 | -r json.txt 16 | -------------------------------------------------------------------------------- /requirements/develop.txt: -------------------------------------------------------------------------------- 1 | # ============================================================================ 2 | # PYTHON PACKAGE REQUIREMENTS FOR: behave -- For development only 3 | # ============================================================================ 4 | 5 | # -- TESTING: Unit tests and behave self-tests. 6 | # PREPARED-FUTURE: behave4cmd0, behave4cmd 7 | nose >= 1.1 8 | mock >= 1.0 9 | PyHamcrest >= 1.6 10 | 11 | # -- DEVELOPMENT SUPPORT: 12 | # PREPARED: nose-cov >= 1.4 13 | # PREPARED: paver >= 1.2 14 | tox >= 1.6.0 15 | coverage >= 3.6 16 | -------------------------------------------------------------------------------- /behave.ini: -------------------------------------------------------------------------------- 1 | # ============================================================================= 2 | # BEHAVE CONFIGURATION 3 | # ============================================================================= 4 | # FILE: .behaverc, behave.ini 5 | # 6 | # SEE ALSO: 7 | # * http://packages.python.org/behave/behave.html#configuration-files 8 | # * https://github.com/behave/behave 9 | # * http://pypi.python.org/pypi/behave/ 10 | # ============================================================================= 11 | 12 | [behave] 13 | show_skipped = false 14 | format = rerun 15 | outfiles = rerun.featureset 16 | -------------------------------------------------------------------------------- /requirements/basic.txt: -------------------------------------------------------------------------------- 1 | # ============================================================================ 2 | # BEHAVE: PYTHON PACKAGE REQUIREMENTS: Normal usage/installation (minimal) 3 | # ============================================================================ 4 | # DESCRIPTION: 5 | # pip install -r 6 | # 7 | # SEE ALSO: 8 | # * http://www.pip-installer.org/ 9 | # ============================================================================ 10 | 11 | argparse >= 1.2 12 | parse >= 1.6.2 13 | 14 | # -- MORE: 15 | # Python2.5: simplejson # -- json module was introduced in python2.6 16 | -------------------------------------------------------------------------------- /issue.features/issue0186.feature: -------------------------------------------------------------------------------- 1 | @issue 2 | Feature: Issue #186 ScenarioOutline uses wrong return value when if fails 3 | 4 | ScenarioOutline returns encountered a failure only if the last scenario failed. 5 | Failures in earlier examples return the wrong result. 6 | Ensure that ScenarioOutline run-logic behaves as expected. 7 | 8 | Scenario: Reuse existing test 9 | Given I use the current directory as working directory 10 | When I run "behave -f plain features/scenario_outline.basics.feature" 11 | Then it should pass with: 12 | """ 13 | 1 feature passed, 0 failed, 0 skipped 14 | """ 15 | -------------------------------------------------------------------------------- /issue.features/issue0065.feature: -------------------------------------------------------------------------------- 1 | @issue 2 | Feature: Issue #65 Unrecognized --tag-help argument 3 | 4 | The "behave --help" output refers to the option "--tag-help" 5 | in the description of the "--tags" option. 6 | The correct option for more help on tags is "--tags-help". 7 | 8 | Scenario: Ensure environment assumptions are correct (Sanity Check) 9 | Given a new working directory 10 | When I run "behave --help" 11 | Then the command output should contain: 12 | """ 13 | --tags-help 14 | """ 15 | But the command output should not contain: 16 | """ 17 | --tag-help 18 | """ 19 | -------------------------------------------------------------------------------- /features/environment.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | 3 | # ----------------------------------------------------------------------------- 4 | # HOOKS: 5 | # ----------------------------------------------------------------------------- 6 | def before_all(context): 7 | setup_context_with_global_params_test(context) 8 | 9 | # ----------------------------------------------------------------------------- 10 | # SPECIFIC FUNCTIONALITY: 11 | # ----------------------------------------------------------------------------- 12 | def setup_context_with_global_params_test(context): 13 | context.global_name = "env:Alice" 14 | context.global_age = 12 15 | -------------------------------------------------------------------------------- /issue.features/issue0031.feature: -------------------------------------------------------------------------------- 1 | @issue 2 | Feature: Issue #31 "behave --format help" raises an error 3 | 4 | Scenario: 5 | Given a new working directory 6 | When I run "behave --format=help" 7 | Then it should pass 8 | And the command output should contain: 9 | """ 10 | Available formatters: 11 | json JSON dump of test run 12 | json.pretty JSON dump of test run (human readable) 13 | null Provides formatter that does not output anything. 14 | plain Very basic formatter with maximum compatibility 15 | pretty Standard colourised pretty formatter 16 | """ 17 | -------------------------------------------------------------------------------- /docs/_themes/kr/relations.html: -------------------------------------------------------------------------------- 1 |

Related Topics

2 | 20 | -------------------------------------------------------------------------------- /behave/compat/collections.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | """ 3 | Compatibility of :module:`collections` between different Python versions. 4 | """ 5 | 6 | from __future__ import absolute_import 7 | import warnings 8 | 9 | try: 10 | # -- SINCE: Python2.7 11 | from collections import OrderedDict 12 | except ImportError: # pragma: no cover 13 | try: 14 | # -- BACK-PORTED FOR: Python 2.4 .. 2.6 15 | from ordereddict import OrderedDict 16 | except ImportError: 17 | message = "collections.OrderedDict is missing: Install 'ordereddict'." 18 | warnings.warn(message) 19 | # -- BACKWARD-COMPATIBLE: Better than nothing (for behave use case). 20 | OrderedDict = dict 21 | -------------------------------------------------------------------------------- /issue.features/issue0030.feature: -------------------------------------------------------------------------------- 1 | @issue 2 | Feature: Issue #30 "behave --version" runs features and shows no version 3 | 4 | Scenario: Ensure environment assumptions are correct (Sanity Check) 5 | Given a new working directory 6 | When I run "behave" 7 | Then it should fail 8 | And the command output should contain: 9 | """ 10 | No steps directory in "{__WORKDIR__}/features" 11 | """ 12 | 13 | Scenario: Ensure --version option is processed correctly 14 | Given a new working directory 15 | When I run "behave --version" 16 | Then it should pass 17 | And the command output should not contain: 18 | """ 19 | No steps directory in "{__WORKDIR__}/features" 20 | """ 21 | 22 | -------------------------------------------------------------------------------- /features/runner.unknown_formatter.feature: -------------------------------------------------------------------------------- 1 | Feature: When an unknown formatter is used 2 | 3 | 4 | Scenario: Unknown formatter is used 5 | When I run "behave -f unknown1" 6 | Then it should fail with: 7 | """ 8 | behave: error: format=unknown1 is unknown 9 | """ 10 | 11 | Scenario: Unknown formatter is used together with another formatter 12 | When I run "behave -f plain -f unknown1" 13 | Then it should fail with: 14 | """ 15 | behave: error: format=unknown1 is unknown 16 | """ 17 | 18 | Scenario: Two unknown formatters are used 19 | When I run "behave -f plain -f unknown1 -f tags -f unknown2" 20 | Then it should fail with: 21 | """ 22 | behave: error: format=unknown1, unknown2 is unknown 23 | """ 24 | -------------------------------------------------------------------------------- /issue.features/issue0171.feature: -------------------------------------------------------------------------------- 1 | @issue 2 | Feature: Issue #171: Importing step from other step file fails with AmbiguousStep Error 3 | 4 | | When a step module imports another step module 5 | | this should not cause AmbiguousStep errors 6 | | due to duplicated registration of the same step functions. 7 | | 8 | | NOTES: 9 | | * In general you should avoid this case (provided as example here). 10 | 11 | 12 | Scenario: Step module imports other step module 13 | 14 | Reuse existing, co-located feature test. 15 | 16 | Given I use the current directory as working directory 17 | When I run "behave -f plain features/step.import_other_step_module.feature" 18 | Then it should pass with: 19 | """ 20 | 2 scenarios passed, 0 failed, 0 skipped 21 | """ 22 | -------------------------------------------------------------------------------- /docs/_themes/kr_small/layout.html: -------------------------------------------------------------------------------- 1 | {% extends "basic/layout.html" %} 2 | {% block header %} 3 | {{ super() }} 4 | {% if pagename == 'index' %} 5 |
6 | {% endif %} 7 | {% endblock %} 8 | {% block footer %} 9 | {% if pagename == 'index' %} 10 |
11 | {% endif %} 12 | {% endblock %} 13 | {# do not display relbars #} 14 | {% block relbar1 %}{% endblock %} 15 | {% block relbar2 %} 16 | {% if theme_github_fork %} 17 | Fork me on GitHub 19 | {% endif %} 20 | {% endblock %} 21 | {% block sidebar1 %}{% endblock %} 22 | {% block sidebar2 %}{% endblock %} 23 | -------------------------------------------------------------------------------- /issue.features/issue0075.feature: -------------------------------------------------------------------------------- 1 | @issue 2 | Feature: Issue #75: behave @features_from_text_file does not work 3 | 4 | | Feature of Cucumber. Reading the source code, I see it partly implemented. 5 | | 6 | | $ behave @list_of_features.txt 7 | | https://github.com/jeamland/behave/blob/master/behave/runner.py#L416:L430 8 | | 9 | | However it fails because: 10 | | * it does not remove the @ from the path 11 | | * it does not search the steps/ directory in the parents of the feature files themselves 12 | 13 | 14 | Scenario: Reuse relocated tests 15 | Given I use the current directory as working directory 16 | When I run "behave -f plain features/runner.feature_listfile.feature" 17 | Then it should pass with: 18 | """ 19 | 13 scenarios passed, 0 failed, 0 skipped 20 | """ 21 | -------------------------------------------------------------------------------- /MANIFEST.in: -------------------------------------------------------------------------------- 1 | include README.rst 2 | include CHANGES.rst 3 | include MANIFEST.in 4 | include LICENSE 5 | include .coveragerc 6 | include .pycheckrc 7 | include pylintrc 8 | include *.cfg 9 | include *.ini 10 | include *.rst 11 | include *.txt 12 | include bin/behave* 13 | recursive-include bin *.py *.cmd *.zip 14 | recursive-include behave4cmd0 *.py 15 | recursive-include etc *.xsd *.txt *.json-schema 16 | recursive-include docs *.rst *.txt *.css *.py *.html *.rst-* *.png Makefile 17 | recursive-include docs/_themes *.html *.conf *.css *.css_t LICENSE* 18 | recursive-include test *.py 19 | recursive-include tools *.feature *.py *.yml *.sh 20 | recursive-include features *.feature *.py *.txt 21 | recursive-include issue.features *.feature *.py *.txt 22 | recursive-include requirements *.txt 23 | prune .tox 24 | -------------------------------------------------------------------------------- /docs/install.rst: -------------------------------------------------------------------------------- 1 | Installation 2 | ============ 3 | 4 | .. pypi-release:: behave 5 | :prefix: Download 6 | :class: note 7 | 8 | Installing *behave* is a relatively simple process if you have `pip`_ 9 | installed: 10 | 11 | pip install behave 12 | 13 | If you do not have `pip`_ installed then you might have easy_install 14 | instead. In that case just substitute "easy_install" for "pip" in the above 15 | command. 16 | 17 | If you have neither then we highly recommend you get `pip`_. 18 | 19 | If you are unwilling or unable to get `pip`_ then you may install *behave* 20 | manually by following the download link above and grabbing the source 21 | bundle. Once you have that you need to unpack it. Then, in the directory 22 | created by the unpacking, run: 23 | 24 | python setup.py install 25 | 26 | .. _`pip`: http://www.pip-installer.org/en/latest/installing.html 27 | -------------------------------------------------------------------------------- /tools/convert_i18n_yaml.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python 2 | 3 | import pprint 4 | import sys 5 | 6 | import yaml 7 | 8 | # 9 | # usage: convert_i18n_yaml.py i18n.yml ../behave/i18n.py 10 | # 11 | languages = yaml.load(open(sys.argv[1])) 12 | 13 | for language in languages: 14 | keywords = languages[language] 15 | for k in keywords: 16 | v = keywords[k] 17 | # bloody YAML parser returns a mixture of unicode and str 18 | if not isinstance(v, unicode): 19 | v = v.decode('utf8') 20 | keywords[k] = v.split('|') 21 | 22 | content = '''#-*- encoding: UTF-8 -*- 23 | 24 | # file generated by convert_i18n_yaml.py from i18n.yaml 25 | 26 | languages = \\ 27 | ''' 28 | 29 | i18n_py = open(sys.argv[2], 'w') 30 | i18n_py.write(content.encode('UTF-8')) 31 | i18n_py.write(pprint.pformat(languages).encode('UTF-8')) 32 | i18n_py.write(u'\n') 33 | i18n_py.close() 34 | -------------------------------------------------------------------------------- /behave/__init__.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | '''behave is behaviour-driven development, Python style 3 | 4 | Behavior-driven development (or BDD) is an agile software development 5 | technique that encourages collaboration between developers, QA and 6 | non-technical or business participants in a software project. 7 | 8 | *behave* uses tests written in a natural language style, backed up by Python 9 | code. 10 | 11 | To get started, we recommend the `tutorial`_ and then the `test language`_ and 12 | `api`_ references. 13 | 14 | .. _`tutorial`: tutorial.html 15 | .. _`test language`: gherkin.html 16 | .. _`api`: api.html 17 | ''' 18 | 19 | __version__ = '1.2.4a1' 20 | 21 | from behave.step_registry import * 22 | from behave.matchers import step_matcher 23 | 24 | names = 'given when then step' 25 | names = names + ' ' + names.title() 26 | names = names + ' step_matcher' 27 | __all__ = names.split() 28 | -------------------------------------------------------------------------------- /issue.features/issue0059.feature: -------------------------------------------------------------------------------- 1 | @issue 2 | Feature: Issue #59 Fatal error when using --format=json 3 | 4 | Using the JSON formatter caused a fatal error. 5 | 6 | Background: Test Setup 7 | Given a new working directory 8 | And a file named "features/steps/steps.py" with: 9 | """ 10 | from behave import given 11 | 12 | @given(u'passing') 13 | def step(context): 14 | pass 15 | """ 16 | And a file named "features/issue59_test.feature" with: 17 | """ 18 | Feature: Passing tagged Scenario 19 | Scenario: P1 20 | Given passing 21 | """ 22 | 23 | Scenario: Use the JSONFormatter 24 | When I run "behave --format=json features/issue59_test.feature" 25 | Then it should pass with: 26 | """ 27 | 1 feature passed, 0 failed, 0 skipped 28 | 1 scenario passed, 0 failed, 0 skipped 29 | """ 30 | -------------------------------------------------------------------------------- /features/context.local_params.feature: -------------------------------------------------------------------------------- 1 | Feature: Local Context Parameters defined in Scenarios (Steps) 2 | 3 | | Specification: 4 | | * When a step adds/modifies an attribute in the Context object, 5 | | then its value is only available to other steps in this scenario. 6 | | * After a scenario is executed all Context object changes are undone. 7 | 8 | Scenario: Add Local Context parameter in Scenario/Step 9 | Given the behave context does not have a parameter "local_name" 10 | When I set the context parameter "local_name" to "Alice" 11 | Then the behave context should have a parameter "local_name" 12 | And the behave context should contain: 13 | | Parameter | Value | 14 | | local_name | "Alice" | 15 | 16 | Scenario: Ensure that Local Context parameter is not available to next Scenario 17 | Then the behave context should not have a parameter "local_name" 18 | -------------------------------------------------------------------------------- /issue.features/issue0032.feature: -------------------------------------------------------------------------------- 1 | @issue 2 | Feature: Issue #32 "behave --junit-directory=xxx" fails if more than 1 level must be created 3 | 4 | Scenario: 5 | Given a new working directory 6 | And a file named "features/steps/steps.py" with: 7 | """ 8 | from behave import given, when, then 9 | 10 | @given(u'passing') 11 | def step(context): 12 | pass 13 | """ 14 | And a file named "features/issue32_1.feature" with: 15 | """ 16 | Feature: One 17 | Scenario: One 18 | Given passing 19 | """ 20 | 21 | When I run "behave --junit --junit-directory=report/test_results" 22 | Then it should pass with: 23 | """ 24 | 1 feature passed, 0 failed, 0 skipped 25 | 1 scenario passed, 0 failed, 0 skipped 26 | 1 step passed, 0 failed, 0 skipped, 0 undefined 27 | """ 28 | And the directory "report/test_results" should exist 29 | -------------------------------------------------------------------------------- /behave4cmd0/passing_steps.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | """ 3 | Passing steps. 4 | Often needed in examples. 5 | 6 | EXAMPLES: 7 | 8 | Given a step passes 9 | When another step passes 10 | Then a step passes 11 | 12 | Given ... 13 | When ... 14 | Then it should pass because "the answer is correct". 15 | """ 16 | 17 | from behave import step, then 18 | 19 | # ----------------------------------------------------------------------------- 20 | # STEPS FOR: passing 21 | # ----------------------------------------------------------------------------- 22 | @step('{word:w} step passes') 23 | def step_passes(context, word): 24 | """ 25 | Step that always fails, mostly needed in examples. 26 | """ 27 | pass 28 | 29 | @then('it should pass because "{reason}"') 30 | def then_it_should_pass_because(context, reason): 31 | """ 32 | Self documenting step that indicates some reason. 33 | """ 34 | pass 35 | 36 | -------------------------------------------------------------------------------- /docs/_themes/kr/layout.html: -------------------------------------------------------------------------------- 1 | {%- extends "basic/layout.html" %} 2 | {%- block extrahead %} 3 | {{ super() }} 4 | {% if theme_touch_icon %} 5 | 6 | {% endif %} 7 | 8 | {% endblock %} 9 | {%- block relbar2 %}{% endblock %} 10 | {%- block footer %} 11 | 14 | 15 | Fork me on GitHub 16 | 17 | {%- endblock %} 18 | -------------------------------------------------------------------------------- /PROJECT_INFO.rst: -------------------------------------------------------------------------------- 1 | Project Info 2 | =============================================================================== 3 | 4 | =============== =============================================================== 5 | Category Hyperlink 6 | =============== =============================================================== 7 | Documentation: http://pythonhosted.org/behave/ 8 | (or: https://behave.readthedocs.org/) 9 | Download: http://pypi.python.org/pypi/behave (or: `github archive`_) 10 | Development: https://github.com/behave/behave 11 | Issue Tracker: https://github.com/behave/behave/issues 12 | Changelog: `CHANGES.rst `_ 13 | =============== =============================================================== 14 | 15 | .. hint:: 16 | 17 | The PyPI version is the latest stable version. 18 | Otherwise, use the latest stable tag or the "bleeding edge" from Github. 19 | 20 | .. _`github archive`: https://github.com/behave/behave/tags 21 | -------------------------------------------------------------------------------- /behave4cmd0/note_steps.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | """ 3 | Step definitions for providing notes/hints. 4 | The note steps explain what was important in the last few steps of 5 | this scenario (for a test reader). 6 | """ 7 | 8 | from behave import step 9 | 10 | 11 | # ----------------------------------------------------------------------------- 12 | # STEPS FOR: remarks/comments 13 | # ----------------------------------------------------------------------------- 14 | @step(u'note that "{remark}"') 15 | def step_note_that(context, remark): 16 | """ 17 | Used as generic step that provides an additional remark/hint 18 | and enhance the readability/understanding without performing any check. 19 | 20 | .. code-block:: gherkin 21 | 22 | Given that today is "April 1st" 23 | But note that "April 1st is Fools day (and beware)" 24 | """ 25 | log = getattr(context, "log", None) 26 | if log: 27 | log.info(u"NOTE: %s;" % remark) 28 | 29 | -------------------------------------------------------------------------------- /test/test_log_capture.py: -------------------------------------------------------------------------------- 1 | from __future__ import with_statement 2 | 3 | from nose.tools import * 4 | from mock import patch 5 | 6 | from behave.log_capture import LoggingCapture 7 | 8 | class TestLogCapture(object): 9 | def test_get_value_returns_all_log_records(self): 10 | class FakeConfig(object): 11 | logging_filter = None 12 | logging_format = None 13 | logging_datefmt = None 14 | logging_level = None 15 | 16 | fake_records = [object() for x in range(0, 10)] 17 | 18 | handler = LoggingCapture(FakeConfig()) 19 | handler.buffer = fake_records 20 | 21 | with patch.object(handler.formatter, 'format') as format: 22 | format.return_value = 'foo' 23 | expected = '\n'.join(['foo'] * len(fake_records)) 24 | 25 | eq_(handler.getvalue(), expected) 26 | 27 | calls = [args[0][0] for args in format.call_args_list] 28 | eq_(calls, fake_records) 29 | -------------------------------------------------------------------------------- /behave/compat/os_path.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | """ 3 | Compatibility of :module:`os.path` between different Python versions. 4 | """ 5 | 6 | import os.path 7 | 8 | relpath = getattr(os.path, "relpath", None) 9 | if relpath is None: # pragma: no cover 10 | # -- Python2.5 doesn't know about relpath 11 | def relpath(path, start=os.path.curdir): 12 | """ 13 | Return a relative version of a path 14 | BASED-ON: Python2.7 15 | """ 16 | if not path: 17 | raise ValueError("no path specified") 18 | 19 | start_list = [x for x in os.path.abspath(start).split(os.path.sep) if x] 20 | path_list = [x for x in os.path.abspath(path).split(os.path.sep) if x] 21 | # Work out how much of the filepath is shared by start and path. 22 | i = len(os.path.commonprefix([start_list, path_list])) 23 | 24 | rel_list = [os.path.pardir] * (len(start_list)-i) + path_list[i:] 25 | if not rel_list: 26 | return os.path.curdir 27 | return os.path.join(*rel_list) 28 | -------------------------------------------------------------------------------- /docs/test_domains.rst: -------------------------------------------------------------------------------- 1 | .. _id.appendix.test_domain: 2 | 3 | Testing Domains 4 | ============================================================================== 5 | 6 | Behave and other BDD frameworks allow you to provide **step libraries** 7 | to reuse step definitions in similar projects that address the same 8 | problem domain. 9 | 10 | Support of the following testing domains is currently known: 11 | 12 | =============== ================= ========================================================= 13 | Testing Domain Name Description 14 | =============== ================= ========================================================= 15 | Command-line `behave4cmd`_ Test command-line tools, like behave, etc. (coming soon). 16 | Web Apps `django-behave`_ Test Django Web apps with behave. 17 | Web, SMS, ... `behaving`_ Test Web Apps, Email, SMS, Personas (step library). 18 | =============== ================= ========================================================= 19 | 20 | .. _behave4cmd: https://github.com/jenisys/behave4cmd 21 | .. _django-behave: https://github.com/rwillmer/django-behave 22 | .. _behaving: https://github.com/ggozad/behaving 23 | -------------------------------------------------------------------------------- /.coveragerc: -------------------------------------------------------------------------------- 1 | # ========================================================================= 2 | # COVERAGE CONFIGURATION FILE: .coveragerc 3 | # ========================================================================= 4 | # LANGUAGE: Python 5 | # SEE ALSO: 6 | # * http://nedbatchelder.com/code/coverage/ 7 | # * http://nedbatchelder.com/code/coverage/config.html 8 | # ========================================================================= 9 | 10 | [run] 11 | append = .coverage 12 | include = behave* 13 | branch = True 14 | parallel = True 15 | 16 | [report] 17 | # Regexes for lines to exclude from consideration 18 | exclude_lines = 19 | # Have to re-enable the standard pragma 20 | pragma: no cover 21 | 22 | # Don't complain about missing debug-only code: 23 | def __repr__ 24 | if self\.debug 25 | 26 | # Don't complain if tests don't hit defensive assertion code: 27 | raise AssertionError 28 | raise NotImplementedError 29 | 30 | # Don't complain if non-runnable code isn't run: 31 | if 0: 32 | if False: 33 | if __name__ == .__main__.: 34 | 35 | ignore_errors = True 36 | 37 | [html] 38 | directory = build/coverage.html 39 | 40 | [xml] 41 | outfile = build/coverage.xml 42 | 43 | -------------------------------------------------------------------------------- /behave4cmd0/failing_steps.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | """ 3 | Generic failing steps. 4 | Often needed in examples. 5 | 6 | EXAMPLES: 7 | 8 | Given a step fails 9 | When another step fails 10 | Then a step fails 11 | 12 | Given ... 13 | When ... 14 | Then it should fail because "the person is unknown". 15 | """ 16 | 17 | from behave import step, then 18 | 19 | # ----------------------------------------------------------------------------- 20 | # STEPS FOR: failing 21 | # ----------------------------------------------------------------------------- 22 | @step('{word:w} step fails') 23 | def step_fails(context, word): 24 | """ 25 | Step that always fails, mostly needed in examples. 26 | """ 27 | assert False, "EXPECT: Failing step" 28 | 29 | @then(u'it should fail because "{reason}"') 30 | def then_it_should_fail_because(context, reason): 31 | """ 32 | Self documenting step that indicates why this step should fail. 33 | """ 34 | assert False, "FAILED: %s" % reason 35 | 36 | # @step(u'an error should fail because "{reason}"') 37 | # def then_it_should_fail_because(context, reason): 38 | # """ 39 | # Self documenting step that indicates why this step should fail. 40 | # """ 41 | # assert False, reason -------------------------------------------------------------------------------- /issue.features/issue0072.feature: -------------------------------------------------------------------------------- 1 | @issue 2 | Feature: Issue #72: Using 'GHERKIN_COLORS' fails with Exception 3 | 4 | > Trying: GHERKIN_COLORS=executing=white behave 5 | > It fails: 6 | > 7 | > Traceback (most recent call last): 8 | > ... 9 | > File "/.../behave/formatter/ansi_escapes.py", line 38, in 10 | > escapes[alias] = ''.join([colors[c] for c in aliases[alias].split(',')]) 11 | > TypeError: list indices must be integers, not str 12 | > 13 | > The reason is that the global variable colors is defined twice in this module. 14 | > The second variable overrides/modifies the first (without intention). 15 | 16 | 17 | Scenario: Using GHERKIN_COLORS in new working dir 18 | Given a new working directory 19 | And I set the environment variable "GHERKIN_COLORS" to "executing=white" 20 | When I run "behave" 21 | Then it should fail with: 22 | """ 23 | No steps directory in "{__WORKDIR__}/features" 24 | """ 25 | But the command output should not contain: 26 | """ 27 | Traceback (most recent call last): 28 | """ 29 | And the command output should not contain: 30 | """ 31 | TypeError: list indices must be integers, not str 32 | """ 33 | -------------------------------------------------------------------------------- /issue.features/issue0142.feature: -------------------------------------------------------------------------------- 1 | @issue 2 | @not_reproducible 3 | Feature: Issue #142: --junit flag fails to output with step table data: TypeError: is not JSON serializable 4 | 5 | DUPLICATES: issue #67 (already fixed). 6 | 7 | Scenario: 8 | Given a new working directory 9 | And a file named "features/steps/steps.py" with: 10 | """ 11 | from behave import given, when, then, step 12 | 13 | @then('use table data with') 14 | def step_impl(context): 15 | pass 16 | """ 17 | And a file named "features/issue0142_example.feature" with: 18 | """ 19 | Feature: 20 | Scenario: Use a table 21 | Then use table data with: 22 | | data | value | 23 | | behave outputs junit with tables | false | 24 | """ 25 | When I run "behave --junit -f json features/issue0142_example.feature" 26 | Then it should pass 27 | But the command output should not contain: 28 | """ 29 | TypeError: is not JSON serializable 30 | """ 31 | And the command output should not contain: 32 | """ 33 | Traceback (most recent call last): 34 | """ 35 | 36 | 37 | 38 | -------------------------------------------------------------------------------- /test/test_configuration.py: -------------------------------------------------------------------------------- 1 | from __future__ import with_statement 2 | import os.path 3 | import tempfile 4 | 5 | from nose.tools import * 6 | from behave import configuration 7 | 8 | # one entry of each kind handled 9 | TEST_CONFIG='''[behave] 10 | outfiles= /absolute/path1 11 | relative/path2 12 | paths = /absolute/path3 13 | relative/path4 14 | tags = @foo,~@bar 15 | @zap 16 | format=pretty 17 | tag-counter 18 | stdout_capture=no 19 | bogus=spam 20 | ''' 21 | 22 | class TestConfiguration(object): 23 | 24 | def test_read_file(self): 25 | tn = tempfile.mktemp() 26 | tndir = os.path.dirname(tn) 27 | with open(tn, 'w') as f: 28 | f.write(TEST_CONFIG) 29 | 30 | d = configuration.read_configuration(tn) 31 | eq_(d['outfiles'], [ 32 | os.path.normpath('/absolute/path1'), 33 | os.path.normpath(os.path.join(tndir, 'relative/path2')), 34 | ]) 35 | eq_(d['paths'], [ 36 | os.path.normpath('/absolute/path3'), # -- WINDOWS-REQUIRES: normpath 37 | os.path.normpath(os.path.join(tndir, 'relative/path4')), 38 | ]) 39 | eq_(d['format'], ['pretty', 'tag-counter']) 40 | eq_(d['tags'], ['@foo,~@bar', '@zap']) 41 | eq_(d['stdout_capture'], False) 42 | ok_('bogus' not in d) 43 | 44 | -------------------------------------------------------------------------------- /behave/textutil.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | """ 3 | Provides some utility functions related to text processing. 4 | """ 5 | 6 | 7 | def make_indentation(indent_size, part=u" "): 8 | """ 9 | Creates an indentation prefix string of the given size. 10 | """ 11 | return indent_size * part 12 | 13 | 14 | def indent(text, prefix): 15 | """ 16 | Indent text or a number of text lines (with newline). 17 | 18 | :param lines: Text lines to indent (as string or list of strings). 19 | :param prefix: Line prefix to use (as string). 20 | :return: Indented text (as unicode string). 21 | """ 22 | lines = text 23 | newline = u"" 24 | if isinstance(text, basestring): 25 | lines = text.splitlines(True) 26 | elif lines and not lines[0].endswith("\n"): 27 | # -- TEXT LINES: Without trailing new-line. 28 | newline = u"\n" 29 | return newline.join([prefix + unicode(line) for line in lines]) 30 | 31 | 32 | def compute_words_maxsize(words): 33 | """ 34 | Compute the maximum word size from a list of words (or strings). 35 | 36 | :param words: List of words (or strings) to use. 37 | :return: Maximum size of all words. 38 | """ 39 | max_size = 0 40 | for word in words: 41 | if len(word) > max_size: 42 | max_size = len(word) 43 | return max_size 44 | -------------------------------------------------------------------------------- /tools/test-features/outline.feature: -------------------------------------------------------------------------------- 1 | Feature: support scenario outlines 2 | 3 | Scenario Outline: run scenarios with one example table 4 | Given Some text 5 | When we add some text 6 | Then we should get the 7 | 8 | Examples: some simple examples 9 | | prefix | suffix | combination | 10 | | go | ogle | google | 11 | | onomat | opoeia | onomatopoeia | 12 | | comb | ination | combination | 13 | 14 | Scenario Outline: run scenarios with examples 15 | Given Some text 16 | When we add some text 17 | Then we should get the 18 | 19 | Examples: some simple examples 20 | | prefix | suffix | combination | 21 | | go | ogle | google | 22 | | onomat | opoeia | onomatopoeia | 23 | | comb | ination | combination | 24 | 25 | Examples: some other examples 26 | | prefix | suffix | combination | 27 | | 1 | 2 | 12 | 28 | | one | two | onetwo | 29 | 30 | @xfail 31 | Scenario Outline: scenarios that reference invalid subs 32 | Given Some text 33 | When we add try to use a reference 34 | Then it won't work 35 | 36 | Examples: some simple examples 37 | | prefix | 38 | | go | 39 | 40 | -------------------------------------------------------------------------------- /more.features/formatter.json.validate_output.feature: -------------------------------------------------------------------------------- 1 | @slow 2 | Feature: Validate JSON Formatter Output 3 | 4 | To ensure that the JSON formatter generates valid JSON output 5 | As a tester 6 | I want that the JSON output is validated against its JSON schema. 7 | 8 | 9 | Scenario: Validate JSON output from features/ test run 10 | Given I use the current directory as working directory 11 | When I run "behave -f json -o testrun1.json features/" 12 | And I run "bin/jsonschema_validate.py testrun1.json" 13 | Then it should pass with: 14 | """ 15 | validate: testrun1.json ... OK 16 | """ 17 | 18 | Scenario: Validate JSON output from issue.features/ test run 19 | Given I use the current directory as working directory 20 | When I run "behave -f json -o testrun2.json issue.features/" 21 | And I run "bin/jsonschema_validate.py testrun2.json" 22 | Then it should pass with: 23 | """ 24 | validate: testrun2.json ... OK 25 | """ 26 | 27 | Scenario: Validate JSON output from tools/test-features/ test run 28 | Given I use the current directory as working directory 29 | When I run "behave -f json -o testrun3.json tools/test-features/" 30 | And I run "bin/jsonschema_validate.py testrun3.json" 31 | Then it should pass with: 32 | """ 33 | validate: testrun3.json ... OK 34 | """ -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | Copyright (c) 2012-2013 Benno Rice, Richard Jones, Jens Engel and others, except where noted. 2 | All rights reserved. 3 | 4 | Redistribution and use in source and binary forms, with or without 5 | modification, are permitted provided that the following conditions 6 | are met: 7 | 8 | 1. Redistributions of source code must retain the above copyright 9 | notice, this list of conditions and the following disclaimer. 10 | 2. Redistributions in binary form must reproduce the above copyright 11 | notice, this list of conditions and the following disclaimer in the 12 | documentation and/or other materials provided with the distribution. 13 | 14 | THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR 15 | IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES 16 | OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. 17 | IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT, 18 | INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT 19 | NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, 20 | DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY 21 | THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT 22 | (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF 23 | THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. 24 | -------------------------------------------------------------------------------- /behave/formatter/filters.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | # FIXME: 3 | __status__ = "DEAD, BROKEN" 4 | 5 | from gherkin.tag_expression import TagExpression 6 | 7 | 8 | class LineFilter(object): 9 | def __init__(self, lines): 10 | self.lines = lines 11 | 12 | def eval(self, tags, names, ranges): 13 | for r in ranges: 14 | for line in self.lines: 15 | if r[0] <= line <= r[1]: 16 | return True 17 | return False 18 | 19 | def filter_table_body_rows(self, rows): 20 | body = [r for r in rows[1:] if r.line in self.lines] 21 | return [rows[0]] + body 22 | 23 | 24 | class RegexpFilter(object): 25 | def __init__(self, regexen): 26 | self.regexen = regexen 27 | 28 | def eval(self, tags, names, ranges): 29 | for regex in self.regexen: 30 | for name in names: 31 | if regex.search(name): 32 | return True 33 | return False 34 | 35 | def filter_table_body_rows(self, rows): 36 | return rows 37 | 38 | 39 | class TagFilter(object): 40 | def __init__(self, tags): 41 | self.tag_expression = TagExpression(tags) 42 | 43 | def eval(self, tags, names, ranges): 44 | return self.tag_expression.eval([tag.name for tag in set(tags)]) 45 | 46 | def filter_table_body_rows(self, rows): 47 | return rows 48 | -------------------------------------------------------------------------------- /test/test_formatter_progress.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | """ 3 | Test progress formatters: 4 | * behave.formatter.progress.ScenarioProgressFormatter 5 | * behave.formatter.progress.StepProgressFormatter 6 | """ 7 | 8 | from __future__ import absolute_import 9 | from .test_formatter import FormatterTests as FormatterTest 10 | from .test_formatter import MultipleFormattersTests as MultipleFormattersTest 11 | 12 | class TestScenarioProgressFormatter(FormatterTest): 13 | formatter_name = "progress" 14 | 15 | 16 | class TestStepProgressFormatter(FormatterTest): 17 | formatter_name = "progress2" 18 | 19 | 20 | class TestPrettyAndScenarioProgress(MultipleFormattersTest): 21 | formatters = ['pretty', 'progress'] 22 | 23 | class TestPlainAndScenarioProgress(MultipleFormattersTest): 24 | formatters = ['plain', 'progress'] 25 | 26 | class TestJSONAndScenarioProgress(MultipleFormattersTest): 27 | formatters = ['json', 'progress'] 28 | 29 | class TestPrettyAndStepProgress(MultipleFormattersTest): 30 | formatters = ['pretty', 'progress2'] 31 | 32 | class TestPlainAndStepProgress(MultipleFormattersTest): 33 | formatters = ['plain', 'progress2'] 34 | 35 | class TestJSONAndStepProgress(MultipleFormattersTest): 36 | formatters = ['json', 'progress2'] 37 | 38 | class TestScenarioProgressAndStepProgress(MultipleFormattersTest): 39 | formatters = ['progress', 'progress2'] 40 | -------------------------------------------------------------------------------- /features/formatter.help.feature: -------------------------------------------------------------------------------- 1 | Feature: Help Formatter 2 | 3 | As a tester 4 | I want to know which formatter are supported 5 | To be able to select one. 6 | 7 | Scenario: 8 | Given a new working directory 9 | When I run "behave --format=help" 10 | Then it should pass 11 | And the command output should contain: 12 | """ 13 | Available formatters: 14 | json JSON dump of test run 15 | json.pretty JSON dump of test run (human readable) 16 | null Provides formatter that does not output anything. 17 | plain Very basic formatter with maximum compatibility 18 | pretty Standard colourised pretty formatter 19 | progress Shows dotted progress for each executed scenario. 20 | progress2 Shows dotted progress for each executed step. 21 | progress3 Shows detailed progress for each step of a scenario. 22 | rerun Emits scenario file locations of failing scenarios 23 | sphinx.steps Generate sphinx-based documentation for step definitions. 24 | steps Shows step definitions (step implementations). 25 | steps.doc Shows documentation for step definitions. 26 | steps.usage Shows how step definitions are used by steps. 27 | tags Shows tags (and how often they are used). 28 | tags.location Shows tags and the location where they are used. 29 | """ 30 | -------------------------------------------------------------------------------- /features/step.use_step_library.feature: -------------------------------------------------------------------------------- 1 | Feature: Use a step library 2 | 3 | As a tester 4 | I want to use steps from one or more step libraries 5 | To simplify the reuse of problem domain specific steps in multiple projects. 6 | 7 | 8 | Scenario: Use a simple step library 9 | Given a new working directory 10 | And an empty file named "step_library1/__init__.py" 11 | And a file named "step_library1/alice_steps.py" with: 12 | """ 13 | from behave import step 14 | 15 | @step('I meet Alice') 16 | def step_meet_alice(context): 17 | pass 18 | """ 19 | And a file named "step_library1/bob_steps.py" with: 20 | """ 21 | from behave import step 22 | 23 | @step('I meet Bob') 24 | def step_meet_bob(context): 25 | pass 26 | """ 27 | And a file named "features/steps/use_step_library.py" with: 28 | """ 29 | from step_library1 import alice_steps, bob_steps 30 | """ 31 | And a file named "features/example_use_step_library.feature" with: 32 | """ 33 | Feature: 34 | Scenario: 35 | Given I meet Alice 36 | And I meet Bob 37 | """ 38 | When I run "behave -f plain features/example_use_step_library.feature" 39 | Then it should pass with: 40 | """ 41 | 1 feature passed, 0 failed, 0 skipped 42 | 1 scenario passed, 0 failed, 0 skipped 43 | 2 steps passed, 0 failed, 0 skipped, 0 undefined 44 | """ 45 | -------------------------------------------------------------------------------- /docs/index.rst: -------------------------------------------------------------------------------- 1 | Welcome to behave! 2 | ================== 3 | 4 | .. pypi-release:: behave 5 | :prefix: Download 6 | :class: note 7 | 8 | behave is behaviour-driven development, Python style. 9 | 10 | Behavior-driven development (or BDD) is an agile software development 11 | technique that encourages collaboration between developers, QA and 12 | non-technical or business participants in a software project. We have 13 | a page further describing this `philosophy`_. 14 | 15 | *behave* uses tests written in a natural language style, backed up by Python 16 | code. 17 | 18 | Once you've `installed`_ *behave* we recommend the `tutorial`_ and then the 19 | `test setup`_ and `api`_ references, the page of `related`_ software 20 | (things that you can combine with *behave*) 21 | and finally the information on `how to use 22 | and configure`_ the *behave* tool. 23 | 24 | There is also a `comparison`_ with the other tools available. 25 | 26 | .. _`philosophy`: philosophy.html 27 | .. _`installed`: install.html 28 | .. _`tutorial`: tutorial.html 29 | .. _`test setup`: gherkin.html 30 | .. _`api`: api.html 31 | .. _`how to use and configure`: behave.html 32 | .. _`related`: related.html 33 | .. _`comparison`: comparison.html 34 | 35 | 36 | .. toctree:: 37 | :maxdepth: 2 38 | 39 | install 40 | tutorial 41 | philosophy 42 | gherkin 43 | behave 44 | api 45 | django 46 | comparison 47 | appendix 48 | 49 | 50 | Indices and tables 51 | ================== 52 | 53 | * :ref:`genindex` 54 | * :ref:`modindex` 55 | * :ref:`search` 56 | 57 | -------------------------------------------------------------------------------- /docs/_themes/kr/static/small_flask.css: -------------------------------------------------------------------------------- 1 | /* 2 | * small_flask.css_t 3 | * ~~~~~~~~~~~~~~~~~ 4 | * 5 | * :copyright: Copyright 2010 by Armin Ronacher. 6 | * :license: Flask Design License, see LICENSE for details. 7 | */ 8 | 9 | body { 10 | margin: 0; 11 | padding: 20px 30px; 12 | } 13 | 14 | div.documentwrapper { 15 | float: none; 16 | background: white; 17 | } 18 | 19 | div.sphinxsidebar { 20 | display: block; 21 | float: none; 22 | width: 102.5%; 23 | margin: 50px -30px -20px -30px; 24 | padding: 10px 20px; 25 | background: #333; 26 | color: white; 27 | } 28 | 29 | div.sphinxsidebar h3, div.sphinxsidebar h4, div.sphinxsidebar p, 30 | div.sphinxsidebar h3 a { 31 | color: white; 32 | } 33 | 34 | div.sphinxsidebar a { 35 | color: #aaa; 36 | } 37 | 38 | div.sphinxsidebar p.logo { 39 | display: none; 40 | } 41 | 42 | div.document { 43 | width: 100%; 44 | margin: 0; 45 | } 46 | 47 | div.related { 48 | display: block; 49 | margin: 0; 50 | padding: 10px 0 20px 0; 51 | } 52 | 53 | div.related ul, 54 | div.related ul li { 55 | margin: 0; 56 | padding: 0; 57 | } 58 | 59 | div.footer { 60 | display: none; 61 | } 62 | 63 | div.bodywrapper { 64 | margin: 0; 65 | } 66 | 67 | div.body { 68 | min-height: 0; 69 | padding: 0; 70 | } 71 | 72 | .rtd_doc_footer { 73 | display: none; 74 | } 75 | 76 | .document { 77 | width: auto; 78 | } 79 | 80 | .footer { 81 | width: auto; 82 | } 83 | 84 | .footer { 85 | width: auto; 86 | } 87 | 88 | .github { 89 | display: none; 90 | } -------------------------------------------------------------------------------- /behave/reporter/base.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | 3 | class Reporter(object): 4 | """ 5 | Base class for all reporters. 6 | A reporter provides an extension point (variant point) for the runner logic. 7 | A reporter is called after a model element is processed 8 | (and its result status is known). 9 | Otherwise, a reporter is similar to a formatter, but it has a simpler API. 10 | 11 | Processing Logic (simplified):: 12 | 13 | config.reporters = ... #< Configuration (and provision). 14 | runner.run(): 15 | for feature in runner.features: 16 | feature.run() # And feature scenarios, too. 17 | for reporter in config.reporters: 18 | reporter.feature(feature) 19 | # -- FINALLY: 20 | for reporter in config.reporters: 21 | reporter.end() 22 | 23 | An existing formatter can be reused as reporter by using 24 | :class:`behave.report.formatter_reporter.FormatterAsReporter`. 25 | """ 26 | def __init__(self, config): 27 | self.config = config 28 | 29 | def feature(self, feature): 30 | """ 31 | Called after a feature was processed. 32 | 33 | :param feature: Feature object (as :class:`behave.model.Feature`) 34 | """ 35 | assert feature.status in ("skipped", "passed", "failed") 36 | raise NotImplementedError 37 | 38 | def end(self): 39 | """ 40 | Called after all model elements are processed (optional-hook). 41 | """ 42 | pass 43 | -------------------------------------------------------------------------------- /features/context.global_params.feature: -------------------------------------------------------------------------------- 1 | Feature: Global Context Parameters defined in "environment.py" 2 | 3 | | Specification: 4 | | * When a Context parameter is defined in "environment.py", 5 | | its value is provided to all scenarios (steps). 6 | | * Each scenario has the same global parameters (and values). 7 | | * A scenario (step) may modify global parameters (values). 8 | | * After a scenario is executed all changes to Context parameters are reverted. 9 | 10 | Scenario: Test Setup Description (Example) 11 | Given a file named "features/environment.py" with: 12 | """ 13 | def before_all(context): 14 | context.global_name = "env:Alice" 15 | context.global_age = 12 16 | """ 17 | 18 | Scenario: Modify global Context parameter in Scenario Step 19 | Given the behave context contains: 20 | | Parameter | Value | 21 | | global_name | "env:Alice" | 22 | | global_age | 12 | 23 | When I set the context parameter "global_name" to "step:Bob" 24 | Then the behave context should contain: 25 | | Parameter | Value | 26 | | global_name | "step:Bob" | 27 | | global_age | 12 | 28 | 29 | Scenario: Ensure that Global Context parameter is reset for next Scenario 30 | Then the behave context should have a parameter "global_name" 31 | And the behave context should contain: 32 | | Parameter | Value | 33 | | global_name | "env:Alice" | 34 | | global_age | 12 | 35 | 36 | -------------------------------------------------------------------------------- /bin/behave: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python 2 | 3 | # -- ENSURE: Use local path during development. 4 | import sys 5 | import os.path 6 | 7 | # ---------------------------------------------------------------------------- 8 | # SETUP PATHS: 9 | # ---------------------------------------------------------------------------- 10 | NAME = "behave" 11 | HERE = os.path.dirname(__file__) 12 | TOP = os.path.join(HERE, "..") 13 | if os.path.isdir(os.path.join(TOP, NAME)): 14 | sys.path.insert(0, os.path.abspath(TOP)) 15 | 16 | # ---------------------------------------------------------------------------- 17 | # BEHAVE-TWEAKS: 18 | # ---------------------------------------------------------------------------- 19 | def setup_behave(): 20 | """ 21 | Apply tweaks, extensions and patches to "behave". 22 | """ 23 | from behave.configuration import Configuration 24 | # -- DISABLE: Timings to simplify issue.features/ tests. 25 | # Configuration.defaults["format0"] = "pretty" 26 | # Configuration.defaults["format0"] = "progress" 27 | Configuration.defaults["show_timings"] = False 28 | 29 | def behave_main0(): 30 | # from behave.configuration import Configuration 31 | from behave.__main__ import main as behave_main 32 | setup_behave() 33 | return behave_main() 34 | 35 | # ---------------------------------------------------------------------------- 36 | # MAIN: 37 | # ---------------------------------------------------------------------------- 38 | if __name__ == "__main__": 39 | if os.environ.has_key("COVERAGE_PROCESS_START"): 40 | import coverage 41 | coverage.process_startup() 42 | sys.exit(behave_main0()) 43 | -------------------------------------------------------------------------------- /issue.features/issue0080.feature: -------------------------------------------------------------------------------- 1 | @issue 2 | Feature: Issue #80: source file names not properly printed with python3 3 | 4 | | $ behave -f pretty example/example.feature 5 | | Scenario: run a simple test # example/example.feature:3 6 | | Given we have behave installed # :3 7 | | When we implement a test # :7 8 | | Then behave will test it for us! # :11 9 | 10 | 11 | Background: Test Setup 12 | Given a new working directory 13 | And a file named "features/steps/steps.py" with: 14 | """ 15 | from behave import given, when, then 16 | 17 | @given(u'a step passes') 18 | def step(context): 19 | pass 20 | 21 | @when(u'a step passes') 22 | def step(context): 23 | pass 24 | 25 | @then(u'a step passes') 26 | def step(context): 27 | pass 28 | """ 29 | And a file named "features/basic.feature" with: 30 | """ 31 | Feature: 32 | Scenario: 33 | Given a step passes 34 | When a step passes 35 | Then a step passes 36 | """ 37 | 38 | Scenario: Show step locations 39 | When I run "behave -c -f pretty --no-timings features/basic.feature" 40 | Then it should pass 41 | And the command output should contain: 42 | """ 43 | Feature: # features/basic.feature:1 44 | Scenario: # features/basic.feature:2 45 | Given a step passes # features/steps/steps.py:3 46 | When a step passes # features/steps/steps.py:7 47 | Then a step passes # features/steps/steps.py:11 48 | """ 49 | And the command output should not contain "# :" 50 | -------------------------------------------------------------------------------- /issue.features/issue0044.feature: -------------------------------------------------------------------------------- 1 | @issue 2 | Feature: Issue #44 Shell-like comments are removed in Multiline Args 3 | 4 | As I user 5 | I want that multiline arguments (docstrings) contents are preserved. 6 | 7 | Background: Test Setup 8 | Given a new working directory 9 | And a file named "features/steps/steps.py" with: 10 | """ 11 | from behave import given, when, then 12 | from hamcrest import assert_that, equal_to 13 | 14 | @given(u'a multiline text argument with') 15 | def step(context): 16 | context.expected_text = context.text 17 | 18 | @then(u'the multiline text argument should be') 19 | def step(context): 20 | assert_that(context.text, equal_to(context.expected_text)) 21 | """ 22 | 23 | Scenario: Ensure shell comment lines are not filtered out in multiline text 24 | Given a file named "features/issue44_test.feature" with: 25 | ''' 26 | Feature: Multiline text with shell comment lines 27 | Scenario: 28 | Given a multiline text argument with: 29 | """ 30 | Lorem ipsum. 31 | # THIS IS A SHELL COMMENT. 32 | Ipsum lorem. 33 | """ 34 | Then the multiline text argument should be: 35 | """ 36 | Lorem ipsum. 37 | # THIS IS A SHELL COMMENT. 38 | Ipsum lorem. 39 | """ 40 | ''' 41 | When I run "behave -c -f pretty features/issue44_test.feature" 42 | Then it should pass 43 | And the command output should contain: 44 | """ 45 | # THIS IS A SHELL COMMENT. 46 | """ 47 | But the command output should not contain: 48 | """ 49 | Lorem ipsum. 50 | Ipsum lorem. 51 | """ 52 | -------------------------------------------------------------------------------- /issue.features/issue0125.feature: -------------------------------------------------------------------------------- 1 | @issue 2 | Feature: Issue #125: Duplicate "Captured stdout" if substep has failed 3 | 4 | 5 | Background: Test Setup 6 | Given a new working directory 7 | And a file named "features/steps/steps.py" with: 8 | """ 9 | from behave import step 10 | 11 | @step('a step fails with stdout "{message}"') 12 | def step_fails_with_stdout(context, message): 13 | print(message) 14 | assert False, 'EXPECT: Step fails with stdout.' 15 | 16 | @step('substep fails with stdout "{message}"') 17 | def substep_fails_with_stdout(context, message): 18 | context.execute_steps(u'When a step fails with stdout "%s"' % message) 19 | """ 20 | 21 | Scenario: Subprocess call shows generated output 22 | Given a file named "features/issue125_example.feature" with: 23 | """ 24 | Feature: 25 | Scenario: 26 | When substep fails with stdout "Hello" 27 | """ 28 | When I run "behave -f plain --no-timings features/issue125_example.feature" 29 | Then it should fail with: 30 | """ 31 | 0 scenarios passed, 1 failed, 0 skipped 32 | 0 steps passed, 1 failed, 0 skipped, 0 undefined 33 | """ 34 | And the command output should not contain: 35 | """ 36 | Captured stdout: 37 | Hello 38 | 39 | Captured stdout: 40 | Hello 41 | """ 42 | But the command output should contain: 43 | """ 44 | Feature: 45 | Scenario: 46 | When substep fails with stdout "Hello" ... failed 47 | 48 | Assertion Failed: FAILED SUB-STEP: When a step fails with stdout "Hello" 49 | Substep info: Assertion Failed: EXPECT: Step fails with stdout. 50 | Captured stdout: 51 | Hello 52 | """ 53 | 54 | -------------------------------------------------------------------------------- /behave/compat/importlib.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | """ 3 | importlib was introduced in python2.7, python3.2... 4 | """ 5 | 6 | try: 7 | from importlib import import_module 8 | except ImportError: 9 | """Backport of importlib.import_module from 3.x.""" 10 | # While not critical (and in no way guaranteed!), it would be nice to keep this 11 | # code compatible with Python 2.3. 12 | import sys 13 | 14 | def _resolve_name(name, package, level): 15 | """Return the absolute name of the module to be imported.""" 16 | if not hasattr(package, 'rindex'): 17 | raise ValueError("'package' not set to a string") 18 | dot = len(package) 19 | for x in xrange(level, 1, -1): 20 | try: 21 | dot = package.rindex('.', 0, dot) 22 | except ValueError: 23 | raise ValueError("attempted relative import beyond top-level " 24 | "package") 25 | return "%s.%s" % (package[:dot], name) 26 | 27 | 28 | def import_module(name, package=None): 29 | """Import a module. 30 | 31 | The 'package' argument is required when performing a relative import. It 32 | specifies the package to use as the anchor point from which to resolve the 33 | relative import to an absolute import. 34 | 35 | """ 36 | if name.startswith('.'): 37 | if not package: 38 | raise TypeError("relative imports require the 'package' argument") 39 | level = 0 40 | for character in name: 41 | if character != '.': 42 | break 43 | level += 1 44 | name = _resolve_name(name[level:], package, level) 45 | __import__(name) 46 | return sys.modules[name] 47 | -------------------------------------------------------------------------------- /issue.features/issue0111.feature: -------------------------------------------------------------------------------- 1 | @issue 2 | Feature: Issue #111: Comment following @wip tag results in scenario being ignored 3 | 4 | | If a comment is placed after the @wip tag, the following scenario 5 | | is ignored by behave: 6 | | 7 | | @wip # comment: this is work in progress 8 | | Scenario: test scenario 9 | | 10 | | results in behave -w not running the "test scenario". 11 | | After removing the comment, it runs as expected. 12 | 13 | 14 | Scenario: Test Setup 15 | Given a new working directory 16 | And a file named "features/steps/passing_steps.py" with: 17 | """ 18 | from behave import step 19 | 20 | @step(u'a step passes') 21 | def step_passes(context): 22 | pass 23 | """ 24 | And a file named "features/syndrome111.feature" with: 25 | """ 26 | Feature: 27 | 28 | @wip # Comment: blabla 29 | Scenario: S1 30 | Given a step passes 31 | 32 | @wip @one # Comment: foobar 33 | Scenario: S2 34 | Given a step passes 35 | """ 36 | 37 | Scenario: Scenario w/ comment on tag-line should run as normal 38 | When I run "behave --wip features/syndrome111.feature" 39 | Then it should pass with: 40 | """ 41 | 1 feature passed, 0 failed, 0 skipped 42 | 2 scenarios passed, 0 failed, 0 skipped 43 | 2 steps passed, 0 failed, 0 skipped, 0 undefined 44 | """ 45 | 46 | Scenario: Ensure 2nd scenario can be selected with other tag 47 | When I run "behave -f plain --tags=one features/syndrome111.feature" 48 | Then it should pass with: 49 | """ 50 | 1 feature passed, 0 failed, 0 skipped 51 | 1 scenario passed, 0 failed, 1 skipped 52 | 1 step passed, 0 failed, 1 skipped, 0 undefined 53 | """ 54 | -------------------------------------------------------------------------------- /setup.py: -------------------------------------------------------------------------------- 1 | import sys 2 | 3 | from setuptools import find_packages, setup 4 | 5 | requirements = ['parse>=1.6.2'] 6 | zip_safe = True 7 | major, minor = sys.version_info[:2] 8 | if major == 2 and minor < 7: 9 | requirements.append('argparse') 10 | requirements.append('importlib') 11 | if major == 2 and minor < 6: 12 | requirements.append('simplejson') 13 | 14 | description = ''.join(open('README.rst').readlines()[5:]) 15 | 16 | setup( 17 | name='behave', 18 | version='1.2.4a1', 19 | description='behave is behaviour-driven development, Python style', 20 | long_description=description, 21 | author='Benno Rice, Richard Jones and Jens Engel', 22 | author_email='behave-users@googlegroups.com', 23 | url='http://github.com/behave/behave', 24 | packages=find_packages(exclude=[ 25 | "test", "test.*", 26 | "behave4cmd0", "behave4cmd0.*"]), 27 | entry_points={ 28 | 'console_scripts': ['behave = behave.__main__:main'], 29 | }, 30 | install_requires=requirements, 31 | use_2to3=True, 32 | license="BSD", 33 | classifiers=[ 34 | "Development Status :: 4 - Beta", 35 | "Environment :: Console", 36 | "Intended Audience :: Developers", 37 | "Operating System :: OS Independent", 38 | "Programming Language :: Python :: 2.5", 39 | "Programming Language :: Python :: 2.6", 40 | "Programming Language :: Python :: 2.7", 41 | "Programming Language :: Python :: 3.2", 42 | "Programming Language :: Python :: 3.3", 43 | "Programming Language :: Python :: Implementation :: CPython", 44 | "Programming Language :: Python :: Implementation :: Jython", 45 | "Programming Language :: Python :: Implementation :: PyPy", 46 | "Topic :: Software Development :: Testing", 47 | "License :: OSI Approved :: BSD License", 48 | ], 49 | ) 50 | -------------------------------------------------------------------------------- /issue.features/issue0172.feature: -------------------------------------------------------------------------------- 1 | @issue 2 | Feature: Issue #172 Junit report file name populated incorrectly when running against a feature file 3 | 4 | @setup 5 | Scenario: Feature Setup 6 | Given a new working directory 7 | And a file named "features/steps/steps.py" with: 8 | """ 9 | from behave import step 10 | 11 | @step('a step passes') 12 | def step_passes(context): 13 | pass 14 | """ 15 | And a file named "features/feature_in_root_folder.feature" with: 16 | """ 17 | Feature: 18 | Scenario: 19 | Given a step passes 20 | """ 21 | And a file named "features/subfolder/feature_in_subfolder.feature" with: 22 | """ 23 | Feature: 24 | Scenario: 25 | Given a step passes 26 | """ 27 | 28 | Scenario: Running behave for one feature in root folder 29 | When I run "behave --junit --junit-directory=test_results features/feature_in_root_folder.feature" 30 | Then it should pass with: 31 | """ 32 | 1 feature passed, 0 failed, 0 skipped 33 | """ 34 | And a file named "test_results/TESTS-feature_in_root_folder.xml" exists 35 | 36 | Scenario: Running behave for one feature in a subfolder 37 | When I run "behave --junit --junit-directory=test_results features/subfolder/feature_in_subfolder.feature" 38 | Then it should pass with: 39 | """ 40 | 1 feature passed, 0 failed, 0 skipped 41 | """ 42 | And a file named "test_results/TESTS-subfolder.feature_in_subfolder.xml" exists 43 | 44 | Scenario: Running behave for all features 45 | When I run "behave --junit --junit-directory=test_results" 46 | Then it should pass with: 47 | """ 48 | 2 features passed, 0 failed, 0 skipped 49 | """ 50 | And a file named "test_results/TESTS-feature_in_root_folder.xml" exists 51 | And a file named "test_results/TESTS-subfolder.feature_in_subfolder.xml" exists 52 | -------------------------------------------------------------------------------- /issue.features/issue0188.feature: -------------------------------------------------------------------------------- 1 | @issue 2 | Feature: Better diagnostics if nested step is undefined 3 | 4 | | Currently if nested step has no match, it's shown like this: 5 | | 6 | | Assertion Failed: Sub-step failed: When I do strange thign 7 | | Substep info: None 8 | | 9 | | Took some time to find that typo. 10 | | The suggestion is to fill substep error_message with at least "No match for step" 11 | | so it would become: 12 | | 13 | | Assertion Failed: Sub-step failed: When I do strange thign 14 | | Substep info: No match for step 15 | | 16 | | IMPLEMENTATION NOTE: 17 | | A slightly different output is provided: 18 | | 19 | | Assertion Failed: UNDEFINED SUB-STEP: When I do strange thign 20 | 21 | Scenario: 22 | Given a new working directory 23 | And a file named "features/steps/steps.py" with: 24 | """ 25 | from behave import step 26 | 27 | @step('{word:w} step passes') 28 | def step_passes(context, word): 29 | pass 30 | 31 | @then('a good diagnostic message is shown') 32 | def step_good_diagnostic_message_is_shown(context): 33 | pass 34 | 35 | @step('I execute nested steps with an undefined step') 36 | def step_passes(context): 37 | context.execute_steps(u''' 38 | Given another step passes 39 | When an undefined, nested step is executed 40 | Then third step passes 41 | ''') 42 | """ 43 | And a file named "features/example.execute_nested_undefined_step.feature" with: 44 | """ 45 | Feature: 46 | Scenario: 47 | Given a step passes 48 | When I execute nested steps with an undefined step 49 | Then a good diagnostic message is shown 50 | """ 51 | When I run "behave -f plain -T features/example.execute_nested_undefined_step.feature" 52 | Then it should fail with: 53 | """ 54 | Assertion Failed: UNDEFINED SUB-STEP: When an undefined, nested step is executed 55 | """ 56 | 57 | -------------------------------------------------------------------------------- /docs/_themes/LICENSE: -------------------------------------------------------------------------------- 1 | Modifications: 2 | 3 | Copyright (c) 2011 Kenneth Reitz. 4 | 5 | 6 | Original Project: 7 | 8 | Copyright (c) 2010 by Armin Ronacher. 9 | 10 | 11 | Some rights reserved. 12 | 13 | Redistribution and use in source and binary forms of the theme, with or 14 | without modification, are permitted provided that the following conditions 15 | are met: 16 | 17 | * Redistributions of source code must retain the above copyright 18 | notice, this list of conditions and the following disclaimer. 19 | 20 | * Redistributions in binary form must reproduce the above 21 | copyright notice, this list of conditions and the following 22 | disclaimer in the documentation and/or other materials provided 23 | with the distribution. 24 | 25 | * The names of the contributors may not be used to endorse or 26 | promote products derived from this software without specific 27 | prior written permission. 28 | 29 | We kindly ask you to only use these themes in an unmodified manner just 30 | for Flask and Flask-related products, not for unrelated projects. If you 31 | like the visual style and want to use it for your own projects, please 32 | consider making some larger changes to the themes (such as changing 33 | font faces, sizes, colors or margins). 34 | 35 | THIS THEME IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" 36 | AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE 37 | IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE 38 | ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE 39 | LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR 40 | CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF 41 | SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS 42 | INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN 43 | CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) 44 | ARISING IN ANY WAY OUT OF THE USE OF THIS THEME, EVEN IF ADVISED OF THE 45 | POSSIBILITY OF SUCH DAMAGE. 46 | -------------------------------------------------------------------------------- /issue.features/issue0052.feature: -------------------------------------------------------------------------------- 1 | @issue 2 | Feature: Issue #52 Summary counts are wrong with option --tags 3 | 4 | Wrong summary counts are shown for skipped and failed scenarios 5 | when option --tags=done is used (and some scenarios are skipped). 6 | 7 | 8 | Background: Test Setup 9 | Given a new working directory 10 | Given a file named "features/steps/steps.py" with: 11 | """ 12 | from behave import given 13 | 14 | @given(u'passing') 15 | def step(context): 16 | pass 17 | 18 | @given(u'failing') 19 | def step(context): 20 | assert False, "failing" 21 | """ 22 | 23 | Scenario: Successful Execution of Tagged Scenario 24 | Given a file named "features/tagged_scenario1.feature" with: 25 | """ 26 | Feature: Passing tagged Scenario 27 | @done 28 | Scenario: P1 29 | Given passing 30 | 31 | @unimplemented 32 | Scenario: N1 33 | Given passing 34 | @unimplemented 35 | Scenario: N2 36 | Given passing 37 | """ 38 | When I run "behave --junit -c --tags @done features/tagged_scenario1.feature" 39 | Then it should pass with: 40 | """ 41 | 1 feature passed, 0 failed, 0 skipped 42 | 1 scenario passed, 0 failed, 2 skipped 43 | """ 44 | 45 | Scenario: Failing Execution of Tagged Scenario 46 | Given a file named "features/tagged_scenario2.feature" with: 47 | """ 48 | Feature: Failing tagged Scenario 49 | @done 50 | Scenario: F1 51 | Given failing 52 | 53 | @unimplemented 54 | Scenario: N1 55 | Given passing 56 | @unimplemented 57 | Scenario: N2 58 | Given passing 59 | """ 60 | When I run "behave --junit -c --tags @done features/tagged_scenario2.feature" 61 | Then it should fail 62 | And the command output should contain: 63 | """ 64 | 0 features passed, 1 failed, 0 skipped 65 | 0 scenarios passed, 1 failed, 2 skipped 66 | """ 67 | -------------------------------------------------------------------------------- /issue.features/issue0152.feature: -------------------------------------------------------------------------------- 1 | @issue 2 | Feature: Issue #152: Fix encoding issues 3 | 4 | | I fixed two encoding issues in pretty formatter and in JUnit serialization. 5 | | Now it's possible to use accented letters in feature files and 6 | | create JUnit reports from the tests. 7 | 8 | 9 | Scenario: Ensure JUnit reports can be created from a foreign language 10 | Given a new working directory 11 | And an empty file named "features/steps/steps.py" 12 | And a file named "features/eins.feature" with: 13 | """ 14 | # language: de 15 | Funktionalität: Die Welt ist schön 16 | Szenario: Was wäre wenn die schöne, neue Welt untergeht 17 | """ 18 | When I run "behave -f plain --junit --no-timings features/eins.feature" 19 | Then it should pass with: 20 | """ 21 | 1 feature passed, 0 failed, 0 skipped 22 | 1 scenario passed, 0 failed, 0 skipped 23 | 0 steps passed, 0 failed, 0 skipped, 0 undefined 24 | """ 25 | And the command output should contain: 26 | """ 27 | Funktionalität: Die Welt ist schön 28 | Szenario: Was wäre wenn die schöne, neue Welt untergeht 29 | """ 30 | 31 | 32 | Scenario: Ensure JUnit reports can be created from a foreign language 33 | Given I use the current directory as working directory 34 | When I run "behave -f plain --junit --no-timings tools/test-features/french.feature" 35 | Then it should pass with: 36 | """ 37 | 1 feature passed, 0 failed, 0 skipped 38 | 2 scenarios passed, 0 failed, 0 skipped 39 | 5 steps passed, 0 failed, 0 skipped, 0 undefined 40 | """ 41 | And the command output should contain: 42 | """ 43 | Fonctionnalité: testing stuff 44 | Scénario: test stuff 45 | Etant donné I am testing stuff ... passed 46 | Quand I exercise it work ... passed 47 | Alors it will work ... passed 48 | Scénario: test more stuff 49 | Etant donné I am testing stuff ... passed 50 | Alors it will work ... passed 51 | """ 52 | -------------------------------------------------------------------------------- /docs/behave.rst-template: -------------------------------------------------------------------------------- 1 | ============== 2 | *behave* usage 3 | ============== 4 | 5 | The command-line tool *behave* has a bunch of `command-line arguments`_ and is 6 | also configurable using `configuration files`_. 7 | 8 | Values defined in the configuration files are used as defaults which the 9 | command-line arguments may override. 10 | 11 | 12 | Command-Line Arguments 13 | ====================== 14 | 15 | You may see the same information presented below at any time using ``behave 16 | -h``. 17 | 18 | {cmdline} 19 | 20 | 21 | Tag Expression 22 | -------------- 23 | 24 | {tag_expression} 25 | 26 | 27 | Configuration Files 28 | =================== 29 | 30 | Configuration files for *behave* are called either ".behaverc" or 31 | "behave.ini" (your preference) and are located in one of three places: 32 | 33 | 1. the current working directory (good for per-project settings), 34 | 2. your home directory ($HOME), or 35 | 3. on Windows, in the %APPDATA% directory. 36 | 37 | If you are wondering where *behave* is getting its configuration defaults 38 | from you can use the "-v" command-line argument and it'll tell you. 39 | 40 | Confuguration files **must** start with the label "[behave]" and are 41 | formatted in the Windows INI style, for example: 42 | 43 | .. code-block:: ini 44 | 45 | [behave] 46 | format=plain 47 | logging_clear_handlers=yes 48 | logging_filter=-suds 49 | 50 | The types possible are: 51 | 52 | **text** 53 | This just assigns whatever text you supply to the configuration setting. 54 | 55 | **boolean** 56 | This assigns a boolean value to the configuration setting. True values 57 | are "1", "yes", "true", and "on". False values are "0", "no", "false", 58 | and "off". 59 | 60 | **text (multiple allowed)** 61 | These fields accept one or more values on new lines, for example a tag 62 | expression might look like: 63 | 64 | .. code-block:: ini 65 | 66 | tags=@foo,~@bar 67 | @zap 68 | 69 | which is the equivalent of the command-line usage:: 70 | 71 | --tags @foo,~@bar --tags @zap. 72 | 73 | 74 | Recognised Settings 75 | ------------------- 76 | 77 | {config} 78 | 79 | 80 | -------------------------------------------------------------------------------- /docs/update_behave_rst.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python 2 | 3 | import re 4 | import sys 5 | import conf 6 | import textwrap 7 | 8 | sys.argv[0] = 'behave' 9 | 10 | from behave import configuration 11 | from behave import __main__ 12 | 13 | with open('behave.rst-template') as f: 14 | template = f.read() 15 | 16 | #cmdline = configuration.parser.format_help() 17 | 18 | config = [] 19 | cmdline = [] 20 | for fixed, keywords in configuration.options: 21 | skip = False 22 | if 'dest' in keywords: 23 | dest = keywords['dest'] 24 | else: 25 | for opt in fixed: 26 | if opt.startswith('--no'): 27 | skip = True 28 | if opt.startswith('--'): 29 | dest = opt[2:].replace('-', '_') 30 | else: 31 | assert len(opt) == 2 32 | dest = opt[1:] 33 | 34 | text = re.sub(r'\s+', ' ', keywords['help']).strip() 35 | text = text.replace('%%', '%') 36 | text = textwrap.fill(text, 70, initial_indent=' ', subsequent_indent=' ') 37 | if fixed: 38 | # -- COMMAND-LINE OPTIONS (CONFIGFILE only have empty fixed): 39 | cmdline.append('**%s**\n%s' % (', '.join(fixed), text)) 40 | 41 | if skip or dest in 'tags_help lang_list lang_help version'.split(): 42 | continue 43 | 44 | action = keywords.get('action', 'store') 45 | if action == 'store': 46 | type = 'text' 47 | elif action in ('store_true','store_false'): 48 | type = 'boolean' 49 | elif action == 'append': 50 | type = 'text (multiple allowed)' 51 | else: 52 | raise ValueError('unknown action %s' % action) 53 | 54 | text = re.sub(r'\s+', ' ', keywords.get('config_help', keywords['help'])).strip() 55 | text = text.replace('%%', '%') 56 | text = textwrap.fill(text, 70, initial_indent=' ', subsequent_indent=' ') 57 | config.append('**%s** -- %s\n%s' % (dest, type, text)) 58 | 59 | 60 | values = dict( 61 | cmdline='\n'.join(cmdline), 62 | tag_expression=__main__.TAG_HELP, 63 | config='\n'.join(config), 64 | ) 65 | 66 | with open('behave.rst', 'w') as f: 67 | f.write(template.format(**values)) 68 | -------------------------------------------------------------------------------- /tools/test-features/step-data.feature: -------------------------------------------------------------------------------- 1 | Feature: steps may have associated data 2 | 3 | Scenario: step with text 4 | Given some body of text 5 | """ 6 | Lorem ipsum dolor sit amet, consectetur adipisicing elit, sed 7 | do eiusmod tempor incididunt ut labore et dolore magna aliqua. Ut 8 | enim ad minim veniam, quis nostrud exercitation ullamco laboris 9 | nisi ut aliquip ex ea commodo consequat. 10 | """ 11 | Then the text is as expected 12 | 13 | Scenario: step with a table 14 | Given some initial data 15 | | name | department | 16 | | Barry | Beer Cans | 17 | | Pudey | Silly Walks | 18 | | Two-Lumps | Silly Walks | 19 | Then we will have the expected data 20 | 21 | @xfail 22 | Scenario: step with text that fails 23 | Given some body of text 24 | """ 25 | Lorem ipsum dolor sit amet, consectetur adipisicing elit, sed 26 | do eiusmod tempor incididunt ut labore et dolore magna aliqua. Ut 27 | enim ad minim VENIAM, quis nostrud exercitation ullamco laboris 28 | nisi ut aliquip ex ea commodo consequat. 29 | """ 30 | Then the text is as expected 31 | 32 | Scenario Outline: step with text and subtitution 33 | Given some body of text 34 | """ 35 | Lorem dolor sit amet, consectetur adipisicing elit, sed 36 | do eiusmod tempor incididunt ut labore et dolore magna aliqua. Ut 37 | enim ad minim veniam, quis nostrud exercitation ullamco laboris 38 | nisi ut aliquip ex ea commodo consequat. 39 | """ 40 | Then the text is substituted as expected 41 | 42 | Examples: 43 | | ipsum | 44 | | spam | 45 | | ham | 46 | 47 | 48 | Scenario Outline: step with a table and substution 49 | Given some initial data 50 | | name | department | 51 | | Barry | Cans | 52 | | Pudey | Silly Walks | 53 | | Two-Lumps | Silly Walks | 54 | Then we will have the substituted data 55 | 56 | Examples: 57 | | spam | 58 | | spam | 59 | | ham | 60 | 61 | -------------------------------------------------------------------------------- /features/step.execute_steps.feature: -------------------------------------------------------------------------------- 1 | Feature: Execute Steps within a Step Function (Nested Steps) 2 | 3 | As a tester 4 | I want to reuse existing steps and call several ones within another step 5 | So that I can comply with the the DRY principle. 6 | 7 | Scenario: Execute a number of simple steps (GOOD CASE) 8 | Given a new working directory 9 | And a file named "features/steps/steps.py" with: 10 | """ 11 | from behave import given, when, then 12 | 13 | @given(u'I go to the supermarket') 14 | def step_given_I_go_to_the_supermarket(context): 15 | context.shopping_cart = {} 16 | 17 | @when(u'I buy {amount:n} {item:w}') 18 | def step_when_I_buy(context, amount, item): 19 | assert amount >= 0 20 | if not context.shopping_cart.has_key(item): 21 | context.shopping_cart[item] = 0 22 | context.shopping_cart[item] += amount 23 | 24 | # -- HERE: Is the interesting functionality. 25 | @when(u'I buy the usual things') 26 | def step_when_I_buy_the_usual_things(context): 27 | context.execute_steps(u''' 28 | When I buy 2 apples 29 | And I buy 3 bananas 30 | ''') 31 | 32 | @then(u'I have {amount:n} {item:w}') 33 | def step_then_I_have(context, amount, item): 34 | actual = context.shopping_cart.get(item, 0) 35 | assert amount == actual 36 | """ 37 | And a file named "features/use_nested_steps.feature" with: 38 | """ 39 | Feature: 40 | Scenario: 41 | Given I go to the supermarket 42 | When I buy the usual things 43 | Then I have 2 apples 44 | And I have 3 bananas 45 | """ 46 | When I run "behave -f plain features/use_nested_steps.feature" 47 | Then it should pass with: 48 | """ 49 | 1 feature passed, 0 failed, 0 skipped 50 | 1 scenario passed, 0 failed, 0 skipped 51 | 4 steps passed, 0 failed, 0 skipped, 0 undefined 52 | """ 53 | 54 | @not_implemented 55 | Scenario: A Nested Step Fails with Assert 56 | 57 | @not_implemented 58 | Scenario: A Nested Step Fails with Exception 59 | 60 | -------------------------------------------------------------------------------- /issue.features/issue0159.feature: -------------------------------------------------------------------------------- 1 | @issue 2 | Feature: Issue #159: output stream is wrapped twice in the codecs.StreamWriter 3 | 4 | @setup 5 | Scenario: Feature Setup 6 | Given a new working directory 7 | And a file named "features/steps/steps.py" with: 8 | """ 9 | # -*- coding: utf-8 -*- 10 | from behave import step 11 | 12 | @step('firstname is "{name}"') 13 | def step_impl(context, name): 14 | pass 15 | 16 | @step(u'full name is Loïc "{name}"') 17 | def step_impl(context, name): 18 | pass 19 | """ 20 | 21 | Scenario: Single feature, pass (a) 22 | Given a file named "features/issue159_stream_writer.feature" with: 23 | """ 24 | Feature: 25 | Scenario: 26 | When firstname is "Loïc" 27 | """ 28 | When I run "behave -f plain features/" 29 | Then it should pass 30 | 31 | 32 | Scenario: Single feature, pass (b) 33 | Given a file named "features/issue159_stream_writer.feature" with: 34 | """ 35 | Feature: 36 | Scenario: 37 | When full name is Loïc "Dupont" 38 | """ 39 | When I run "behave -f plain features/" 40 | Then it should pass 41 | 42 | 43 | Scenario: Two features, FAIL (a) 44 | Given a file named "features/issue159_stream_writer.feature" with: 45 | """ 46 | Feature: 47 | Scenario: 48 | When full name is Loïc "Dupont" 49 | """ 50 | And a file named "features/issue159_stream_writer_again.feature" with: 51 | """ 52 | Feature: 53 | Scenario: 54 | When full name is Loïc "Dupond" 55 | """ 56 | When I run "behave -f plain features/" 57 | Then it should pass 58 | 59 | 60 | Scenario: Two features, FAIL (b) 61 | Given a file named "features/issue159_stream_writer.feature" with: 62 | """ 63 | Feature: 64 | Scenario: 65 | When firstname is "Loïc" 66 | """ 67 | And a file named "features/issue159_stream_writer_again.feature" with: 68 | """ 69 | Feature: 70 | Scenario: 71 | When firstname is "Loïc" 72 | """ 73 | When I run "behave -f plain features/" 74 | Then it should pass 75 | -------------------------------------------------------------------------------- /features/runner.select_files_by_regexp.example.feature: -------------------------------------------------------------------------------- 1 | Feature: Select feature files by using regular expressions (self-test) 2 | 3 | Use behave self-tests to ensure that --incude/--exclude options work. 4 | RELATED: runner.select_files_by_regexp.feature 5 | 6 | @setup 7 | Scenario: Feature Setup 8 | Given a new working directory 9 | And an empty file named "features/steps/steps.py" 10 | And a file named "features/alice.feature" with: 11 | """ 12 | Feature: Alice 13 | Scenario: A1 14 | """ 15 | And a file named "features/barbi.feature" with: 16 | """ 17 | Feature: Barbi 18 | Scenario: B1 19 | """ 20 | And a file named "features/bob.feature" with: 21 | """ 22 | Feature: Bob 23 | Scenario: B2 24 | """ 25 | 26 | 27 | Scenario: Include only feature files 28 | 29 | Select the following feature files: barbi.feature, bob.feature 30 | 31 | When I run "behave --include='features/b.*' -f plain features/" 32 | Then it should pass with: 33 | """ 34 | 2 features passed, 0 failed, 0 skipped 35 | """ 36 | And the command output should contain: 37 | """ 38 | Feature: Barbi 39 | Scenario: B1 40 | 41 | Feature: Bob 42 | Scenario: B2 43 | """ 44 | 45 | Scenario: Exclude only feature files 46 | 47 | Select the following feature files: alice.feature 48 | 49 | When I run "behave --exclude='features/b.*' -f plain features/" 50 | Then it should pass with: 51 | """ 52 | 1 feature passed, 0 failed, 0 skipped 53 | """ 54 | And the command output should contain: 55 | """ 56 | Feature: Alice 57 | """ 58 | 59 | Scenario: Include and exclude feature files 60 | 61 | Select the following feature files: alice.feature 62 | 63 | When I run "behave --include='features/.*a.*\.feature' --exclude='.*/barbi.*' -f plain features/" 64 | Then it should pass with: 65 | """ 66 | 1 feature passed, 0 failed, 0 skipped 67 | """ 68 | And the command output should contain: 69 | """ 70 | Feature: Alice 71 | """ 72 | -------------------------------------------------------------------------------- /issue.features/issue0116.feature: -------------------------------------------------------------------------------- 1 | @issue 2 | @change_request 3 | Feature: Issue #116: SummaryReporter shows failed scenarios list 4 | 5 | Scenario: Test Setup 6 | Given a new working directory 7 | And a file named "features/steps/passing_failing_steps.py" with: 8 | """ 9 | from behave import step 10 | 11 | @step(u'a step passes') 12 | def step_passes(context): 13 | pass 14 | 15 | @step(u'a step fails') 16 | def step_fails(context): 17 | assert False, "FAILS" 18 | """ 19 | And a file named "features/e1.feature" with: 20 | """ 21 | Feature: E1 22 | 23 | Scenario: E1.1 24 | Given a step passes 25 | 26 | @xfail 27 | Scenario: E1.2 (XFAIL) 28 | Given a step fails 29 | 30 | Scenario: E1.3 31 | Given a step passes 32 | """ 33 | And a file named "features/e2.feature" with: 34 | """ 35 | @example2 36 | Feature: E2 37 | 38 | @xfail 39 | Scenario: E2.1 (XFAIL) 40 | Given a step fails 41 | 42 | Scenario: E2.2 43 | Given a step passes 44 | """ 45 | 46 | Scenario: Summary shows list of failed scenarios when at least one fails 47 | When I run "behave -f plain features/" 48 | Then it should fail 49 | And the command output should contain: 50 | """ 51 | Failing scenarios: 52 | features/e1.feature:7 E1.2 (XFAIL) 53 | features/e2.feature:5 E2.1 (XFAIL) 54 | 55 | 0 features passed, 2 failed, 0 skipped 56 | 3 scenarios passed, 2 failed, 0 skipped 57 | 3 steps passed, 2 failed, 0 skipped, 0 undefined 58 | """ 59 | 60 | Scenario: Summary hides list of failed scenarios when all scenarios pass 61 | When I run "behave -f plain --tags=~@xfail features/" 62 | Then it should pass with: 63 | """ 64 | 2 features passed, 0 failed, 0 skipped 65 | 3 scenarios passed, 0 failed, 2 skipped 66 | 3 steps passed, 0 failed, 2 skipped, 0 undefined 67 | """ 68 | But the command output should not contain: 69 | """ 70 | Failing scenarios: 71 | """ 72 | -------------------------------------------------------------------------------- /issue.features/issue0139.feature: -------------------------------------------------------------------------------- 1 | @issue 2 | @not_reproducible 3 | Feature: Issue #139: Wrong steps seem to be executed when using --wip 4 | 5 | | RELATED-TO: issue #35 6 | | behave --format=plain --tags @one" seems to execute right scenario w/ wrong steps 7 | | 8 | | If you have a feature file with two scenarios where the second is tagged 9 | | with @wip, running behave -w will output step names from the first scenario. 10 | | It does seem to run the correct code for the steps. 11 | 12 | 13 | Scenario: 14 | Given a new working directory 15 | And a file named "features/steps/steps.py" with: 16 | """ 17 | from behave import given, when, then, step 18 | 19 | @step('a step passes') 20 | def step_passes(context): 21 | pass 22 | 23 | @step('a step fails') 24 | def step_fails(context): 25 | assert False, "XFAIL" 26 | 27 | @when('I run a test step') 28 | def step_impl(context): 29 | pass 30 | 31 | @when('I run some other test step') 32 | def step_impl(context): 33 | pass 34 | 35 | @then('I should not see a failure here') 36 | def step_impl(context): 37 | pass 38 | """ 39 | And a file named "features/issue0139_example.feature" with: 40 | """ 41 | Feature: Bug in wip/behave -w 42 | 43 | Scenario: This is strange 44 | Given a step passes 45 | When a step passes 46 | Then a step fails 47 | 48 | @wip 49 | Scenario: Demonstrate bug 50 | When I run a test step 51 | And I run some other test step 52 | Then I should not see a failure here 53 | """ 54 | When I run "behave -w -f plain -T features/issue0139_example.feature" 55 | Then it should pass 56 | And the command output should contain: 57 | """ 58 | Feature: Bug in wip/behave -w 59 | Scenario: This is strange 60 | Scenario: Demonstrate bug 61 | When I run a test step ... passed 62 | And I run some other test step ... passed 63 | Then I should not see a failure here ... passed 64 | """ 65 | 66 | 67 | 68 | -------------------------------------------------------------------------------- /issue.features/issue0069.feature: -------------------------------------------------------------------------------- 1 | @issue 2 | Feature: Issue #69: JUnitReporter: Fault when processing ScenarioOutlines with failing steps 3 | 4 | The problem occurs when "behave --junit ..." is used 5 | And a feature contains one or more ScenarioOutline(s) with failing steps. 6 | 7 | The JUnitReport does not process ScenarioOutline correctly (logic-error). 8 | Therefore, Scenarios of a ScenarioOutline are processes as Scenario steps. 9 | This causes problems when the step.status is "failed". 10 | 11 | RELATED: 12 | * issue #63 13 | 14 | Background: Test Setup 15 | Given a new working directory 16 | And a file named "features/steps/steps.py" with: 17 | """ 18 | from behave import given 19 | 20 | @given(u'a {outcome} step with "{name}"') 21 | def step(context, outcome, name): 22 | context.name = name 23 | assert outcome == "passing" 24 | 25 | @when(u'a {outcome} step with "{name}" occurs') 26 | def step(context, outcome, name): 27 | assert outcome == "passing" 28 | assert context.name == name 29 | 30 | @then(u'a {outcome} step with "{name}" is reached') 31 | def step(context, outcome, name): 32 | assert outcome == "passing" 33 | assert context.name == name 34 | """ 35 | 36 | Scenario: ScenarioOutline with Failing Steps 37 | Given a file named "features/issue63_case2.feature" with: 38 | """ 39 | Feature: ScenarioOutline with Passing and Failing Steps 40 | Scenario Outline: 41 | Given a passing step with "" 42 | When a failing step with "" occurs 43 | Then a passing step with "" is reached 44 | 45 | Examples: 46 | |name | 47 | |Alice| 48 | |Bob | 49 | """ 50 | When I run "behave -c --junit features/issue63_case2.feature" 51 | Then it should fail with: 52 | """ 53 | 0 scenarios passed, 2 failed, 0 skipped 54 | 2 steps passed, 2 failed, 2 skipped, 0 undefined 55 | """ 56 | But the command output should not contain: 57 | """ 58 | AttributeError: 'Scenario' object has no attribute 'exception' 59 | """ 60 | And the command output should not contain: 61 | """ 62 | behave/reporter/junit.py 63 | """ 64 | 65 | -------------------------------------------------------------------------------- /issue.features/issue0145.feature: -------------------------------------------------------------------------------- 1 | @issue 2 | Feature: Issue #145: before_feature/after_feature should not be skipped 3 | 4 | | Hooks before_feature(), after_feature() (and before_step()) are skipped 5 | | if --tags options select feature tag and scenario tag. 6 | | 7 | | SEE ALSO: https://github.com/cucumber/cucumber/wiki/Tags 8 | 9 | @setup 10 | Scenario: Setup 11 | Given a new working directory 12 | And a file named "features/steps/steps.py" with: 13 | """ 14 | from behave import step 15 | 16 | @step('a step passes') 17 | def step_passes(context): 18 | pass 19 | """ 20 | And a file named "features/issue0145_example.feature" with: 21 | """ 22 | @feature 23 | Feature: Feature-145 24 | 25 | @scenario 26 | Scenario: Scenario-145 27 | Given a step passes 28 | When a step passes 29 | Then a step passes 30 | """ 31 | And a file named "features/environment.py" with: 32 | """ 33 | from __future__ import print_function 34 | 35 | def before_feature(context, feature): 36 | print("hooks.before_feature: %s called." % feature.name) 37 | 38 | def after_feature(context, feature): 39 | print("hooks.after_feature: %s called." % feature.name) 40 | """ 41 | 42 | Scenario: Select only @scenario tag 43 | When I run "behave -f plain -T --tags=@scenario features/issue0145_example.feature" 44 | Then it should pass with: 45 | """ 46 | 1 feature passed, 0 failed, 0 skipped 47 | 1 scenario passed, 0 failed, 0 skipped 48 | 3 steps passed, 0 failed, 0 skipped, 0 undefined 49 | """ 50 | And the behave hook "before_feature" was called 51 | And the behave hook "after_feature" was called 52 | 53 | Scenario: Select @feature tag and @scenario tag (logical-and, fails if not fixed) 54 | When I run "behave -f plain -T --tags=@feature --tags=@scenario features/issue0145_example.feature" 55 | Then it should pass with: 56 | """ 57 | 1 feature passed, 0 failed, 0 skipped 58 | 1 scenario passed, 0 failed, 0 skipped 59 | 3 steps passed, 0 failed, 0 skipped, 0 undefined 60 | """ 61 | And the behave hook "before_feature" was called 62 | And the behave hook "after_feature" was called 63 | 64 | -------------------------------------------------------------------------------- /issue.features/issue0092.feature: -------------------------------------------------------------------------------- 1 | @issue 2 | Feature: Issue #92: Output from --format=plain shows skipped steps in next scenario 3 | 4 | | DUPLICATED, FIXED-BY: issue #35 solution. 5 | | 6 | | Given a feature has more than one scenario 7 | | When the --format=plain option is used 8 | | and a middle step of a scenario fails 9 | | Then the skipped steps appear under the next scenario 10 | 11 | 12 | Scenario: 13 | Given a new working directory 14 | And a file named "features/issue92_syndrome.feature" with: 15 | """ 16 | Feature: Testing Plain Output 17 | Reproduces bug where output from previous scenario appears before current. 18 | 19 | Scenario: First example 20 | Given this step works 21 | When this step fails 22 | Then this step appears in the wrong place 23 | 24 | Scenario: Second example 25 | Given this step works 26 | When this step fails 27 | Then this step appears in the wrong place 28 | """ 29 | And a file named "features/steps/steps.py" with: 30 | """ 31 | from behave import step 32 | 33 | @step(u'this step works') 34 | def working(context): 35 | pass 36 | 37 | 38 | @step(u'this step fails') 39 | def failing(context): 40 | assert False, 'step failed' 41 | 42 | 43 | @step(u'this step appears in the wrong place') 44 | def missing(context): 45 | pass 46 | """ 47 | When I run "behave --no-timings --format=plain features/issue92_syndrome.feature" 48 | Then it should fail with: 49 | """ 50 | 0 features passed, 1 failed, 0 skipped 51 | 0 scenarios passed, 2 failed, 0 skipped 52 | 2 steps passed, 2 failed, 2 skipped, 0 undefined 53 | """ 54 | And the command output should contain: 55 | """ 56 | Feature: Testing Plain Output 57 | Scenario: First example 58 | Given this step works ... passed 59 | When this step fails ... failed 60 | Assertion Failed: step failed 61 | 62 | Scenario: Second example 63 | Given this step works ... passed 64 | When this step fails ... failed 65 | Assertion Failed: step failed 66 | """ 67 | -------------------------------------------------------------------------------- /issue.features/issue0127.feature: -------------------------------------------------------------------------------- 1 | @issue 2 | Feature: Issue #127: Strip trailing colons 3 | 4 | | Trailing colon in a step is stripped by the Gherkin parser. 5 | | Undefined step snippets should not suggest the step with a trailing colon. 6 | | 7 | | GENERAL RULE (by looking at the parser): 8 | | 1. Colon in step in feature file is OK 9 | | (parser strips this for step-with-table or step-with-multiline-text). 10 | | 2. Step definitions in Python files should not end with a colon 11 | | (used in @given/@when/@then decorators). 12 | 13 | 14 | Background: 15 | Given a new working directory 16 | And a file named "features/example127.feature" with: 17 | """ 18 | Feature: 19 | Scenario: 20 | Given the following superusers exist: 21 | | Name | User Id | 22 | | Alice | 101 | 23 | | Bob | 102 | 24 | """ 25 | 26 | Scenario: Step Definition has no trailing colon (GOOD CASE) 27 | Given a file named "features/steps/good_steps.py" with: 28 | """ 29 | from behave import given 30 | 31 | @given(u'the following superusers exist') 32 | def step_given_following_superusers_exist(context): 33 | pass 34 | """ 35 | When I run "behave -f plain features/example127.feature" 36 | Then it should pass 37 | And the command output should not contain: 38 | """ 39 | You can implement step definitions for undefined steps with these snippets: 40 | 41 | @given(u'the following superusers exist:') 42 | def step_impl(context): 43 | assert False 44 | """ 45 | 46 | Scenario: Step Definition has trailing colon (BAD CASE) 47 | Given a file named "features/steps/bad_steps.py" with: 48 | """ 49 | from behave import given 50 | 51 | @given(u'the following superusers exist:') 52 | def step_given_following_superusers_exist(context): 53 | pass 54 | """ 55 | When I run "behave -f plain features/example127.feature" 56 | Then it should fail 57 | And the command output should contain: 58 | """ 59 | You can implement step definitions for undefined steps with these snippets: 60 | 61 | @given(u'the following superusers exist') 62 | def step_impl(context): 63 | assert False 64 | """ 65 | -------------------------------------------------------------------------------- /test/test_formatter_tags.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | """ 3 | Test formatters: 4 | * behave.formatter.tags.TagsCountFormatter 5 | * behave.formatter.tags.TagsLocationFormatter 6 | """ 7 | 8 | from __future__ import absolute_import 9 | from .test_formatter import FormatterTests as FormatterTest 10 | from .test_formatter import MultipleFormattersTests as MultipleFormattersTest 11 | 12 | # ----------------------------------------------------------------------------- 13 | # FORMATTER TESTS: With TagCountFormatter 14 | # ----------------------------------------------------------------------------- 15 | class TestTagsCountFormatter(FormatterTest): 16 | formatter_name = "tags" 17 | 18 | # ----------------------------------------------------------------------------- 19 | # FORMATTER TESTS: With TagLocationFormatter 20 | # ----------------------------------------------------------------------------- 21 | class TestTagsLocationFormatter(FormatterTest): 22 | formatter_name = "tags.location" 23 | 24 | 25 | # ----------------------------------------------------------------------------- 26 | # MULTI-FORMATTER TESTS: With TagCountFormatter 27 | # ----------------------------------------------------------------------------- 28 | class TestPrettyAndTagsCount(MultipleFormattersTest): 29 | formatters = ["pretty", "tags"] 30 | 31 | class TestPlainAndTagsCount(MultipleFormattersTest): 32 | formatters = ["plain", "tags"] 33 | 34 | class TestJSONAndTagsCount(MultipleFormattersTest): 35 | formatters = ["json", "tags"] 36 | 37 | class TestRerunAndTagsCount(MultipleFormattersTest): 38 | formatters = ["rerun", "tags"] 39 | 40 | 41 | # ----------------------------------------------------------------------------- 42 | # MULTI-FORMATTER TESTS: With TagLocationFormatter 43 | # ----------------------------------------------------------------------------- 44 | class TestPrettyAndTagsLocation(MultipleFormattersTest): 45 | formatters = ["pretty", "tags.location"] 46 | 47 | class TestPlainAndTagsLocation(MultipleFormattersTest): 48 | formatters = ["plain", "tags.location"] 49 | 50 | class TestJSONAndTagsLocation(MultipleFormattersTest): 51 | formatters = ["json", "tags.location"] 52 | 53 | class TestRerunAndTagsLocation(MultipleFormattersTest): 54 | formatters = ["rerun", "tags.location"] 55 | 56 | class TestTagsCountAndTagsLocation(MultipleFormattersTest): 57 | formatters = ["tags", "tags.location"] 58 | -------------------------------------------------------------------------------- /issue.features/issue0083.feature: -------------------------------------------------------------------------------- 1 | @issue 2 | Feature: Issue #83: behave.__main__:main() Various sys.exit issues 3 | 4 | | Currently, the main function has several issues related 5 | | to sys.exit() returncode usage: 6 | | 7 | | 1. sys.exit("string") is invalid, a number must be used: 8 | | => Used in exception cases after run (ParseError, ConfigError) 9 | | 10 | | 2. On success, the main() function returns implicitly None 11 | | instead of using sys.exit(0) 12 | | => No statement at end of function after failed case. 13 | 14 | @setup 15 | Scenario: Feature Setup 16 | Given a new working directory 17 | And a file named "features/steps/steps.py" with: 18 | """ 19 | from behave import step 20 | 21 | @step(u'a step passes') 22 | def step_passes(context): 23 | pass 24 | """ 25 | 26 | Scenario: Successful test run 27 | Given a file named "features/passing.feature" with: 28 | """ 29 | Feature: 30 | Scenario: 31 | Given a step passes 32 | When a step passes 33 | Then a step passes 34 | """ 35 | When I run "behave -c features/passing.feature" 36 | Then it should pass 37 | And the command returncode is "0" 38 | 39 | Scenario: ParseError occurs 40 | Given a file named "features/invalid_with_ParseError.feature" with: 41 | """ 42 | Feature: 43 | Scenario: Invalid scenario which raises ParseError 44 | Given a step passes 45 | When2 a step passes 46 | """ 47 | When I run "behave -c features/invalid_with_ParseError.feature" 48 | Then it should fail 49 | And the command returncode is non-zero 50 | And the command output should contain: 51 | """ 52 | Failed to parse "{__WORKDIR__}/features/invalid_with_ParseError.feature" 53 | """ 54 | 55 | Scenario: ConfigError occurs 56 | Given a new working directory 57 | And a file named "features/passing2.feature" with: 58 | """ 59 | Feature: 60 | Scenario: 61 | Given a step passes 62 | """ 63 | When I run "behave -c features/passing2.feature" 64 | Then it should fail 65 | And the command returncode is non-zero 66 | And the command output should contain: 67 | """ 68 | No steps directory in "{__WORKDIR__}/features" 69 | """ 70 | -------------------------------------------------------------------------------- /issue.features/issue0109.feature: -------------------------------------------------------------------------------- 1 | @issue 2 | Feature: Issue #109: Insists that implemented tests are not implemented 3 | 4 | | STATUS: Resolved, not a behave problem. 5 | | 6 | | Following feature file marks implemented step "when I submit the following data" 7 | | as not implemented. 8 | 9 | 10 | Scenario: 11 | Given a new working directory 12 | And a file named "features/syndrome109.feature" with: 13 | """ 14 | @wip 15 | Feature: Manage accounts from the admin interface 16 | 17 | Scenario: Login successfully via login form 18 | Given I navigate to "/admin/" 19 | when I submit the following data 20 | | name | value | 21 | | username | admin@foo.bar | 22 | | password | pass | 23 | then I see the word "Welcome" 24 | 25 | Scenario: Create user via admin user creation form 26 | Given I navigate to "/admin/users/user/add/" 27 | when I submit the following data 28 | | name | value | 29 | | email | spaaaaaaaaaaaaaaaaaaam@ham.eggs | 30 | | password1 | pass | 31 | | password2 | pass | 32 | then I see the word "successfully" 33 | """ 34 | And a file named "features/steps/steps.py" with: 35 | """ 36 | from behave import given, when, then 37 | 38 | @given(u'I navigate to "{url}"') 39 | def step_navigate_to_url(context, url): 40 | pass 41 | 42 | @when(u'I submit the following data') 43 | def step_submit_data(context): 44 | pass 45 | 46 | @then(u'I see the word "{word}"') 47 | def step_see_word(context, word): 48 | pass 49 | """ 50 | When I run "behave -w features/syndrome109.feature" 51 | Then it should pass with: 52 | """ 53 | 1 feature passed, 0 failed, 0 skipped 54 | 2 scenarios passed, 0 failed, 0 skipped 55 | 6 steps passed, 0 failed, 0 skipped, 0 undefined 56 | """ 57 | And the command output should not contain: 58 | """ 59 | You can implement step definitions for undefined steps with these snippets: 60 | """ 61 | -------------------------------------------------------------------------------- /issue.features/issue0112.feature: -------------------------------------------------------------------------------- 1 | @issue 2 | @change_request 3 | Feature: Issue #112: Improvement to AmbiguousStep error 4 | 5 | | AmbiguousStep could be more useful if it also showed the existing string 6 | | with which the new one is clashing. This is particularly useful 7 | | if using step parameters. 8 | 9 | 10 | Background: 11 | Given a new working directory 12 | And a file named "features/syndrome112.feature" with: 13 | """ 14 | Feature: 15 | Scenario: 16 | Given I buy 10 oranges 17 | """ 18 | 19 | Scenario: Good step ordering -- From specific to generic regular expression 20 | Given a file named "features/steps/good_steps.py" with: 21 | """ 22 | from behave import given, when, then 23 | 24 | # -- ORDERING-IMPORTANT: From more specific steps to less specific. 25 | @given(u'I buy {number:n} {items:w}') 26 | def step_given_I_buy2(context, number, items): 27 | pass 28 | 29 | # -- OTHERWISE: Generic step matches all other patterns. 30 | @given(u'I buy {amount} {product}') 31 | def step_given_I_buy(context, amount, product): 32 | pass 33 | """ 34 | When I run "behave -c features/syndrome112.feature" 35 | Then it should pass with: 36 | """ 37 | 1 feature passed, 0 failed, 0 skipped 38 | 1 scenario passed, 0 failed, 0 skipped 39 | 1 step passed, 0 failed, 0 skipped, 0 undefined 40 | """ 41 | 42 | 43 | Scenario: Bad step ordering causes AmbiguousStep 44 | Given a file named "features/steps/bad_steps.py" with: 45 | """ 46 | from behave import given, when, then 47 | 48 | # -- ORDERING-VIOLATED: Generic step comes first. 49 | @given(u'I buy {amount} {product}') 50 | def step_given_I_buy(context, amount, product): 51 | pass 52 | 53 | # -- AMBIGUOUS-STEP: Will occur here. 54 | @given(u'I buy {number:n} {items:w}') 55 | def step_given_I_buy2(context, number, items): 56 | pass 57 | """ 58 | When I run "behave -c features/syndrome112.feature" 59 | Then it should fail 60 | And the command output should contain: 61 | """ 62 | AmbiguousStep: @given('I buy {number:n} {items:w}') has already been defined in 63 | existing step @given('I buy {amount} {product}') at features/steps/bad_steps.py:4 64 | """ 65 | -------------------------------------------------------------------------------- /behave/tag_expression.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | 3 | 4 | class TagExpression(object): 5 | def __init__(self, tag_expressions): 6 | self.ands = [] 7 | self.limits = {} 8 | 9 | for expr in tag_expressions: 10 | self.add([e.strip() for e in expr.strip().split(',')]) 11 | 12 | def check(self, tags): 13 | if not self.ands: 14 | return True 15 | 16 | params = set(tags) 17 | 18 | def test_tag(tag): 19 | if tag.startswith('-') or tag.startswith('~'): 20 | return tag[1:] not in params 21 | return tag in params 22 | 23 | return all(any(test_tag(tag) for tag in ors) for ors in self.ands) 24 | 25 | def add(self, tags): 26 | negatives = [] 27 | positives = [] 28 | 29 | for tag in tags: 30 | if tag.startswith('@'): 31 | positives.append(tag[1:]) 32 | elif tag.startswith('-@') or tag.startswith('~@'): 33 | negatives.append('-' + tag[2:]) 34 | elif tag.startswith('-') or tag.startswith('~'): 35 | negatives.append(tag) 36 | else: 37 | positives.append(tag) 38 | 39 | self.store_and_extract_limits(negatives, True) 40 | self.store_and_extract_limits(positives, False) 41 | 42 | def store_and_extract_limits(self, tags, negated): 43 | tags_with_negation = [] 44 | 45 | for tag in tags: 46 | tag = tag.split(':') 47 | tag_with_negation = tag.pop(0) 48 | tags_with_negation.append(tag_with_negation) 49 | 50 | if tag: 51 | limit = int(tag[0]) 52 | if negated: 53 | tag_without_negation = tag_with_negation[1:] 54 | else: 55 | tag_without_negation = tag_with_negation 56 | limited = tag_without_negation in self.limits 57 | if limited and self.limits[tag_without_negation] != limit: 58 | msg = "Inconsistent tag limits for {0}: {1:d} and {2:d}" 59 | msg = msg.format(tag_without_negation, 60 | self.limits[tag_without_negation], limit) 61 | raise Exception(msg) 62 | self.limits[tag_without_negation] = limit 63 | 64 | if tags_with_negation: 65 | self.ands.append(tags_with_negation) 66 | 67 | def __len__(self): 68 | return len(self.ands) 69 | -------------------------------------------------------------------------------- /issue.features/issue0046.feature: -------------------------------------------------------------------------------- 1 | @issue 2 | Feature: Issue #46 Behave returns 0 (SUCCESS) even in case of test failures 3 | 4 | As I behave user 5 | I want to detect test success or test failures 6 | By using the process return value, 0 (SUCCESS) and non-zero for failure. 7 | 8 | Background: Test Setup 9 | Given a new working directory 10 | Given a file named "features/steps/steps.py" with: 11 | """ 12 | from behave import given 13 | 14 | @given(u'passing') 15 | def step(context): 16 | pass 17 | 18 | @given(u'failing') 19 | def step(context): 20 | assert False, "failing" 21 | """ 22 | 23 | Scenario: Successful Execution 24 | Given a file named "features/passing.feature" with: 25 | """ 26 | Feature: Passing 27 | Scenario: Passing Scenario Example 28 | Given passing 29 | """ 30 | When I run "behave -c -q features/passing.feature" 31 | Then it should pass with: 32 | """ 33 | 1 feature passed, 0 failed, 0 skipped 34 | 1 scenario passed, 0 failed, 0 skipped 35 | 1 step passed, 0 failed, 0 skipped, 0 undefined 36 | """ 37 | 38 | Scenario: Failing Execution 39 | Given a file named "features/failing.feature" with: 40 | """ 41 | Feature: Failing 42 | Scenario: Failing Scenario Example 43 | Given failing 44 | """ 45 | When I run "behave -c -q features/failing.feature" 46 | Then it should fail with: 47 | """ 48 | 0 features passed, 1 failed, 0 skipped 49 | 0 scenarios passed, 1 failed, 0 skipped 50 | 0 steps passed, 1 failed, 0 skipped, 0 undefined 51 | """ 52 | 53 | Scenario: Passing and Failing Execution 54 | Given a file named "features/passing_and_failing.feature" with: 55 | """ 56 | Feature: Passing and Failing 57 | Scenario: Passing Scenario Example 58 | Given passing 59 | Scenario: Failing Scenario Example 60 | Given failing 61 | """ 62 | When I run "behave -c -q features/passing_and_failing.feature" 63 | Then it should fail with: 64 | """ 65 | 0 features passed, 1 failed, 0 skipped 66 | 1 scenario passed, 1 failed, 0 skipped 67 | 1 step passed, 1 failed, 0 skipped, 0 undefined 68 | """ 69 | And the command output should contain: 70 | """ 71 | Feature: Passing and Failing 72 | Scenario: Passing Scenario Example 73 | Given passing 74 | Scenario: Failing Scenario Example 75 | Given failing 76 | Assertion Failed: failing 77 | """ 78 | -------------------------------------------------------------------------------- /issue.features/issue0084.feature: -------------------------------------------------------------------------------- 1 | @issue 2 | Feature: Issue #84: behave.runner behave does not reliably detected failed test runs 3 | 4 | | Behave does currently not reliably detected failed test runs and 5 | | therefore returns not sys.exit(1) at end of main(). 6 | | 7 | | 1. behave.runner:Runner.run_with_paths() returns failed==True 8 | | if last feature was successful and test runner does not stop 9 | | after first failing feature. 10 | | 11 | | 2. Issue #64: Same problem in behave.model.Feature.run() with scenarios 12 | 13 | Scenario: Test Setup 14 | Given a new working directory 15 | And a file named "features/passing.feature" with: 16 | """ 17 | Feature: Passing 18 | Scenario: 19 | Given a step passes 20 | When a step passes 21 | Then a step passes 22 | """ 23 | And a file named "features/failing.feature" with: 24 | """ 25 | Feature: Failing 26 | Scenario: 27 | Given a step fails 28 | When a step fails 29 | Then a step fails 30 | """ 31 | And a file named "features/steps/steps.py" with: 32 | """ 33 | from behave import step 34 | 35 | @step(u'a step passes') 36 | def step_passes(context): 37 | pass 38 | 39 | @step(u'a step fails') 40 | def step_fails(context): 41 | assert False, "step: a step fails" 42 | """ 43 | 44 | Scenario: First feature fails, second feature passes 45 | When I run "behave -f plain features/failing.feature features/passing.feature" 46 | Then it should fail with: 47 | """ 48 | 1 feature passed, 1 failed, 0 skipped 49 | 1 scenario passed, 1 failed, 0 skipped 50 | 3 steps passed, 1 failed, 2 skipped, 0 undefined 51 | """ 52 | 53 | Scenario: First feature passes, second feature fails 54 | When I run "behave -f plain features/passing.feature features/failing.feature" 55 | Then it should fail with: 56 | """ 57 | 1 feature passed, 1 failed, 0 skipped 58 | 1 scenario passed, 1 failed, 0 skipped 59 | 3 steps passed, 1 failed, 2 skipped, 0 undefined 60 | """ 61 | 62 | Scenario: First feature passes, second fails, last passes 63 | When I run "behave -f plain features/passing.feature features/failing.feature features/passing.feature" 64 | Then it should fail with: 65 | """ 66 | 2 features passed, 1 failed, 0 skipped 67 | 2 scenarios passed, 1 failed, 0 skipped 68 | 6 steps passed, 1 failed, 2 skipped, 0 undefined 69 | """ 70 | -------------------------------------------------------------------------------- /issue.features/issue0162.feature: -------------------------------------------------------------------------------- 1 | @issue 2 | Feature: Issue #162 Unnecessary ContextMaskWarnings when assert fails or exception is raised 3 | 4 | | Behave shows unnecessary ContextMaskWarnings related to: 5 | | 6 | | * tags 7 | | * capture_stdout 8 | | * capture_stderr 9 | | * log_capture 10 | | 11 | | if: 12 | | 13 | | * an assertion fails in a step-definition/step-function 14 | | * an exception is raised by a step-definition/step-function 15 | | 16 | | and an additional scenario follows. 17 | | REASON: Context "behave" mode is not restored when an exception is raised. 18 | 19 | 20 | @setup 21 | Scenario: Feature Setup 22 | Given a new working directory 23 | And a file named "features/steps/steps.py" with: 24 | """ 25 | from behave import step 26 | 27 | @step('a step passes') 28 | def step_passes(context): 29 | pass 30 | 31 | @step('a step assert fails') 32 | def step_assert_fails(context): 33 | assert False, "XFAIL-STEP" 34 | 35 | @step('an exception is raised') 36 | def step_raises_exception(context): 37 | raise RuntimeError("XFAIL-STEP") 38 | """ 39 | 40 | 41 | Scenario: Assertion fails in a step 42 | Given a file named "features/example0162_assert_fails.feature" with: 43 | """ 44 | Feature: 45 | Scenario: 46 | Given a step passes 47 | When a step assert fails 48 | Then a step passes 49 | 50 | Scenario: 51 | Given a step passes 52 | """ 53 | When I run "behave -f plain features/example0162_assert_fails.feature" 54 | Then it should fail with: 55 | """ 56 | 1 scenario passed, 1 failed, 0 skipped 57 | 2 steps passed, 1 failed, 1 skipped, 0 undefined 58 | """ 59 | But the command output should not contain: 60 | """ 61 | ContextMaskWarning: user code is masking context attribute 62 | """ 63 | 64 | 65 | Scenario: Exception is raised in a step 66 | Given a file named "features/example0162_exception_raised.feature" with: 67 | """ 68 | Feature: 69 | Scenario: 70 | Given a step passes 71 | When an exception is raised 72 | Then a step passes 73 | 74 | Scenario: 75 | Given a step passes 76 | """ 77 | When I run "behave -f plain features/example0162_exception_raised.feature" 78 | Then it should fail with: 79 | """ 80 | 1 scenario passed, 1 failed, 0 skipped 81 | 2 steps passed, 1 failed, 1 skipped, 0 undefined 82 | """ 83 | But the command output should not contain: 84 | """ 85 | ContextMaskWarning: user code is masking context attribute 86 | """ 87 | -------------------------------------------------------------------------------- /issue.features/issue0035.feature: -------------------------------------------------------------------------------- 1 | @issue 2 | Feature: Issue #35 Plain Formatter shows wrong steps when tag-selection is used 3 | 4 | Background: Test Setup 5 | Given a new working directory 6 | And a file named "features/steps/steps.py" with: 7 | """ 8 | from behave import given, when, then 9 | 10 | @given(u'the ninja has a third level black-belt') 11 | def step(context): 12 | pass 13 | 14 | @when(u'attacked by {opponent}') 15 | def step(context, opponent): 16 | pass 17 | 18 | @then(u'the ninja should {reaction}') 19 | def step(context, reaction): 20 | pass 21 | """ 22 | And a file named "features/issue35_1.feature" with: 23 | """ 24 | Feature: Using Tags with Features and Scenarios 25 | 26 | @one 27 | Scenario: Weaker opponent 28 | Given the ninja has a third level black-belt 29 | When attacked by a samurai 30 | Then the ninja should engage the opponent 31 | 32 | @two 33 | Scenario: Stronger opponent 34 | Given the ninja has a third level black-belt 35 | When attacked by Chuck Norris 36 | Then the ninja should run for his life 37 | """ 38 | 39 | Scenario: Select First Scenario with Tag 40 | When I run "behave --no-timings -f plain --tags=@one features/issue35_1.feature" 41 | Then it should pass with: 42 | """ 43 | 1 feature passed, 0 failed, 0 skipped 44 | 1 scenario passed, 0 failed, 1 skipped 45 | 3 steps passed, 0 failed, 3 skipped, 0 undefined 46 | """ 47 | And the command output should contain: 48 | """ 49 | Feature: Using Tags with Features and Scenarios 50 | Scenario: Weaker opponent 51 | Given the ninja has a third level black-belt ... passed 52 | When attacked by a samurai ... passed 53 | Then the ninja should engage the opponent ... passed 54 | Scenario: Stronger opponent 55 | """ 56 | 57 | Scenario: Select Second Scenario with Tag 58 | When I run "behave --no-timings -f plain --tags=@two features/issue35_1.feature" 59 | Then it should pass with: 60 | """ 61 | 1 feature passed, 0 failed, 0 skipped 62 | 1 scenario passed, 0 failed, 1 skipped 63 | 3 steps passed, 0 failed, 3 skipped, 0 undefined 64 | """ 65 | And the command output should contain: 66 | """ 67 | Feature: Using Tags with Features and Scenarios 68 | Scenario: Weaker opponent 69 | Scenario: Stronger opponent 70 | Given the ninja has a third level black-belt ... passed 71 | When attacked by Chuck Norris ... passed 72 | Then the ninja should run for his life ... passed 73 | """ 74 | 75 | -------------------------------------------------------------------------------- /features/directory_layout.basic.feature: -------------------------------------------------------------------------------- 1 | Feature: Basic directory layout (Variant 1) 2 | 3 | As a story/test writer 4 | I want a simple, non-deep directory structure 5 | So that I can easily get an overview which stories/tests exist 6 | 7 | | BASIC DIRECTORY LAYOUT STRUCTURE: 8 | | features/ 9 | | +-- steps/*.py # Step definitions or step-library imports. 10 | | +-- *.feature # Feature files. 11 | | +-- environment.py # OPTIONAL: environment setup/hooks. 12 | | 13 | | SEE ALSO: 14 | | * http://pythonhosted.org/behave/gherkin.html#layout-variations 15 | 16 | 17 | @setup 18 | Scenario: Setup directory structure 19 | Given a new working directory 20 | And a file named "features/steps/steps.py" with: 21 | """ 22 | from behave import step 23 | 24 | @step('{word:w} step passes') 25 | def step_passes(context, word): 26 | pass 27 | 28 | @step('{word:w} step fails') 29 | def step_fails(context, word): 30 | assert False, "XFAIL-STEP" 31 | """ 32 | And a file named "features/alice.feature" with: 33 | """ 34 | Feature: Alice 35 | Scenario: A1 36 | Given a step passes 37 | When another step passes 38 | Then a step passes 39 | """ 40 | And a file named "features/bob.feature" with: 41 | """ 42 | Feature: Bob 43 | Scenario: B1 44 | When a step passes 45 | Then another step passes 46 | """ 47 | 48 | 49 | Scenario: Run behave with feature directory 50 | When I run "behave -f progress features/" 51 | Then it should pass with: 52 | """ 53 | 2 features passed, 0 failed, 0 skipped 54 | 2 scenarios passed, 0 failed, 0 skipped 55 | 5 steps passed, 0 failed, 0 skipped, 0 undefined 56 | """ 57 | 58 | Scenario: Run behave with one feature file 59 | When I run "behave -f progress features/alice.feature" 60 | Then it should pass with: 61 | """ 62 | 1 feature passed, 0 failed, 0 skipped 63 | 1 scenario passed, 0 failed, 0 skipped 64 | 3 steps passed, 0 failed, 0 skipped, 0 undefined 65 | """ 66 | 67 | 68 | Scenario: Run behave with two feature files 69 | When I run "behave -f progress features/alice.feature features/bob.feature" 70 | Then it should pass with: 71 | """ 72 | 2 features passed, 0 failed, 0 skipped 73 | 2 scenarios passed, 0 failed, 0 skipped 74 | 5 steps passed, 0 failed, 0 skipped, 0 undefined 75 | """ 76 | -------------------------------------------------------------------------------- /features/step_dialect.given_when_then.feature: -------------------------------------------------------------------------------- 1 | Feature: Step Dialect for BDD Steps with Given/When/Then Keywords 2 | 3 | In order to execute a sequence of steps with BDD keywords (Given/When/Then) 4 | As a test/story writer 5 | I want to have the possibility to express myself. 6 | 7 | | NOTE: 8 | | * More details are provided in other features. 9 | 10 | 11 | Scenario: Simple example 12 | 13 | Normally preferred style with BDD keywords. 14 | Note that BDD keywords are dependent on language settings. 15 | 16 | Given a step passes 17 | When a step passes 18 | And a step passes 19 | Then a step passes 20 | And a step passes 21 | But a step passes 22 | 23 | 24 | Scenario: Simple example (with lower-case keywords) 25 | 26 | Alternative style with lower-case BDD keywords. 27 | 28 | given a step passes 29 | when a step passes 30 | and a step passes 31 | then a step passes 32 | and a step passes 33 | but a step passes 34 | 35 | 36 | Scenario: Step usage example with details by running behave 37 | Given a new working directory 38 | And a file named "features/steps/steps.py" with: 39 | """ 40 | from behave import given, when, then, step 41 | 42 | @given('a step passes') 43 | def given_step_passes(context): 44 | pass 45 | 46 | @when('a step passes') 47 | def when_step_passes(context): 48 | pass 49 | 50 | @then('a step passes') 51 | def then_step_passes(context): 52 | pass 53 | 54 | @step('a step passes with "{param}"') 55 | def step_passes_with_param(context, param): 56 | pass 57 | 58 | @step('another step passes') 59 | def step_passes(context): 60 | pass 61 | 62 | @step('another step passes with "{param}"') 63 | def step_passes(context, param): 64 | pass 65 | """ 66 | And a file named "features/basic_steps.feature" with: 67 | """ 68 | Feature: 69 | Scenario: 70 | Given a step passes 71 | And another step passes 72 | When a step passes with "Alice" 73 | Then another step passes with "Bob" 74 | """ 75 | When I run "behave -f plain -T features/basic_steps.feature" 76 | Then it should pass with: 77 | """ 78 | 1 scenario passed, 0 failed, 0 skipped 79 | 4 steps passed, 0 failed, 0 skipped 80 | """ 81 | And the command output should contain: 82 | """ 83 | Feature: 84 | Scenario: 85 | Given a step passes ... passed 86 | And another step passes ... passed 87 | When a step passes with "Alice" ... passed 88 | Then another step passes with "Bob" ... passed 89 | """ -------------------------------------------------------------------------------- /features/step.execute_steps.with_table.feature: -------------------------------------------------------------------------------- 1 | Feature: Execute nested steps that use a table 2 | 3 | Scenario: 4 | Given a new working directory 5 | And a file named "features/steps/steps.py" with: 6 | """ 7 | from behave import given, when, then, step 8 | 9 | @given('the following nested steps') 10 | def step_given_following_nested_steps(context): 11 | assert context.text, "ENSURE: multi-line text is provided." 12 | context.nested_steps = unicode(context.text) 13 | 14 | @step('I execute the nested steps {comment}') 15 | def step_execute_nested_steps_with_table(context, comment): 16 | assert context.nested_steps, "ENSURE: nested steps are provided." 17 | context.execute_steps(context.nested_steps) 18 | 19 | @then('the step "{expected_step}" was called') 20 | def then_step_was_called(context, expected_step): 21 | assert context.steps_called, "ENSURE: steps_called is provided." 22 | assert expected_step in context.steps_called 23 | 24 | @then('the table should be equal to') 25 | def then_table_should_be_equal_to(context): 26 | assert context.table, "ENSURE: table is provided." 27 | expected_table = context.table 28 | actual_table = context.the_table 29 | assert actual_table == expected_table 30 | 31 | # -- SPECIAL-STEP: 32 | @step('I setup an address book with') 33 | def step_setup_address_book_with_friends(context): 34 | assert context.table, "ENSURE: table is provided." 35 | if not hasattr(context, "steps_called"): 36 | context.steps_called = [] 37 | context.steps_called.append("I setup an address book with") 38 | context.the_table = context.table 39 | """ 40 | And a file named "features/execute_nested_steps_with_table.feature" with: 41 | ''' 42 | Feature: 43 | Scenario: 44 | Given the following nested steps: 45 | """ 46 | When I setup an address book with: 47 | | Name | Telephone Number | 48 | | Alice | 555 1111 | 49 | | Bob | 555 2222 | 50 | """ 51 | When I execute the nested steps with a table 52 | Then the step "I setup an address book with" was called 53 | And the table should be equal to: 54 | | Name | Telephone Number | 55 | | Alice | 555 1111 | 56 | | Bob | 555 2222 | 57 | ''' 58 | When I run "behave -f plain features/execute_nested_steps_with_table.feature" 59 | Then it should pass with: 60 | """ 61 | 1 feature passed, 0 failed, 0 skipped 62 | 1 scenario passed, 0 failed, 0 skipped 63 | 4 steps passed, 0 failed, 0 skipped, 0 undefined 64 | """ 65 | 66 | -------------------------------------------------------------------------------- /issue.features/issue0066.feature: -------------------------------------------------------------------------------- 1 | @issue 2 | Feature: Issue #66: context.text and context.table are not cleared 3 | 4 | I noticed that context.table and context.text survive after the step is finished. 5 | 6 | 7 | Background: Test Setup 8 | Given a new working directory 9 | And a file named "features/steps/steps.py" with: 10 | """ 11 | from behave import given, when, then 12 | from hamcrest import assert_that, equal_to, is_not, is_, none 13 | 14 | @given(u'a step with multiline text') 15 | def step(context): 16 | assert context.text is not None 17 | assert context.text, "Ensure non-empty" 18 | assert isinstance(context.text, basestring) 19 | 20 | @given(u'a step with a table') 21 | def step(context): 22 | assert context.table is not None 23 | 24 | @when(u'I check the "context.{name}" attribute') 25 | def step(context, name): 26 | context.name = name 27 | context.value = getattr(context, name, None) 28 | 29 | @then(u'its value is None') 30 | def step(context): 31 | assert_that(context.value, is_(none())) 32 | 33 | @then(u'its value is "{value}"') 34 | def step(context, value): 35 | assert_that(context.value, equal_to(value)) 36 | 37 | @then(u'its value is not "{value}"') 38 | def step(context, value): 39 | assert_that(value, is_not(equal_to(context.value))) 40 | """ 41 | 42 | Scenario: Ensure multiline text data is cleared for next step 43 | Given a file named "features/issue66_case1.feature" with: 44 | """ 45 | Feature: 46 | Scenario: 47 | Given a step with multiline text: 48 | ''' 49 | Alice, Bob and Charly 50 | ''' 51 | When I check the "context.text" attribute 52 | Then its value is not "Alice, Bob and Charly" 53 | But its value is None 54 | """ 55 | When I run "behave -f plain features/issue66_case1.feature" 56 | Then it should pass with: 57 | """ 58 | 1 scenario passed, 0 failed, 0 skipped 59 | 4 steps passed, 0 failed, 0 skipped, 0 undefined 60 | """ 61 | 62 | Scenario: Ensure table data is cleared for next step 63 | Given a file named "features/issue66_case2.feature" with: 64 | """ 65 | Feature: 66 | Scenario: 67 | Given a step with a table: 68 | | name | gender | 69 | | Alice | female | 70 | | Bob | male | 71 | When I check the "context.table" attribute 72 | Then its value is None 73 | """ 74 | When I run "behave -f plain features/issue66_case2.feature" 75 | Then it should pass with: 76 | """ 77 | 1 scenario passed, 0 failed, 0 skipped 78 | 3 steps passed, 0 failed, 0 skipped, 0 undefined 79 | """ 80 | -------------------------------------------------------------------------------- /test/test_ansi_escapes.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | # pylint: disable=C0103,R0201,W0401,W0614,W0621 3 | # C0103 Invalid name (setUp(), ...) 4 | # R0201 Method could be a function 5 | # W0401 Wildcard import 6 | # W0614 Unused import ... from wildcard import 7 | # W0621 Redefining name ... from outer scope 8 | 9 | from nose import tools 10 | from behave.formatter import ansi_escapes 11 | import unittest 12 | 13 | class StripEscapesTest(unittest.TestCase): 14 | ALL_COLORS = ansi_escapes.colors.keys() 15 | CURSOR_UPS = [ ansi_escapes.up(count) for count in range(10) ] 16 | TEXTS = [ 17 | u"lorem ipsum", 18 | u"Alice\nBob\nCharly\nDennis", 19 | ] 20 | 21 | @classmethod 22 | def colorize(cls, text, color): 23 | color_escape = "" 24 | if color: 25 | color_escape = ansi_escapes.colors[color] 26 | return color_escape + text + ansi_escapes.escapes["reset"] 27 | 28 | @classmethod 29 | def colorize_text(cls, text, colors=None): 30 | if not colors: 31 | colors = [] 32 | colors_size = len(colors) 33 | color_index = 0 34 | colored_chars = [] 35 | for char in text: 36 | color = colors[color_index] 37 | colored_chars.append(cls.colorize(char, color)) 38 | color_index += 1 39 | if color_index >= colors_size: 40 | color_index = 0 41 | return "".join(colored_chars) 42 | 43 | def test_should_return_same_text_without_escapes(self): 44 | for text in self.TEXTS: 45 | tools.eq_(text, ansi_escapes.strip_escapes(text)) 46 | 47 | def test_should_return_empty_string_for_any_ansi_escape(self): 48 | for text in ansi_escapes.colors.values(): 49 | tools.eq_("", ansi_escapes.strip_escapes(text)) 50 | for text in ansi_escapes.escapes.values(): 51 | tools.eq_("", ansi_escapes.strip_escapes(text)) 52 | 53 | 54 | def test_should_strip_color_escapes_from_text(self): 55 | for text in self.TEXTS: 56 | colored_text = self.colorize_text(text, self.ALL_COLORS) 57 | tools.eq_(text, ansi_escapes.strip_escapes(colored_text)) 58 | self.assertNotEqual(text, colored_text) 59 | 60 | for color in self.ALL_COLORS: 61 | colored_text = self.colorize(text, color) 62 | tools.eq_(text, ansi_escapes.strip_escapes(colored_text)) 63 | self.assertNotEqual(text, colored_text) 64 | 65 | def test_should_strip_cursor_up_escapes_from_text(self): 66 | for text in self.TEXTS: 67 | for cursor_up in self.CURSOR_UPS: 68 | colored_text = cursor_up + text + ansi_escapes.escapes["reset"] 69 | tools.eq_(text, ansi_escapes.strip_escapes(colored_text)) 70 | self.assertNotEqual(text, colored_text) 71 | -------------------------------------------------------------------------------- /docs/parse_builtin_types.rst: -------------------------------------------------------------------------------- 1 | .. _id.appendix.parse_builtin_types: 2 | 3 | Predefined Data Types in ``parse`` 4 | ============================================================================== 5 | 6 | `behave`_ uses the `parse`_ module (inverse of Python `string.format`_) 7 | under the hoods to parse parameters in step definitions. 8 | This leads to rather simple and readable parse expressions for step parameters. 9 | 10 | .. code:: python 11 | 12 | # -- FILE: features/steps/type_transform_example_steps.py 13 | from behave import given 14 | 15 | @given('I have {number:d} friends') #< Convert 'number' into int type. 16 | def step_given_i_have_number_friends(context, number): 17 | assert number > 0 18 | ... 19 | 20 | Therefore, the following ``parse types`` are already supported 21 | in step definitions without registration of any *user-defined type*: 22 | 23 | 24 | ===== =========================================== ============ 25 | Type Characters Matched Output Type 26 | ===== =========================================== ============ 27 | w Letters and underscore str 28 | W Non-letter and underscore str 29 | s Whitespace str 30 | S Non-whitespace str 31 | d Digits (effectively integer numbers) int 32 | D Non-digit str 33 | n Numbers with thousands separators (, or .) int 34 | % Percentage (converted to value/100.0) float 35 | f Fixed-point numbers float 36 | e Floating-point numbers with exponent float 37 | e.g. 1.1e-10, NAN (all case insensitive) 38 | g General number format (either d, f or e) float 39 | b Binary numbers int 40 | o Octal numbers int 41 | x Hexadecimal numbers (lower and upper case) int 42 | ti ISO 8601 format date/time datetime 43 | e.g. 1972-01-20T10:21:36Z 44 | te RFC2822 e-mail format date/time datetime 45 | e.g. Mon, 20 Jan 1972 10:21:36 +1000 46 | tg Global (day/month) format date/time datetime 47 | e.g. 20/1/1972 10:21:36 AM +1:00 48 | ta US (month/day) format date/time datetime 49 | e.g. 1/20/1972 10:21:36 PM +10:30 50 | tc ctime() format date/time datetime 51 | e.g. Sun Sep 16 01:03:52 1973 52 | th HTTP log format date/time datetime 53 | e.g. 21/Nov/2011:00:07:11 +0000 54 | tt Time time 55 | e.g. 10:21:36 PM -5:30 56 | ===== =========================================== ============ 57 | 58 | 59 | .. _behave: http://pypi.python.org/pypi/behave 60 | .. _parse: http://pypi.python.org/pypi/parse 61 | .. _string.format: http://docs.python.org/library/string.html#format-string-syntax -------------------------------------------------------------------------------- /behave/importer.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | """ 3 | Importer module for lazy-loading/importing modules and objects. 4 | 5 | REQUIRES: importlib (provided in Python2.7, Python3.2...) 6 | """ 7 | 8 | from behave.compat import importlib 9 | 10 | 11 | class Unknown(object): 12 | pass 13 | 14 | 15 | class LazyObject(object): 16 | """ 17 | Provides a placeholder for an object that should be loaded lazily. 18 | """ 19 | 20 | def __init__(self, module_name, object_name=None): 21 | if ":" in module_name and not object_name: 22 | module_name, object_name = module_name.split(":") 23 | assert ":" not in module_name 24 | self.module_name = module_name 25 | self.object_name = object_name 26 | self.obj = None 27 | 28 | @staticmethod 29 | def load_module(module_name): 30 | return importlib.import_module(module_name) 31 | 32 | def __get__(self, obj=None, type=None): 33 | """ 34 | Implement descriptor protocol, 35 | useful if this class is used as attribute. 36 | :return: Real object (lazy-loaded if necessary). 37 | :raise ImportError: If module or object cannot be imported. 38 | """ 39 | __pychecker__ = "unusednames=obj,type" 40 | if not self.obj: 41 | # -- SETUP-ONCE: Lazy load the real object. 42 | module = self.load_module(self.module_name) 43 | obj = getattr(module, self.object_name, Unknown) 44 | if obj is Unknown: 45 | msg = "%s: %s is Unknown" % (self.module_name, self.object_name) 46 | raise ImportError(msg) 47 | self.obj = obj 48 | return obj 49 | 50 | def __set__(self, obj, value): 51 | """ 52 | Implement descriptor protocol. 53 | """ 54 | __pychecker__ = "unusednames=obj" 55 | self.obj = value 56 | 57 | def get(self): 58 | return self.__get__() 59 | 60 | 61 | class LazyDict(dict): 62 | """ 63 | Provides a dict that supports lazy loading of objects. 64 | A LazyObject is provided as placeholder for a value that should be 65 | loaded lazily. 66 | """ 67 | 68 | def __getitem__(self, key): 69 | """ 70 | Provides access to stored dict values. 71 | Implements lazy loading of item value (if necessary). 72 | When lazy object is loaded, its value with the dict is replaced 73 | with the real value. 74 | 75 | :param key: Key to access the value of an item in the dict. 76 | :return: value 77 | :raises: KeyError if item is not found 78 | :raises: ImportError for a LazyObject that cannot be imported. 79 | """ 80 | value = dict.__getitem__(self, key) 81 | if isinstance(value, LazyObject): 82 | # -- LAZY-LOADING MECHANISM: Load object and replace with lazy one. 83 | value = value.__get__() 84 | self[key] = value 85 | return value 86 | -------------------------------------------------------------------------------- /behave/formatter/ansi_escapes.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | """ 3 | Provides ANSI escape sequences for coloring/formatting output in ANSI terminals. 4 | """ 5 | 6 | import os 7 | import re 8 | 9 | colors = { 10 | 'black': u"\x1b[30m", 11 | 'red': u"\x1b[31m", 12 | 'green': u"\x1b[32m", 13 | 'yellow': u"\x1b[33m", 14 | 'blue': u"\x1b[34m", 15 | 'magenta': u"\x1b[35m", 16 | 'cyan': u"\x1b[36m", 17 | 'white': u"\x1b[37m", 18 | 'grey': u"\x1b[90m", 19 | 'bold': u"\x1b[1m", 20 | } 21 | 22 | aliases = { 23 | 'undefined': 'yellow', 24 | 'pending': 'yellow', 25 | 'executing': 'grey', 26 | 'failed': 'red', 27 | 'passed': 'green', 28 | 'outline': 'cyan', 29 | 'skipped': 'cyan', 30 | 'comments': 'grey', 31 | 'tag': 'cyan', 32 | } 33 | 34 | escapes = { 35 | 'reset': u'\x1b[0m', 36 | 'up': u'\x1b[1A', 37 | } 38 | 39 | if 'GHERKIN_COLORS' in os.environ: 40 | new_aliases = [p.split('=') for p in os.environ['GHERKIN_COLORS'].split(':')] 41 | aliases.update(dict(new_aliases)) 42 | 43 | for alias in aliases: 44 | escapes[alias] = ''.join([colors[c] for c in aliases[alias].split(',')]) 45 | arg_alias = alias + '_arg' 46 | arg_seq = aliases.get(arg_alias, aliases[alias] + ',bold') 47 | escapes[arg_alias] = ''.join([colors[c] for c in arg_seq.split(',')]) 48 | 49 | 50 | def up(n): 51 | return u"\x1b[%dA" % n 52 | 53 | _ANSI_ESCAPE_PATTERN = re.compile(u"\x1b\[\d+[mA]", re.UNICODE) 54 | def strip_escapes(text): 55 | """ 56 | Removes ANSI escape sequences from text (if any are contained). 57 | 58 | :param text: Text that may or may not contain ANSI escape sequences. 59 | :return: Text without ANSI escape sequences. 60 | """ 61 | return _ANSI_ESCAPE_PATTERN.sub("", text) 62 | 63 | 64 | def use_ansi_escape_colorbold_composites(): # pragma: no cover 65 | """ 66 | Patch for "sphinxcontrib-ansi" to process the following ANSI escapes 67 | correctly (set-color set-bold sequences): 68 | 69 | ESC[{color}mESC[1m => ESC[{color};1m 70 | 71 | Reapply aliases to ANSI escapes mapping. 72 | """ 73 | global escapes 74 | color_codes = {} 75 | for color_name, color_escape in colors.items(): 76 | color_code = color_escape.replace(u"\x1b[", u"").replace(u"m", u"") 77 | color_codes[color_name] = color_code 78 | 79 | for alias in aliases: 80 | parts = [ color_codes[c] for c in aliases[alias].split(',') ] 81 | composite_escape = u"\x1b[{0}m".format(u";".join(parts)) 82 | escapes[alias] = composite_escape 83 | 84 | arg_alias = alias + '_arg' 85 | arg_seq = aliases.get(arg_alias, aliases[alias] + ',bold') 86 | parts = [ color_codes[c] for c in arg_seq.split(',') ] 87 | composite_escape = u"\x1b[{0}m".format(u";".join(parts)) 88 | escapes[arg_alias] = composite_escape 89 | -------------------------------------------------------------------------------- /docs/formatters.rst: -------------------------------------------------------------------------------- 1 | .. _id.appendix.formatters: 2 | 3 | ============================================================================== 4 | Formatters and Reporters 5 | ============================================================================== 6 | 7 | `behave`_ provides 2 different concepts for reporting results of a test run: 8 | 9 | * formatters 10 | * reporters 11 | 12 | A slightly different interface is provided for each "formatter" concept. 13 | The ``Formatter`` is informed about each step that is taken. 14 | The ``Reporter`` has a more coarse-grained API. 15 | 16 | 17 | Formatters 18 | ------------------------------------------------------------------------------ 19 | 20 | The following formatters are currently supported: 21 | 22 | ============== ======== ================================================================ 23 | Name Mode Description 24 | ============== ======== ================================================================ 25 | help normal Shows all registered formatters. 26 | json normal JSON dump of test run 27 | json.pretty normal JSON dump of test run (human readable) 28 | plain normal Very basic formatter with maximum compatibility 29 | pretty normal Standard colourised pretty formatter 30 | progress normal Shows dotted progress for each executed scenario. 31 | progress2 normal Shows dotted progress for each executed step. 32 | progress3 normal Shows detailed progress for each step of a scenario. 33 | rerun normal Emits scenario file locations of failing scenarios 34 | sphinx.steps dry-run Generate sphinx-based documentation for step definitions. 35 | steps dry-run Shows step definitions (step implementations). 36 | steps.doc dry-run Shows documentation for step definitions. 37 | steps.usage dry-run Shows how step definitions are used by steps (in feature files). 38 | tags dry-run Shows tags (and how often they are used). 39 | tags.location dry-run Shows tags and the location where they are used. 40 | ============== ======== ================================================================ 41 | 42 | .. note:: 43 | 44 | You can use more that one formatter during a test run. 45 | But in general you have only one formatter that writes to ``stdout``. 46 | 47 | The "Mode" column indicates if a formatter is intended to be used in 48 | dry-run (``--dry-run`` command-line option) or normal mode. 49 | 50 | 51 | Reporters 52 | ------------------------------------------------------------------------------ 53 | 54 | The following reporters are currently supported: 55 | 56 | ============== ================================================================ 57 | Name Description 58 | ============== ================================================================ 59 | junit Provides JUnit XML-like output. 60 | summary Provides a summary of the test run. 61 | ============== ================================================================ 62 | 63 | .. _behave: http://pypi.python.org/pypi/behave 64 | -------------------------------------------------------------------------------- /features/steps/context_steps.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | """ 3 | Step definition for Context object tests. 4 | 5 | EXAMPLE 6 | Scenario: Show that Context parameter 7 | Given I set the parameter "person" to "Alice" in the behave context 8 | Then the behave context should have a parameter named "person" 9 | And the behave context object should contain: 10 | | Parameter | Value | 11 | | person | "Alice" | 12 | 13 | Scenario: Show that Context parameter are not present in next scenario 14 | Then the behave context should not have a parameter named "person" 15 | """ 16 | 17 | from behave import given, when, then, step 18 | from hamcrest import assert_that, equal_to 19 | 20 | # ----------------------------------------------------------------------------- 21 | # STEPS: 22 | # ----------------------------------------------------------------------------- 23 | @step(u'I set the context parameter "{param_name}" to "{value}"') 24 | def step_set_behave_context_parameter_to(context, param_name, value): 25 | setattr(context, param_name, value) 26 | 27 | @step(u'the parameter "{param_name}" exists in the behave context') 28 | def step_behave_context_parameter_exists(context, param_name): 29 | assert hasattr(context, param_name) 30 | 31 | @step(u'the parameter "{param_name}" does not exist in the behave context') 32 | def step_behave_context_parameter_not_exists(context, param_name): 33 | assert not hasattr(context, param_name) 34 | 35 | @given(u'the behave context has a parameter "{param_name}"') 36 | def given_behave_context_has_parameter_named(context, param_name): 37 | step_behave_context_parameter_exists(context, param_name) 38 | 39 | @given(u'the behave context does not have a parameter "{param_name}"') 40 | def given_behave_context_does_not_have_parameter_named(context, param_name): 41 | step_behave_context_parameter_not_exists(context, param_name) 42 | 43 | @step(u'the behave context should have a parameter "{param_name}"') 44 | def step_behave_context_should_have_parameter_named(context, param_name): 45 | step_behave_context_parameter_exists(context, param_name) 46 | 47 | @step(u'the behave context should not have a parameter "{param_name}"') 48 | def step_behave_context_should_not_have_parameter_named(context, param_name): 49 | step_behave_context_parameter_not_exists(context, param_name) 50 | 51 | @then(u'the behave context should contain') 52 | def then_behave_context_should_contain_with_table(context): 53 | assert context.table, "ENSURE: table is provided." 54 | for row in context.table.rows: 55 | param_name = row["Parameter"] 56 | param_value = row["Value"] 57 | if param_value.startswith('"') and param_value.endswith('"'): 58 | param_value = param_value[1:-1] 59 | actual = str(getattr(context, param_name, None)) 60 | assert hasattr(context, param_name) 61 | assert_that(actual, equal_to(param_value)) 62 | 63 | @given(u'the behave context contains') 64 | def given_behave_context_contains_with_table(context): 65 | then_behave_context_should_contain_with_table(context) -------------------------------------------------------------------------------- /test/test_tag_expression.py: -------------------------------------------------------------------------------- 1 | from nose import tools 2 | 3 | from behave.tag_expression import TagExpression 4 | 5 | class TestTagExpressionNoTags(object): 6 | def setUp(self): 7 | self.e = TagExpression([]) 8 | 9 | def test_should_match_foo(self): 10 | assert self.e.check(['foo']) 11 | 12 | def test_should_match_empty_tags(self): 13 | assert self.e.check([]) 14 | 15 | class TestTagExpressionFoo(object): 16 | def setUp(self): 17 | self.e = TagExpression(['foo']) 18 | 19 | def test_should_match_foo(self): 20 | assert self.e.check(['foo']) 21 | 22 | def test_should_not_match_bar(self): 23 | assert not self.e.check(['bar']) 24 | 25 | def test_should_not_match_no_tags(self): 26 | assert not self.e.check([]) 27 | 28 | class TestTagExpressionNotFoo(object): 29 | def setUp(self): 30 | self.e = TagExpression(['-foo']) 31 | 32 | def test_should_match_bar(self): 33 | assert self.e.check(['bar']) 34 | 35 | def test_should_not_match_foo(self): 36 | assert not self.e.check(['foo']) 37 | 38 | class TestTagExpressionFooOrBar(object): 39 | def setUp(self): 40 | self.e = TagExpression(['foo,bar']) 41 | 42 | def test_should_match_foo(self): 43 | assert self.e.check(['foo']) 44 | 45 | def test_should_match_bar(self): 46 | assert self.e.check(['bar']) 47 | 48 | def test_should_not_match_zap(self): 49 | assert not self.e.check(['zap']) 50 | 51 | class TestTagExpressionFooOrBarAndNotZap(object): 52 | def setUp(self): 53 | self.e = TagExpression(['foo,bar', '-zap']) 54 | 55 | def test_should_match_foo(self): 56 | assert self.e.check(['foo']) 57 | 58 | def test_should_not_match_foo_zap(self): 59 | assert not self.e.check(['foo', 'zap']) 60 | 61 | class TestTagExpressionFoo3OrNotBar4AndZap5(object): 62 | def setUp(self): 63 | self.e = TagExpression(['foo:3,-bar', 'zap:5']) 64 | 65 | def test_should_count_tags_for_positive_tags(self): 66 | tools.eq_(self.e.limits, {'foo': 3, 'zap': 5}) 67 | 68 | def test_should_match_foo_zap(self): 69 | assert self.e.check(['foo', 'zap']) 70 | 71 | class TestTagExpressionParsing(object): 72 | def setUp(self): 73 | self.e = TagExpression([' foo:3 , -bar ', ' zap:5 ']) 74 | 75 | def test_should_have_limits(self): 76 | tools.eq_(self.e.limits, {'zap': 5, 'foo': 3}) 77 | 78 | class TestTagExpressionTagLimits(object): 79 | def test_should_be_counted_for_negative_tags(self): 80 | e = TagExpression(['-todo:3']) 81 | tools.eq_(e.limits, {'todo': 3}) 82 | 83 | def test_should_be_counted_for_positive_tags(self): 84 | e = TagExpression(['todo:3']) 85 | tools.eq_(e.limits, {'todo': 3}) 86 | 87 | def test_should_raise_an_error_for_inconsistent_limits(self): 88 | tools.assert_raises(Exception, TagExpression, ['todo:3', '-todo:4']) 89 | 90 | def test_should_allow_duplicate_consistent_limits(self): 91 | e = TagExpression(['todo:3', '-todo:3']) 92 | tools.eq_(e.limits, {'todo': 3}) 93 | 94 | -------------------------------------------------------------------------------- /issue.features/issue0064.feature: -------------------------------------------------------------------------------- 1 | @issue 2 | Feature: Issue #64 Exit status not set to 1 even there are failures in certain cases 3 | 4 | The behave exit status not always returns 1 when failure(s) occur. 5 | The problem was associated with the Feature.run() logic implementation. 6 | 7 | This problem was first discovered while verifying issue #52 (see comments). 8 | See also similar test when tags select a subset of scenarios. 9 | 10 | RELATED ISSUES: 11 | * issue #52 12 | 13 | Background: Test Setup 14 | Given a new working directory 15 | Given a file named "features/steps/steps.py" with: 16 | """ 17 | from behave import given 18 | 19 | @given(u'passing') 20 | def step(context): 21 | pass 22 | 23 | @given(u'failing') 24 | def step(context): 25 | assert False, "failing" 26 | """ 27 | 28 | Scenario: Failing in First Scenario 29 | Given a file named "features/issue64_case1.feature" with: 30 | """ 31 | Feature: Failing in First Scenario 32 | Scenario: 33 | Given failing 34 | 35 | Scenario: 36 | Given passing 37 | """ 38 | When I run "behave --format=plain features/issue64_case1.feature" 39 | Then it should fail with: 40 | """ 41 | 0 features passed, 1 failed, 0 skipped 42 | 1 scenario passed, 1 failed, 0 skipped 43 | """ 44 | 45 | Scenario: Failing in Middle Scenario 46 | Given a file named "features/issue64_case2.feature" with: 47 | """ 48 | Feature: Failing in Middle Scenario 49 | Scenario: 50 | Given passing 51 | Scenario: 52 | Given failing 53 | Scenario: 54 | Given passing 55 | """ 56 | When I run "behave --format=plain features/issue64_case2.feature" 57 | Then it should fail with: 58 | """ 59 | 0 features passed, 1 failed, 0 skipped 60 | 2 scenarios passed, 1 failed, 0 skipped 61 | """ 62 | 63 | Scenario: Failing in Last Scenario 64 | Given a file named "features/issue64_case3.feature" with: 65 | """ 66 | Feature: Failing in Last Scenario 67 | Scenario: 68 | Given passing 69 | Scenario: 70 | Given passing 71 | Scenario: 72 | Given failing 73 | """ 74 | When I run "behave --format=plain features/issue64_case3.feature" 75 | Then it should fail with: 76 | """ 77 | 0 features passed, 1 failed, 0 skipped 78 | 2 scenarios passed, 1 failed, 0 skipped 79 | """ 80 | 81 | Scenario: Failing in First and Last Scenario 82 | Given a file named "features/issue64_case4.feature" with: 83 | """ 84 | Feature: Failing in First and Last Scenario 85 | Scenario: 86 | Given failing 87 | Scenario: 88 | Given passing 89 | Scenario: 90 | Given failing 91 | """ 92 | When I run "behave --format=plain features/issue64_case4.feature" 93 | Then it should fail with: 94 | """ 95 | 0 features passed, 1 failed, 0 skipped 96 | 1 scenario passed, 2 failed, 0 skipped 97 | """ 98 | -------------------------------------------------------------------------------- /features/runner.select_files_by_regexp.feature: -------------------------------------------------------------------------------- 1 | @sequential 2 | Feature: Select feature files by using regular expressions 3 | 4 | As a tester 5 | I want to include/exclude feature files into/from a test run by using wildcards 6 | To be more flexible and avoid to specify all feature files 7 | 8 | | SPECIFICATION: 9 | | * behave provides --include and --exclude command line options 10 | | * --include option selects a subset of all files that should be included 11 | | * --exclude option is applied after include option is applied 12 | | 13 | | EXAMPLE: 14 | | behave --include="features/ali.*\.feature" ... 15 | | behave --exclude="features/ali.*" ... 16 | 17 | 18 | Background: 19 | Given behave has the following feature fileset: 20 | """ 21 | features/alice.feature 22 | features/bob.feature 23 | features/barbi.feature 24 | """ 25 | 26 | Scenario: Include only feature files 27 | When behave includes feature files with "features/a.*" 28 | And behave excludes no feature files 29 | Then the following feature files are selected: 30 | """ 31 | features/alice.feature 32 | """ 33 | 34 | Scenario: Include more than one feature file 35 | When behave includes feature files with "features/b.*" 36 | Then the following feature files are selected: 37 | """ 38 | features/bob.feature 39 | features/barbi.feature 40 | """ 41 | 42 | 43 | Scenario: Exclude only feature files 44 | When behave excludes feature files with "features/a.*" 45 | And behave includes all feature files 46 | Then the following feature files are selected: 47 | """ 48 | features/bob.feature 49 | features/barbi.feature 50 | """ 51 | 52 | Scenario: Exclude more than one feature file 53 | When behave excludes feature files with "features/b.*" 54 | Then the following feature files are selected: 55 | """ 56 | features/alice.feature 57 | """ 58 | 59 | Scenario: Include and exclude feature files 60 | 61 | Ensure that exclude file pattern is applied after include file pattern. 62 | 63 | When behave includes feature files with "features/.*a.*\.feature" 64 | And behave excludes feature files with ".*/barbi.*" 65 | Then the following feature files are selected: 66 | """ 67 | features/alice.feature 68 | """ 69 | 70 | Scenario: Include and exclude feature files (in 2 steps) 71 | 72 | Show how file inclusion/exclusion works by emulating the two steps. 73 | 74 | When behave includes feature files with "features/.*a.*\.feature" 75 | Then the following feature files are selected: 76 | """ 77 | features/alice.feature 78 | features/barbi.feature 79 | """ 80 | When behave excludes feature files with ".*/barbi.*" 81 | Then the following feature files are selected: 82 | """ 83 | features/alice.feature 84 | """ 85 | -------------------------------------------------------------------------------- /issue.features/issue0067.feature: -------------------------------------------------------------------------------- 1 | @issue 2 | Feature: Issue #67: JSON formatter cannot serialize tables. 3 | 4 | The JSON formatter cannot handle tables (currently): 5 | 6 | * Table as setup/intermediate/result table in steps of scenario 7 | * Examples tables in a ScenarioOutline 8 | 9 | A JSON exception occurs when such a feature file should be processed. 10 | 11 | 12 | Scenario: Scenario with Tables 13 | Given a new working directory 14 | And a file named "features/steps/steps1.py" with: 15 | """ 16 | from behave import given, when, then 17 | 18 | @given(u'I add the following employees') 19 | def step(context): 20 | pass # -- SKIP: Table processing here. 21 | 22 | @when(u'I select department "{department}"') 23 | def step(context, department): 24 | context.department = department 25 | 26 | @then(u'I get the following employees') 27 | def step(context): 28 | pass # -- SKIP: Table processing here. 29 | """ 30 | And a file named "features/issue67_case1.feature" with: 31 | """ 32 | Feature: Scenario with Tables 33 | Scenario: 34 | Given I add the following employees: 35 | | name | department | 36 | | Alice | Wonderland | 37 | | Bob | Moonwalk | 38 | When I select department "Wonderland" 39 | Then I get the following employees: 40 | | name | department | 41 | | Alice | Wonderland | 42 | """ 43 | When I run "behave -f json features/issue67_case1.feature" 44 | Then it should pass with: 45 | """ 46 | 1 scenario passed, 0 failed, 0 skipped 47 | 3 steps passed, 0 failed, 0 skipped, 0 undefined 48 | """ 49 | But the command output should not contain: 50 | """ 51 | TypeError: is not JSON serializable 52 | """ 53 | 54 | Scenario: ScenarioOutline with Examples Table 55 | Given a file named "features/steps/steps2.py" with: 56 | """ 57 | from behave import given, when, then 58 | 59 | @given(u'a step with "{name}"') 60 | def step(context, name): 61 | context.name = name 62 | 63 | @when(u'a step with "{name}" occurs') 64 | def step(context, name): 65 | assert context.name == name 66 | 67 | @then(u'a step with "{name}" is reached') 68 | def step(context, name): 69 | assert context.name == name 70 | """ 71 | And a file named "features/issue67_case2.feature" with: 72 | """ 73 | Feature: ScenarioOutline with Examples Table 74 | Scenario Outline: 75 | Given a step with "" 76 | When a step with "" occurs 77 | Then a step with "" is reached 78 | 79 | Examples: 80 | |name | 81 | |Alice| 82 | |Bob | 83 | """ 84 | When I run "behave -f json features/issue67_case2.feature" 85 | Then it should pass with: 86 | """ 87 | 2 scenarios passed, 0 failed, 0 skipped 88 | 6 steps passed, 0 failed, 0 skipped, 0 undefined 89 | """ 90 | 91 | -------------------------------------------------------------------------------- /issue.features/issue0077.feature: -------------------------------------------------------------------------------- 1 | @issue 2 | Feature: Issue #77: Does not capture stdout from sub-processes 3 | 4 | | My step functions are using wrapper objects to interact with SUT. 5 | | Those wrappers use this kind of thing to invoke executables: 6 | | 7 | | subprocess.check_call('myprog', ..., stderr=subprocess.STDOUT) 8 | | 9 | | However, the output from those calls does not appear in the stdout 10 | | captured by behave when a step fails. 11 | 12 | 13 | Background: Test Setup 14 | Given a new working directory 15 | Given a file named "hello.py" with: 16 | """ 17 | import sys 18 | 19 | def hello(): 20 | result = 0 21 | args = sys.argv[1:] 22 | if args and args[0].startswith("--fail"): 23 | result = 1 24 | args = args[1:] 25 | message = " ".join(args) 26 | sys.stdout.write("Hello {0}\n".format(message)) 27 | sys.exit(result) 28 | 29 | if __name__ == "__main__": 30 | hello() 31 | """ 32 | And a file named "features/steps/subprocess_call_steps.py" with: 33 | """ 34 | from behave import given, when, then 35 | import subprocess 36 | import os.path 37 | import sys 38 | 39 | PYTHON = sys.executable 40 | HERE = os.path.dirname(__file__) 41 | 42 | @when(u'I make a subprocess call "hello {commandline}"') 43 | def step(context, commandline): 44 | result = subprocess_call_hello(commandline.split()) 45 | assert result == 0 46 | 47 | def subprocess_call_hello(args): 48 | command_args = [ PYTHON, "hello.py" ] + args 49 | result = subprocess.check_call(command_args, stderr=subprocess.STDOUT) 50 | return result 51 | # result = subprocess.check_output(command_args, stderr=subprocess.STDOUT) 52 | # return result 53 | """ 54 | 55 | Scenario: Subprocess call shows generated output 56 | Given a file named "features/issue77_hello_OK.feature" with: 57 | """ 58 | Feature: 59 | Scenario: 60 | When I make a subprocess call "hello world." 61 | """ 62 | When I run "behave -f plain features/issue77_hello_OK.feature" 63 | Then it should pass with: 64 | """ 65 | 1 scenario passed, 0 failed, 0 skipped 66 | 1 step passed, 0 failed, 0 skipped, 0 undefined 67 | """ 68 | And the command output should contain: 69 | """ 70 | Hello world. 71 | """ 72 | 73 | Scenario: Subprocess call fails with captured output 74 | Given a file named "features/issue77_hello_FAIL.feature" with: 75 | """ 76 | Feature: 77 | Scenario: 78 | When I make a subprocess call "hello --fail FAIL." 79 | """ 80 | When I run "behave -f plain features/issue77_hello_FAIL.feature" 81 | Then it should fail with: 82 | """ 83 | 0 scenarios passed, 1 failed, 0 skipped 84 | 0 steps passed, 1 failed, 0 skipped, 0 undefined 85 | """ 86 | And the command output should contain: 87 | """ 88 | Hello FAIL. 89 | """ 90 | -------------------------------------------------------------------------------- /test/test_step_registry.py: -------------------------------------------------------------------------------- 1 | from __future__ import with_statement 2 | 3 | from mock import Mock, patch 4 | from nose.tools import * 5 | from behave import step_registry 6 | 7 | class TestStepRegistry(object): 8 | def test_add_step_definition_adds_to_lowercased_keyword(self): 9 | registry = step_registry.StepRegistry() 10 | with patch('behave.matchers.get_matcher') as get_matcher: 11 | func = lambda x: -x 12 | string = 'just a test string' 13 | magic_object = object() 14 | get_matcher.return_value = magic_object 15 | 16 | for step_type in registry.steps.keys(): 17 | l = [] 18 | registry.steps[step_type] = l 19 | 20 | registry.add_step_definition(step_type.upper(), string, func) 21 | 22 | get_matcher.assert_called_with(func, string) 23 | eq_(l, [magic_object]) 24 | 25 | def test_find_match_with_specific_step_type_also_searches_generic(self): 26 | registry = step_registry.StepRegistry() 27 | 28 | given_mock = Mock() 29 | given_mock.match.return_value = None 30 | step_mock = Mock() 31 | step_mock.match.return_value = None 32 | 33 | registry.steps['given'].append(given_mock) 34 | registry.steps['step'].append(step_mock) 35 | 36 | step = Mock() 37 | step.step_type = 'given' 38 | step.name = 'just a test step' 39 | 40 | assert registry.find_match(step) is None 41 | 42 | given_mock.match.assert_called_with(step.name) 43 | step_mock.match.assert_called_with(step.name) 44 | 45 | def test_find_match_with_no_match_returns_none(self): 46 | registry = step_registry.StepRegistry() 47 | 48 | step_defs = [Mock() for x in range(0, 10)] 49 | for mock in step_defs: 50 | mock.match.return_value = None 51 | 52 | registry.steps['when'] = step_defs 53 | 54 | step = Mock() 55 | step.step_type = 'when' 56 | step.name = 'just a test step' 57 | 58 | assert registry.find_match(step) is None 59 | 60 | def test_find_match_with_a_match_returns_match(self): 61 | registry = step_registry.StepRegistry() 62 | 63 | step_defs = [Mock() for x in range(0, 10)] 64 | for mock in step_defs: 65 | mock.match.return_value = None 66 | magic_object = object() 67 | step_defs[5].match.return_value = magic_object 68 | 69 | registry.steps['then'] = step_defs 70 | 71 | step = Mock() 72 | step.step_type = 'then' 73 | step.name = 'just a test step' 74 | 75 | assert registry.find_match(step) is magic_object 76 | for mock in step_defs[6:]: 77 | eq_(mock.match.call_count, 0) 78 | 79 | @patch.object(step_registry.registry, 'add_step_definition') 80 | def test_make_step_decorator_ends_up_adding_a_step_definition(self, add_step_definition): 81 | step_type = object() 82 | string = object() 83 | func = object() 84 | 85 | decorator = step_registry.registry.make_decorator(step_type) 86 | wrapper = decorator(string) 87 | assert wrapper(func) is func 88 | add_step_definition.assert_called_with(step_type, string, func) 89 | 90 | -------------------------------------------------------------------------------- /features/directory_layout.basic2.feature: -------------------------------------------------------------------------------- 1 | Feature: Basic directory layout (Variant 1B) 2 | 3 | As a story/test writer 4 | I want a simple, non-deep directory structure 5 | So that I can easily get an overview which stories/tests exist 6 | 7 | | BASIC DIRECTORY LAYOUT STRUCTURE: 8 | | testing/features/ 9 | | +-- steps/*.py # Step definitions or step-library imports. 10 | | +-- *.feature # Feature files. 11 | | +-- environment.py # OPTIONAL: environment setup/hooks. 12 | | 13 | | SEE ALSO: 14 | | * http://pythonhosted.org/behave/gherkin.html#layout-variations 15 | 16 | 17 | @setup 18 | Scenario: Setup directory structure 19 | Given a new working directory 20 | And a file named "testing/features/steps/steps.py" with: 21 | """ 22 | from behave import step 23 | 24 | @step('{word:w} step passes') 25 | def step_passes(context, word): 26 | pass 27 | 28 | @step('{word:w} step fails') 29 | def step_fails(context, word): 30 | assert False, "XFAIL-STEP" 31 | """ 32 | And a file named "testing/features/alice.feature" with: 33 | """ 34 | Feature: Alice 35 | Scenario: A1 36 | Given a step passes 37 | When another step passes 38 | Then a step passes 39 | """ 40 | And a file named "testing/features/bob.feature" with: 41 | """ 42 | Feature: Bob 43 | Scenario: B1 44 | When a step passes 45 | Then another step passes 46 | """ 47 | 48 | 49 | Scenario: Run behave with testing directory 50 | When I run "behave -f progress testing/" 51 | Then it should fail with: 52 | """ 53 | ConfigError: No steps directory in "{__WORKDIR__}/testing" 54 | """ 55 | 56 | Scenario: Run behave with feature subdirectory 57 | When I run "behave -f progress testing/features/" 58 | Then it should pass with: 59 | """ 60 | 2 features passed, 0 failed, 0 skipped 61 | 2 scenarios passed, 0 failed, 0 skipped 62 | 5 steps passed, 0 failed, 0 skipped, 0 undefined 63 | """ 64 | 65 | Scenario: Run behave with one feature file 66 | When I run "behave -f progress testing/features/alice.feature" 67 | Then it should pass with: 68 | """ 69 | 1 feature passed, 0 failed, 0 skipped 70 | 1 scenario passed, 0 failed, 0 skipped 71 | 3 steps passed, 0 failed, 0 skipped, 0 undefined 72 | """ 73 | 74 | 75 | Scenario: Run behave with two feature files 76 | Given a file named "one.featureset" with: 77 | """ 78 | testing/features/alice.feature 79 | testing/features/bob.feature 80 | """ 81 | When I run "behave -f progress @one.featureset" 82 | Then it should pass with: 83 | """ 84 | 2 features passed, 0 failed, 0 skipped 85 | 2 scenarios passed, 0 failed, 0 skipped 86 | 5 steps passed, 0 failed, 0 skipped, 0 undefined 87 | """ 88 | -------------------------------------------------------------------------------- /behave/reporter/summary.py: -------------------------------------------------------------------------------- 1 | # -*- coding: UTF-8 -*- 2 | """ 3 | Provides a summary after each test run. 4 | """ 5 | 6 | import sys 7 | from behave.model import ScenarioOutline 8 | from behave.reporter.base import Reporter 9 | 10 | 11 | # -- DISABLED: optional_steps = ('untested', 'undefined') 12 | optional_steps = ('untested',) 13 | 14 | 15 | def format_summary(statement_type, summary): 16 | parts = [] 17 | for status in ('passed', 'failed', 'skipped', 'undefined', 'untested'): 18 | if status not in summary: 19 | continue 20 | counts = summary[status] 21 | if status in optional_steps and counts == 0: 22 | # -- SHOW-ONLY: For relevant counts, suppress: untested items, etc. 23 | continue 24 | 25 | if not parts: 26 | # -- FIRST ITEM: Add statement_type to counter. 27 | label = statement_type 28 | if counts != 1: 29 | label += 's' 30 | part = '%d %s %s' % (counts, label, status) 31 | else: 32 | part = '%d %s' % (counts, status) 33 | parts.append(part) 34 | return ', '.join(parts) + '\n' 35 | 36 | 37 | class SummaryReporter(Reporter): 38 | show_failed_scenarios = True 39 | output_stream_name = "stdout" 40 | 41 | def __init__(self, config): 42 | super(SummaryReporter, self).__init__(config) 43 | self.stream = getattr(sys, self.output_stream_name, sys.stderr) 44 | self.feature_summary = {'passed': 0, 'failed': 0, 'skipped': 0, 45 | 'untested': 0} 46 | self.scenario_summary = {'passed': 0, 'failed': 0, 'skipped': 0, 47 | 'untested': 0} 48 | self.step_summary = {'passed': 0, 'failed': 0, 'skipped': 0, 49 | 'undefined': 0, 'untested': 0} 50 | self.duration = 0.0 51 | self.failed_scenarios = [] 52 | 53 | def feature(self, feature): 54 | self.feature_summary[feature.status or 'skipped'] += 1 55 | self.duration += feature.duration 56 | for scenario in feature: 57 | if isinstance(scenario, ScenarioOutline): 58 | self.process_scenario_outline(scenario) 59 | else: 60 | self.process_scenario(scenario) 61 | 62 | def end(self): 63 | # -- SHOW FAILED SCENARIOS (optional): 64 | if self.show_failed_scenarios and self.failed_scenarios: 65 | self.stream.write("\nFailing scenarios:\n") 66 | for scenario in self.failed_scenarios: 67 | self.stream.write(" %s %s\n" % ( 68 | scenario.location, scenario.name)) 69 | self.stream.write("\n") 70 | 71 | # -- SHOW SUMMARY COUNTS: 72 | self.stream.write(format_summary('feature', self.feature_summary)) 73 | self.stream.write(format_summary('scenario', self.scenario_summary)) 74 | self.stream.write(format_summary('step', self.step_summary)) 75 | timings = int(self.duration / 60), self.duration % 60 76 | self.stream.write('Took %dm%02.3fs\n' % timings) 77 | 78 | def process_scenario(self, scenario): 79 | if scenario.status == 'failed': 80 | self.failed_scenarios.append(scenario) 81 | self.scenario_summary[scenario.status or 'skipped'] += 1 82 | for step in scenario: 83 | self.step_summary[step.status or 'skipped'] += 1 84 | 85 | def process_scenario_outline(self, scenario_outline): 86 | for scenario in scenario_outline.scenarios: 87 | self.process_scenario(scenario) 88 | -------------------------------------------------------------------------------- /features/steps/behave_select_files_steps.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | """ 3 | Provides step definitions that test how the behave runner selects feature files. 4 | 5 | EXAMPLE: 6 | Given behave has the following feature fileset: 7 | ''' 8 | features/alice.feature 9 | features/bob.feature 10 | features/barbi.feature 11 | ''' 12 | When behave includes feature files with "features/a.*\.feature" 13 | And behave excludes feature files with "features/b.*\.feature" 14 | Then the following feature files are selected: 15 | ''' 16 | features/alice.feature 17 | ''' 18 | """ 19 | 20 | from behave import given, when, then 21 | from behave.runner_util import FeatureListParser 22 | from hamcrest import assert_that, equal_to 23 | from copy import copy 24 | import re 25 | 26 | # ----------------------------------------------------------------------------- 27 | # STEP UTILS: 28 | # ----------------------------------------------------------------------------- 29 | class BasicBehaveRunner(object): 30 | def __init__(self, config=None): 31 | self.config = config 32 | self.feature_files = [] 33 | 34 | def select_files(self): 35 | """ 36 | Emulate behave runners file selection by using include/exclude patterns. 37 | :return: List of selected feature filenames. 38 | """ 39 | selected = [] 40 | for filename in self.feature_files: 41 | if not self.config.exclude(filename): 42 | selected.append(str(filename)) 43 | return selected 44 | 45 | # ----------------------------------------------------------------------------- 46 | # STEP DEFINITIONS: 47 | # ----------------------------------------------------------------------------- 48 | @given('behave has the following feature fileset') 49 | def step_given_behave_has_feature_fileset(context): 50 | assert context.text is not None, "REQUIRE: text" 51 | behave_runner = BasicBehaveRunner(config=copy(context.config)) 52 | behave_runner.feature_files = FeatureListParser.parse(context.text) 53 | context.behave_runner = behave_runner 54 | 55 | @when('behave includes all feature files') 56 | def step_when_behave_includes_all_feature_files(context): 57 | assert context.behave_runner, "REQUIRE: context.behave_runner" 58 | context.behave_runner.config.include_re = None 59 | 60 | @when('behave includes feature files with "{pattern}"') 61 | def step_when_behave_includes_feature_files_with_pattern(context, pattern): 62 | assert context.behave_runner, "REQUIRE: context.behave_runner" 63 | context.behave_runner.config.include_re = re.compile(pattern) 64 | 65 | @when('behave excludes no feature files') 66 | def step_when_behave_excludes_no_feature_files(context): 67 | assert context.behave_runner, "REQUIRE: context.behave_runner" 68 | context.behave_runner.config.exclude_re = None 69 | 70 | @when('behave excludes feature files with "{pattern}"') 71 | def step_when_behave_excludes_feature_files_with_pattern(context, pattern): 72 | assert context.behave_runner, "REQUIRE: context.behave_runner" 73 | context.behave_runner.config.exclude_re = re.compile(pattern) 74 | 75 | @then('the following feature files are selected') 76 | def step_then_feature_files_are_selected_with_text(context): 77 | assert context.text is not None, "REQUIRE: text" 78 | assert context.behave_runner, "REQUIRE: context.behave_runner" 79 | selected_files = context.text.strip().splitlines() 80 | actual_files = context.behave_runner.select_files() 81 | assert_that(actual_files, equal_to(selected_files)) 82 | -------------------------------------------------------------------------------- /features/scenario_outline.basics.feature: -------------------------------------------------------------------------------- 1 | @issue 2 | Feature: Issue #187 ScenarioOutline uses wrong return value when if fails 3 | 4 | Ensure that ScenarioOutline run-logic behaves as expected. 5 | 6 | @setup 7 | Scenario: Feature Setup 8 | Given a new working directory 9 | And a file named "features/steps/steps.py" with: 10 | """ 11 | from behave import step 12 | 13 | @step('a step passes') 14 | def step_passes(context): 15 | pass 16 | 17 | @step('a step fails') 18 | def step_fails(context): 19 | assert False, "XFAIL-STEP" 20 | """ 21 | 22 | Scenario: All examples pass 23 | Given a file named "features/example.scenario_outline_pass.feature" with: 24 | """ 25 | Feature: All Examples pass 26 | Scenario Outline: 27 | Given a step 28 | 29 | Examples: 30 | | outcome | Comment | 31 | | passes | First example passes | 32 | | passes | Last example passes | 33 | """ 34 | When I run "behave -f plain features/example.scenario_outline_pass.feature" 35 | Then it should pass with: 36 | """ 37 | 1 feature passed, 0 failed, 0 skipped 38 | 2 scenarios passed, 0 failed, 0 skipped 39 | """ 40 | 41 | @scenario_outline.fails 42 | Scenario: First example fails 43 | Given a file named "features/example.scenario_outline_fail_first.feature" with: 44 | """ 45 | Feature: First Example in Scenario Outline fails 46 | Scenario Outline: 47 | Given a step 48 | 49 | Examples: 50 | | outcome | Comment | 51 | | fails | First example fails | 52 | | passes | Last example passes | 53 | """ 54 | When I run "behave -f plain features/example.scenario_outline_fail_first.feature" 55 | Then it should fail with: 56 | """ 57 | 0 features passed, 1 failed, 0 skipped 58 | 1 scenario passed, 1 failed, 0 skipped 59 | """ 60 | 61 | @scenario_outline.fails 62 | Scenario: Last example fails 63 | Given a file named "features/example.scenario_outline_fail_last.feature" with: 64 | """ 65 | Feature: Last Example in Scenario Outline fails 66 | Scenario Outline: 67 | Given a step 68 | 69 | Examples: 70 | | outcome | Comment | 71 | | passes | First example passes | 72 | | fails | Last example fails | 73 | """ 74 | When I run "behave -f plain features/example.scenario_outline_fail_last.feature" 75 | Then it should fail with: 76 | """ 77 | 0 features passed, 1 failed, 0 skipped 78 | 1 scenario passed, 1 failed, 0 skipped 79 | """ 80 | 81 | @scenario_outline.fails 82 | Scenario: Middle example fails 83 | Given a file named "features/example.scenario_outline_fail_middle.feature" with: 84 | """ 85 | Feature: Middle Example in Scenario Outline fails 86 | Scenario Outline: 87 | Given a step 88 | 89 | Examples: 90 | | outcome | Comment | 91 | | passes | First example passes | 92 | | fails | Middle example fails | 93 | | passes | Last example passes | 94 | """ 95 | When I run "behave -f plain features/example.scenario_outline_fail_middle.feature" 96 | Then it should fail with: 97 | """ 98 | 0 features passed, 1 failed, 0 skipped 99 | 2 scenarios passed, 1 failed, 0 skipped 100 | """ 101 | -------------------------------------------------------------------------------- /behave/formatter/formatters.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | 3 | import sys 4 | from behave.formatter.base import StreamOpener 5 | from behave.textutil import compute_words_maxsize 6 | from behave.importer import LazyDict, LazyObject 7 | 8 | 9 | # ----------------------------------------------------------------------------- 10 | # FORMATTER REGISTRY: 11 | # ----------------------------------------------------------------------------- 12 | formatters = LazyDict() 13 | 14 | 15 | def register_as(formatter_class, name): 16 | """ 17 | Register formatter class with given name. 18 | 19 | :param formatter_class: Formatter class to register. 20 | :param name: Name for this formatter (as identifier). 21 | """ 22 | formatters[name] = formatter_class 23 | 24 | def register(formatter_class): 25 | register_as(formatter_class, formatter_class.name) 26 | 27 | 28 | def list_formatters(stream): 29 | """ 30 | Writes a list of the available formatters and their description to stream. 31 | 32 | :param stream: Output stream to use. 33 | """ 34 | formatter_names = sorted(formatters) 35 | column_size = compute_words_maxsize(formatter_names) 36 | schema = u" %-"+ str(column_size) +"s %s\n" 37 | for name in formatter_names: 38 | stream.write(schema % (name, formatters[name].description)) 39 | 40 | 41 | def get_formatter(config, stream_openers): 42 | # -- BUILD: Formatter list 43 | default_stream_opener = StreamOpener(stream=sys.stdout) 44 | formatter_list = [] 45 | for i, name in enumerate(config.format): 46 | stream_opener = default_stream_opener 47 | if i < len(stream_openers): 48 | stream_opener = stream_openers[i] 49 | formatter_list.append(formatters[name](stream_opener, config)) 50 | return formatter_list 51 | 52 | 53 | # ----------------------------------------------------------------------------- 54 | # SETUP: 55 | # ----------------------------------------------------------------------------- 56 | def setup_formatters(): 57 | # -- NOTE: Use lazy imports for formatters (to speed up start-up time). 58 | _L = LazyObject 59 | register_as(_L("behave.formatter.plain:PlainFormatter"), "plain") 60 | register_as(_L("behave.formatter.pretty:PrettyFormatter"), "pretty") 61 | register_as(_L("behave.formatter.json:JSONFormatter"), "json") 62 | register_as(_L("behave.formatter.json:PrettyJSONFormatter"), "json.pretty") 63 | register_as(_L("behave.formatter.null:NullFormatter"), "null") 64 | register_as(_L("behave.formatter.progress:ScenarioProgressFormatter"), 65 | "progress") 66 | register_as(_L("behave.formatter.progress:StepProgressFormatter"), 67 | "progress2") 68 | register_as(_L("behave.formatter.progress:ScenarioStepProgressFormatter"), 69 | "progress3") 70 | register_as(_L("behave.formatter.rerun:RerunFormatter"), "rerun") 71 | register_as(_L("behave.formatter.tags:TagsFormatter"), "tags") 72 | register_as(_L("behave.formatter.tags:TagsLocationFormatter"), 73 | "tags.location") 74 | register_as(_L("behave.formatter.steps:StepsFormatter"), "steps") 75 | register_as(_L("behave.formatter.steps:StepsDocFormatter"), "steps.doc") 76 | register_as(_L("behave.formatter.steps:StepsUsageFormatter"), "steps.usage") 77 | register_as(_L("behave.formatter.sphinx_steps:SphinxStepsFormatter"), 78 | "sphinx.steps") 79 | 80 | 81 | # ----------------------------------------------------------------------------- 82 | # MODULE-INIT: 83 | # ----------------------------------------------------------------------------- 84 | setup_formatters() 85 | -------------------------------------------------------------------------------- /test/test_formatter_rerun.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | """ 3 | Test behave formatters: 4 | * behave.formatter.rerun.RerunFormatter 5 | """ 6 | 7 | from __future__ import absolute_import 8 | from .test_formatter import FormatterTests as FormatterTest, _tf 9 | from .test_formatter import MultipleFormattersTests as MultipleFormattersTest 10 | from nose.tools import * 11 | 12 | class TestRerunFormatter(FormatterTest): 13 | formatter_name = "rerun" 14 | 15 | def test_feature_with_two_passing_scenarios(self): 16 | p = self._formatter(_tf(), self.config) 17 | f = self._feature() 18 | scenarios = [ self._scenario(), self._scenario() ] 19 | for scenario in scenarios: 20 | f.add_scenario(scenario) 21 | 22 | # -- FORMATTER CALLBACKS: 23 | p.feature(f) 24 | for scenario in f.scenarios: 25 | p.scenario(scenario) 26 | assert scenario.status == "passed" 27 | p.eof() 28 | eq_([], p.failed_scenarios) 29 | # -- EMIT REPORT: 30 | p.close() 31 | 32 | def test_feature_with_one_passing_one_failing_scenario(self): 33 | p = self._formatter(_tf(), self.config) 34 | f = self._feature() 35 | passing_scenario = self._scenario() 36 | failing_scenario = self._scenario() 37 | failing_scenario.steps.append(self._step()) 38 | scenarios = [ passing_scenario, failing_scenario ] 39 | for scenario in scenarios: 40 | f.add_scenario(scenario) 41 | 42 | # -- FORMATTER CALLBACKS: 43 | p.feature(f) 44 | for scenario in f.scenarios: 45 | p.scenario(scenario) 46 | 47 | failing_scenario.steps[0].status = "failed" 48 | assert scenarios[0].status == "passed" 49 | assert scenarios[1].status == "failed" 50 | p.eof() 51 | eq_([ failing_scenario ], p.failed_scenarios) 52 | # -- EMIT REPORT: 53 | p.close() 54 | 55 | def test_feature_with_one_passing_two_failing_scenario(self): 56 | p = self._formatter(_tf(), self.config) 57 | f = self._feature() 58 | passing_scenario = self._scenario() 59 | failing_scenario1 = self._scenario() 60 | failing_scenario1.steps.append(self._step()) 61 | failing_scenario2 = self._scenario() 62 | failing_scenario2.steps.append(self._step()) 63 | scenarios = [ failing_scenario1, passing_scenario, failing_scenario2 ] 64 | for scenario in scenarios: 65 | f.add_scenario(scenario) 66 | 67 | # -- FORMATTER CALLBACKS: 68 | p.feature(f) 69 | for scenario in f.scenarios: 70 | p.scenario(scenario) 71 | 72 | failing_scenario1.steps[0].status = "failed" 73 | failing_scenario2.steps[0].status = "failed" 74 | assert scenarios[0].status == "failed" 75 | assert scenarios[1].status == "passed" 76 | assert scenarios[2].status == "failed" 77 | p.eof() 78 | eq_([ failing_scenario1, failing_scenario2 ], p.failed_scenarios) 79 | # -- EMIT REPORT: 80 | p.close() 81 | 82 | 83 | class TestRerunAndPrettyFormatters(MultipleFormattersTest): 84 | formatters = ["rerun", "pretty"] 85 | 86 | class TestRerunAndPlainFormatters(MultipleFormattersTest): 87 | formatters = ["rerun", "plain"] 88 | 89 | class TestRerunAndScenarioProgressFormatters(MultipleFormattersTest): 90 | formatters = ["rerun", "progress"] 91 | 92 | class TestRerunAndStepProgressFormatters(MultipleFormattersTest): 93 | formatters = ["rerun", "progress2"] 94 | 95 | class TestRerunAndJsonFormatter(MultipleFormattersTest): 96 | formatters = ["rerun", "json"] 97 | -------------------------------------------------------------------------------- /features/step.import_other_step_module.feature: -------------------------------------------------------------------------------- 1 | Feature: Ensure that a step module can import another step module 2 | 3 | As a test writer 4 | I want to import step definitions from another module in a step module 5 | So that I can reuse other steps and call them directly. 6 | 7 | | When a step module imports another step module 8 | | this should not cause AmbiguousStep errors 9 | | due to duplicated registration of the same step functions. 10 | | 11 | | NOTES: 12 | | * In general you should avoid this case (provided as example here). 13 | | * Use "context.execute_steps(...)" to avoid importing other step modules 14 | | * Use step-libraries; this will in general use sane imports of other step modules 15 | 16 | 17 | Scenario: Step module that imports another step module 18 | Given a new working directory 19 | And a file named "features/steps/alice1_steps.py" with: 20 | """ 21 | from behave import given 22 | 23 | @given(u'I call Alice') 24 | def step_call_alice(context): 25 | pass 26 | """ 27 | And a file named "features/steps/bob1_steps.py" with: 28 | """ 29 | from behave import given 30 | from alice1_steps import step_call_alice 31 | 32 | @given(u'I call Bob') 33 | def step_call_bob(context): 34 | pass 35 | 36 | @given(u'I call Bob and Alice') 37 | def step_call_bob_and_alice(context): 38 | step_call_bob(context) 39 | step_call_alice(context) 40 | """ 41 | And a file named "features/example.import_step_module.feature" with: 42 | """ 43 | Feature: 44 | Scenario: 45 | Given I call Bob and Alice 46 | """ 47 | When I run "behave -f plain --no-timings features/example.import_step_module.feature" 48 | Then it should pass with: 49 | """ 50 | 1 scenario passed, 0 failed, 0 skipped 51 | 1 step passed, 0 failed, 0 skipped, 0 undefined 52 | """ 53 | And the command output should contain: 54 | """ 55 | Feature: 56 | Scenario: 57 | Given I call Bob and Alice ... passed 58 | """ 59 | 60 | 61 | Scenario: Step module that imports another step module (cross-wise) 62 | Given a new working directory 63 | And a file named "features/steps/alice2_steps.py" with: 64 | """ 65 | from behave import given 66 | import bob2_steps # -- BAD: Import other step module, cross-wise. 67 | 68 | @given(u'I call Alice') 69 | def step_call_alice(context): 70 | pass 71 | """ 72 | And a file named "features/steps/bob2_steps.py" with: 73 | """ 74 | from behave import given 75 | import alice2_steps # -- BAD: Import other step module, cross-wise. 76 | 77 | @given(u'I call Bob') 78 | def step_call_bob(context): 79 | pass 80 | """ 81 | And a file named "features/example.cross_imported_step_modules.feature" with: 82 | """ 83 | Feature: 84 | Scenario: 85 | Given I call Alice 86 | And I call Bob 87 | """ 88 | When I run "behave -f plain --no-timings features/example.cross_imported_step_modules.feature" 89 | Then it should pass with: 90 | """ 91 | 1 feature passed, 0 failed, 0 skipped 92 | 1 scenario passed, 0 failed, 0 skipped 93 | 2 steps passed, 0 failed, 0 skipped, 0 undefined 94 | """ 95 | And the command output should contain: 96 | """ 97 | Feature: 98 | Scenario: 99 | Given I call Alice ... passed 100 | And I call Bob ... passed 101 | """ 102 | 103 | 104 | -------------------------------------------------------------------------------- /features/step.duplicated_step.feature: -------------------------------------------------------------------------------- 1 | @wip 2 | Feature: Duplicated Step Definitions 3 | 4 | As I tester and test writer 5 | I want to know when step definitions are duplicated 6 | So that I can fix these problems. 7 | 8 | 9 | Scenario: Duplicated Step in same File 10 | Given a new working directory 11 | And a file named "features/steps/alice_steps.py" with: 12 | """ 13 | from behave import given, when, then 14 | 15 | @given(u'I call Alice') 16 | def step(context): 17 | pass 18 | 19 | @given(u'I call Alice') 20 | def step(context): 21 | pass 22 | """ 23 | And a file named "features/duplicated_step_alice.feature" with: 24 | """ 25 | Feature: 26 | Scenario: Duplicated Step 27 | Given I call Alice 28 | """ 29 | When I run "behave -f plain features/duplicated_step_alice.feature" 30 | Then it should fail 31 | And the command output should contain: 32 | """ 33 | AmbiguousStep: @given('I call Alice') has already been defined in 34 | existing step @given('I call Alice') at features/steps/alice_steps.py:3 35 | """ 36 | And the command output should contain: 37 | """ 38 | File "{__WORKDIR__}/features/steps/alice_steps.py", line 7, in 39 | @given(u'I call Alice') 40 | """ 41 | 42 | 43 | Scenario: Duplicated Step Definition in another File 44 | Given a new working directory 45 | And a file named "features/steps/bob1_steps.py" with: 46 | """ 47 | from behave import given 48 | 49 | @given('I call Bob') 50 | def step_call_bob1(context): 51 | pass 52 | """ 53 | And a file named "features/steps/bob2_steps.py" with: 54 | """ 55 | from behave import given 56 | 57 | @given('I call Bob') 58 | def step_call_bob2(context): 59 | pass 60 | """ 61 | And a file named "features/duplicated_step_bob.feature" with: 62 | """ 63 | Feature: 64 | Scenario: Duplicated Step 65 | Given I call Bob 66 | """ 67 | When I run "behave -f plain features/duplicated_step_bob.feature" 68 | Then it should fail 69 | And the command output should contain: 70 | """ 71 | AmbiguousStep: @given('I call Bob') has already been defined in 72 | existing step @given('I call Bob') at features/steps/bob1_steps.py:3 73 | """ 74 | And the command output should contain: 75 | """ 76 | File "{__WORKDIR__}/features/steps/bob2_steps.py", line 3, in 77 | @given('I call Bob') 78 | """ 79 | 80 | @wip 81 | @xfail 82 | Scenario: Duplicated Same Step Definition via import from another File 83 | Given a new working directory 84 | And a file named "features/steps/charly1_steps.py" with: 85 | """ 86 | from behave import given 87 | 88 | @given('I call Charly') 89 | def step_call_charly1(context): 90 | pass 91 | """ 92 | And a file named "features/steps/charly2_steps.py" with: 93 | """ 94 | import charly1_steps 95 | """ 96 | And a file named "features/duplicated_step_via_import.feature" with: 97 | """ 98 | Feature: 99 | Scenario: Duplicated same step via import 100 | Given I call Charly 101 | """ 102 | When I run "behave -f plain features/duplicated_step_via_import.feature" 103 | Then it should pass 104 | And the command output should not contain: 105 | """ 106 | AmbiguousStep: @given('I call Charly') has already been defined in 107 | existing step @given('I call Charly') at features/steps/charly1_steps.py:3 108 | """ 109 | -------------------------------------------------------------------------------- /docs/context_attributes.rst: -------------------------------------------------------------------------------- 1 | .. _id.appendix.context_attributes: 2 | 3 | ============================================================================== 4 | Context Attributes 5 | ============================================================================== 6 | 7 | A context object (:class:`~behave.runner.Context`) is handed to 8 | 9 | * step definitions (step implementations) 10 | * behave hooks (:func:`before_all`, :func:`before_feature`, ..., :func:`after_all`) 11 | 12 | 13 | Behave Attributes 14 | ------------------------- 15 | 16 | The `behave`_ runner assigns a number of attributes to the context object 17 | during a test run. 18 | 19 | =============== ========= ============================================= ============================================================== 20 | Attribute Name Layer Type Description 21 | =============== ========= ============================================= ============================================================== 22 | config test run :class:`~behave.configuration.Configuration` Configuration that is used. 23 | aborted test run bool Set to true if test run is aborted by the user. 24 | failed test run bool Set to true if a step fails. 25 | feature feature :class:`~behave.model.Feature` Current feature. 26 | tags feature, list<:class:`~behave.model.Tag`> Effective tags of current feature, scenario, scenario outline. 27 | scenario 28 | active_outline scenario :class:`~behave.model.Row` Current row in a scenario outline (in examples table). 29 | outline 30 | scenario scenario :class:`~behave.model.Scenario` Current scenario. 31 | log_capture scenario :class:`~behave.log_capture.LoggingCapture` If logging capture is enabled. 32 | stdout_capture scenario :class:`~StringIO.StringIO` If stdout capture is enabled. 33 | stderr_capture scenario :class:`~StringIO.StringIO` If stderr capture is enabled. 34 | table step :class:`~behave.model.Table` Contains step's table, otherwise None. 35 | text step String Contains step's multi-line text (unicode), otherwise None. 36 | =============== ========= ============================================= ============================================================== 37 | 38 | .. note:: 39 | 40 | `Behave attributes`_ in the context object should not be modified by a user. 41 | See :class:`~behave.runner.Context` class description for more details. 42 | 43 | 44 | User Attributes 45 | ------------------------- 46 | 47 | A user can assign (or modify) own attributes to the context object. 48 | But these attributes will be removed again from the context object depending 49 | where these attributes are defined. 50 | 51 | ======= =========================== ======================= 52 | Kind Assign Location Lifecycle Layer (Scope) 53 | ======= =========================== ======================= 54 | Hook :func:`before_all` test run 55 | Hook :func:`after_all` test run 56 | Hook :func:`before_tags` feature or scenario 57 | Hook :func:`after_tags` feature or scenario 58 | Hook :func:`before_feature` feature 59 | Hook :func:`after_feature` feature 60 | Hook :func:`before_scenario` scenario 61 | Hook :func:`after_scenario` scenario 62 | Hook :func:`before_step` scenario 63 | Hook :func:`after_step` scenario 64 | Step Step definition scenario 65 | ======= =========================== ======================= 66 | 67 | 68 | .. _behave: http://pypi.python.org/pypi/behave 69 | -------------------------------------------------------------------------------- /issue.features/issue0148.feature: -------------------------------------------------------------------------------- 1 | @issue 2 | @already_fixed 3 | Feature: Issue #148: Substeps do not fail 4 | 5 | FIXED-BY: issue #117 context.execute_steps() should support table and multi-line text. 6 | RELATED-TO: issue #96 7 | 8 | @setup 9 | Scenario: Setup 10 | Given a new working directory 11 | And a file named "features/steps/passing_steps.py" with: 12 | """ 13 | @step('a step passes') 14 | def step_passes(context): 15 | pass 16 | 17 | @step('a step fails') 18 | def step_fails(context): 19 | assert False, "XFAIL" 20 | """ 21 | And a file named "features/issue0148_example.feature" with: 22 | """ 23 | Feature: Sub steps 24 | 25 | @xfail 26 | Scenario: Failing test without substeps 27 | Given a step passes 28 | When a step fails 29 | Then a step passes 30 | 31 | @xfail 32 | Scenario: Failing test with substeps 33 | Given a step passes 34 | When I do something with stupid substeps 35 | Then a step passes 36 | """ 37 | 38 | Scenario: Missing Step Keywords in Substeps 39 | Given a file named "features/steps/substeps.py" with: 40 | """ 41 | @When('I do something with stupid substeps') 42 | def step(context): 43 | context.execute_steps(u''' 44 | I do something stupid 45 | there is a second stupid step 46 | ''') # Given/When/Then keywords are missing in substeps above. 47 | """ 48 | When I run "behave -f plain -T features/issue0148_example.feature" 49 | Then it should fail with: 50 | """ 51 | 0 features passed, 1 failed, 0 skipped 52 | 0 scenarios passed, 2 failed, 0 skipped 53 | 2 steps passed, 2 failed, 2 skipped, 0 undefined 54 | """ 55 | And the command output should contain: 56 | """ 57 | Scenario: Failing test without substeps 58 | Given a step passes ... passed 59 | When a step fails ... failed 60 | """ 61 | And the command output should contain: 62 | """ 63 | Scenario: Failing test with substeps 64 | Given a step passes ... passed 65 | When I do something with stupid substeps ... failed 66 | """ 67 | And the command output should contain: 68 | """ 69 | ParserError: Failed to parse : 70 | Parser failure in state steps, at line 2: 'I do something stupid' 71 | """ 72 | 73 | 74 | Scenario: Use Step Keywords in Substeps 75 | Given a file named "features/steps/substeps.py" with: 76 | """ 77 | @when('I do something with stupid substeps') 78 | def step(context): 79 | context.execute_steps(u''' 80 | When a step fails 81 | Then a step fails 82 | ''') 83 | """ 84 | When I run "behave -f plain -T features/issue0148_example.feature" 85 | Then it should fail with: 86 | """ 87 | 0 features passed, 1 failed, 0 skipped 88 | 0 scenarios passed, 2 failed, 0 skipped 89 | 2 steps passed, 2 failed, 2 skipped, 0 undefined 90 | """ 91 | And the command output should contain: 92 | """ 93 | Scenario: Failing test with substeps 94 | Given a step passes ... passed 95 | When I do something with stupid substeps ... failed 96 | Assertion Failed: FAILED SUB-STEP: When a step fails 97 | Substep info: Assertion Failed: XFAIL 98 | """ 99 | But the command output should not contain: 100 | """ 101 | 102 | ParserError: Failed to parse 103 | """ 104 | 105 | 106 | --------------------------------------------------------------------------------