{"id":16538672,"url":"https://github.com/spyoungtech/behave-classy","last_synced_at":"2025-10-28T14:31:07.212Z","repository":{"id":57414550,"uuid":"144298559","full_name":"spyoungtech/behave-classy","owner":"spyoungtech","description":"Class-based step implementations for the Python behave BDD framework","archived":false,"fork":false,"pushed_at":"2019-12-21T01:29:27.000Z","size":14,"stargazers_count":16,"open_issues_count":1,"forks_count":0,"subscribers_count":5,"default_branch":"master","last_synced_at":"2024-09-16T23:32:58.792Z","etag":null,"topics":["bdd","behave"],"latest_commit_sha":null,"homepage":null,"language":"Python","has_issues":true,"has_wiki":null,"has_pages":null,"mirror_url":null,"source_name":null,"license":"mit","status":null,"scm":"git","pull_requests_enabled":true,"icon_url":"https://github.com/spyoungtech.png","metadata":{"files":{"readme":"readme.md","changelog":null,"contributing":null,"funding":null,"license":"LICENSE","code_of_conduct":null,"threat_model":null,"audit":null,"citation":null,"codeowners":null,"security":null,"support":null}},"created_at":"2018-08-10T14:47:33.000Z","updated_at":"2024-03-17T00:15:32.000Z","dependencies_parsed_at":"2022-09-10T04:03:35.987Z","dependency_job_id":null,"html_url":"https://github.com/spyoungtech/behave-classy","commit_stats":null,"previous_names":[],"tags_count":1,"template":false,"template_full_name":null,"repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/spyoungtech%2Fbehave-classy","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/spyoungtech%2Fbehave-classy/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/spyoungtech%2Fbehave-classy/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/spyoungtech%2Fbehave-classy/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/spyoungtech","download_url":"https://codeload.github.com/spyoungtech/behave-classy/tar.gz/refs/heads/master","host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":219859479,"owners_count":16556036,"icon_url":"https://github.com/github.png","version":null,"created_at":"2022-05-30T11:31:42.601Z","updated_at":"2022-07-04T15:15:14.044Z","host_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub","repositories_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories","repository_names_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repository_names","owners_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners"}},"keywords":["bdd","behave"],"created_at":"2024-10-11T18:46:19.358Z","updated_at":"2025-10-28T14:31:06.874Z","avatar_url":"https://github.com/spyoungtech.png","language":"Python","funding_links":[],"categories":[],"sub_categories":[],"readme":"# behave-classy\n\n## Installation\n\nInstallation is made easy with `pip`\n\n```\npip install behave-classy\n```\n\n## What is behave-classy\n\n*beahve-classy* provides a class-based API for behave step implementations\n\nThis package is geared towards authors of step implementation libraries and aims to provide a more flexible and \nextensible interface for behave step libraries and their users.\n\nThe primary features \n\n- ability to define step definitions as classes\n- ability to extend steps from your own classes (or perhaps classes provided by other libraries/packages)\n- ability to define a matcher per-method without changing global state of the 'current matcher'\n- wraps methods to transform context into an attribute (`self.context`), so it's not necessary to have each method use context as first parameter in the signature. Works with default behave runner.\n\n\n# Usage\n\nUsage is fairly simple and follows these basic steps:\n\n- Use the `step_impl_base` to create a base class that will contain your step definitions in its own local registry.\n- Make your subclass and definitions\n- When you want to use your steps, simply call the `register` method of your class.\n\n\n# Example\n\nConsider some step library which provides a set of steps in the class `BankAccountSteps`.\n\n```python\n#  some_library/steps.py\nfrom behave_classy import step_impl_base\n\nBase = step_impl_base()\n\nclass BankAccountSteps(Base):\n    @Base.given(u'I have a balance of {amount:d}')\n    def set_balance(self, amount):\n        self.balance = amount\n    \n    @Base.when(u'I deposit {amount:d} into the account')\n    def deposit(self, amount):\n        if amount \u003c 0:\n            raise ValueError('Deposit amounts cannot be negative')\n        self.balance += amount\n    \n    @Base.when(u'I withdraw {amount:d} from the account')\n    def withdraw(self, amount):\n        self.balance -= amount\n    \n    @Base.then(u'the balance should be {expected_amount:d}')\n    def check_balance(self, expected_amount):\n        assert self.balance == expected_amount\n\n    @property\n    def balance(self):\n        \"\"\"convenience shortcut for context balance\"\"\"\n        amount = getattr(self.context, 'balance', 0)\n        return amount\n    \n    @balance.setter\n    def balance(self, new_amount):\n        \"\"\"convenience setter\"\"\"\n        self.context.balance = new_amount\n\n\n\n```\n\nAs a user of such a library, you would import the class, then *register* it, so its definitions are added to the global step registry used by behave.\n\n```python\n# myproject/features/steps/mysteps.py\nfrom some_library.steps import BankAccountSteps\nBankAccountSteps().register()\n```\n\nThen with a typical feature file...\n\n```gherkin\n# myproject/features/account_balance.feature\nFeature: bank account balance\n  Background: \n    Given I have a balance of 100\n    \n  Scenario: withdraw\n    When I withdraw 42 from the account\n    Then the balance should be 58\n  \n  Scenario: deposit\n    When I deposit 42 into the account\n    Then the balance should be 142\n```\n\nYou can simply run behave as normal\n\n```\n$ behave\n\nFeature: bank account balance # features/account_balance.feature:2\n\n  Background:   # features/account_balance.feature:3\n\n  Scenario: withdraw                    # features/account_balance.feature:6\n    Given I have a balance of 100       # features/steps/steps.py:18\n    When I withdraw 42 from the account # features/steps/steps.py:28\n    Then the balance should be 58       # features/steps/steps.py:32\n\n  Scenario: deposit                    # features/account_balance.feature:10\n    Given I have a balance of 100      # features/steps/steps.py:18\n    When I deposit 42 into the account # features/steps/steps.py:22\n    Then the balance should be 142     # features/steps/steps.py:32\n\n1 feature passed, 0 failed, 0 skipped\n2 scenarios passed, 0 failed, 0 skipped\n6 steps passed, 0 failed, 0 skipped, 0 undefined\nTook 0m0.002s\n```\n\n\n## extending and integrating existing work\n\nWithout *behave-classy*, you would typically use behave's `context.execute_steps` feature to extend on work. Although \nthis works for simple cases, it can be quite inflexible, especially once your cases become nontrivial. *behave-classy* allows you to use usual python techniques (e.g. subclassing, method extensions/overriding, etc.) to reuse \nyour existing code, just like you would do in any other python code.\n\nConsider the Bank Account example from the previous section. This example **demonstrates several features**:\n\n1. We will add additional steps the class, adding new methods, which reuse existing methods. One of these will use a different matcher, the RegexMatcher.\n2. We will *extend* the existing `withdraw` method to first check that the account has sufficient funds, otherwise raising a ValueError.\n3. We will *integrate* unittest assertion matchers to the class by using `unittest.TestCase` as a mixin\n4. We will *override* the existing `check_balance` method to use `unittest.TestCase` assertions\n\n```python\n# myproject/features/steps/mysteps.py\nfrom unittest import TestCase\nfrom behave.matchers import RegexMatcher\nfrom some_library.steps import BankAccountSteps as Base\n\nclass ExtendedSteps(Base, TestCase):\n    def withdraw(self, amount):\n        \"\"\"Extends withdraw method to make sure enough funds are in the account, then calls withdraw from superclass\"\"\"\n        if amount \u003e self.balance:\n            raise ValueError('Insufficient Funds')\n        super().withdraw(amount)\n    \n    def check_balance(self, expected_amount):\n        \"\"\"Override check_balance method to use unittest assertions instead\"\"\"\n        self.assertEquals(self.balance, expected_amount)\n        \n\n    @Base.when(u'I pay my {loan_name} loan payment')\n    def pay_loan(self, loan_name):\n        \"\"\"additional when step for loan payments\"\"\"\n        loan_payments = {\n            'student': 600,\n            'car': 200\n        }\n        payment_amount = loan_payments[loan_name]\n        self.withdraw(payment_amount)\n\n\n    @Base.then(u'the balance should be (less|greater) than (or equal to )*(\\d+)', matcher=RegexMatcher)\n    def compare_balance(self, operator, or_equals, amount):\n        \"\"\"Additional step using regex matcher to compare the current balance with some number\"\"\"\n        amount = int(amount)\n        if operator == 'less':\n            if or_equals:\n                self.assertLessEqual(self.balance, amount)\n            else:\n                self.assertLess(self.balance, amount)\n        elif or_equals:\n            self.assertGreaterEqual(self.balance, amount)\n        else:\n            self.assertGreater(self.balance, amount)\n\n\nExtendedSteps().register()\n```\n\nWe'll add some additional scenarios to our feature file to show off these new alterations\n\n```gherkin\n# myproject/features/account_balance.feature\n# ...\n  Scenario: pay loans\n    Given I have a balance of 1000\n    When  I pay my student loan payment\n    Then  the balance should be less than 1000\n    When  I pay my car loan payment\n    Then  the balance should be greater than or equal to 1\n\n  Scenario: failing unittest assertion (expected to fail)\n    # this is expected to fail, to show off the unittest assertion at work\n    Given I have a balance of 100\n    Then  the balance should be greater than 100\n\n\n  Scenario: withdrawing more than balance raises ValueError (expected to fail)\n    #  this is expected to fail with a ValueError to show off the extended withdraw method\n    Given I have a balance of 100\n    When  I withdraw 500 from the account\n\n```\n\nThen if we run this using behave...\n\n```\n$ behave\nFeature: bank account balance # features/account_balance.feature:2\n\n  Background:   # features/account_balance.feature:3\n\n  Scenario: withdraw                    # features/account_balance.feature:6\n    Given I have a balance of 100       # features/steps/steps.py:25\n    When I withdraw 42 from the account # features/steps/steps.py:58\n    Then the balance should be 58       # features/steps/steps.py:45\n\n  Scenario: deposit                    # features/account_balance.feature:10\n    Given I have a balance of 100      # features/steps/steps.py:25\n    When I deposit 42 into the account # features/steps/steps.py:29\n    Then the balance should be 142     # features/steps/steps.py:45\n\n  Scenario: pay loans                                     # features/account_balance.feature:14\n    Given I have a balance of 100                         # features/steps/steps.py:25\n    Given I have a balance of 1000                        # features/steps/steps.py:25\n    When I pay my student loan payment                    # features/steps/steps.py:48\n    Then the balance should be less than 1000             # features/steps/steps.py:64\n    When I pay my car loan payment                        # features/steps/steps.py:48\n    Then the balance should be greater than or equal to 1 # features/steps/steps.py:64\n\n  Scenario: failing unittest assertion (expected to fail)  # features/account_balance.feature:21\n    Given I have a balance of 100                          # features/steps/steps.py:25\n    Given I have a balance of 100                          # features/steps/steps.py:25\n    Then the balance should be 10                          # features/steps/steps.py:45\n      Assertion Failed: 100 != 10\n\n\n  Scenario: withdrawing more than balance raises ValueError (expected to fail)  # features/account_balance.feature:27\n    Given I have a balance of 100                                               # features/steps/steps.py:25\n    Given I have a balance of 100                                               # features/steps/steps.py:25\n    When I withdraw 500 from the account                                        # features/steps/steps.py:58\n      Traceback (most recent call last):\n        \u003cpartially omitted for readme brevity\u003e\n        File \"features\\steps\\steps.py\", line 61, in withdraw\n          raise ValueError('Insufficient Funds')\n      ValueError: Insufficient Funds\n\n\n\nFailing scenarios:\n  features/account_balance.feature:21  failing unittest assertion (expected to fail)\n  features/account_balance.feature:27  withdrawing more than balance raises ValueError (expected to fail)\n\n0 features passed, 1 failed, 0 skipped\n3 scenarios passed, 2 failed, 0 skipped\n16 steps passed, 2 failed, 0 skipped, 0 undefined\nTook 0m0.008s\n```\n\nWe can see our additional scenarios ran and observe the following from the results:\n\n1. Our additional loan payment steps ran as expected\n2. Our step definition `compare_balance`, using the RegexMatcher, correctly matched our steps in the feature file\n3. Our override of the `check_balance` method worked and used unittest assertion (evidenced in the failure case)\n4. Our extended `withdraw` method was successfully used as expected (demonstrated by the failing case with ValueError)\n","project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fspyoungtech%2Fbehave-classy","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Fspyoungtech%2Fbehave-classy","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fspyoungtech%2Fbehave-classy/lists"}