{"id":28545489,"url":"https://github.com/pytest-dev/pytest-describe","last_synced_at":"2026-04-05T17:37:51.810Z","repository":{"id":20715981,"uuid":"23999995","full_name":"pytest-dev/pytest-describe","owner":"pytest-dev","description":"Describe-style plugin for the pytest framework","archived":false,"fork":false,"pushed_at":"2025-12-12T18:44:03.000Z","size":102,"stargazers_count":230,"open_issues_count":7,"forks_count":18,"subscribers_count":6,"default_branch":"main","last_synced_at":"2026-04-03T14:06:16.928Z","etag":null,"topics":["pytest","pytest-plugin","python","testing"],"latest_commit_sha":null,"homepage":"","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/pytest-dev.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,"governance":null,"roadmap":null,"authors":null,"dei":null,"publiccode":null,"codemeta":null}},"created_at":"2014-09-13T16:54:52.000Z","updated_at":"2026-03-20T06:25:50.000Z","dependencies_parsed_at":"2024-06-18T15:25:53.671Z","dependency_job_id":"96312346-cf5c-4ebf-992e-2d7af8926ead","html_url":"https://github.com/pytest-dev/pytest-describe","commit_stats":{"total_commits":82,"total_committers":13,"mean_commits":"6.3076923076923075","dds":0.5853658536585367,"last_synced_commit":"cffdf710ebb9a7e1dd4e3a364f0986ac85bf51f2"},"previous_names":[],"tags_count":6,"template":false,"template_full_name":null,"purl":"pkg:github/pytest-dev/pytest-describe","repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/pytest-dev%2Fpytest-describe","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/pytest-dev%2Fpytest-describe/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/pytest-dev%2Fpytest-describe/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/pytest-dev%2Fpytest-describe/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/pytest-dev","download_url":"https://codeload.github.com/pytest-dev/pytest-describe/tar.gz/refs/heads/main","sbom_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/pytest-dev%2Fpytest-describe/sbom","scorecard":{"id":752066,"data":{"date":"2025-08-11","repo":{"name":"github.com/pytest-dev/pytest-describe","commit":"cffdf710ebb9a7e1dd4e3a364f0986ac85bf51f2"},"scorecard":{"version":"v5.2.1-40-gf6ed084d","commit":"f6ed084d17c9236477efd66e5b258b9d4cc7b389"},"score":3.5,"checks":[{"name":"Dangerous-Workflow","score":10,"reason":"no dangerous workflow patterns detected","details":null,"documentation":{"short":"Determines if the project's GitHub Action workflows avoid dangerous patterns.","url":"https://github.com/ossf/scorecard/blob/f6ed084d17c9236477efd66e5b258b9d4cc7b389/docs/checks.md#dangerous-workflow"}},{"name":"Maintained","score":0,"reason":"0 commit(s) and 0 issue activity found in the last 90 days -- score normalized to 0","details":null,"documentation":{"short":"Determines if the project is \"actively maintained\".","url":"https://github.com/ossf/scorecard/blob/f6ed084d17c9236477efd66e5b258b9d4cc7b389/docs/checks.md#maintained"}},{"name":"Binary-Artifacts","score":10,"reason":"no binaries found in the repo","details":null,"documentation":{"short":"Determines if the project has generated executable (binary) artifacts in the source repository.","url":"https://github.com/ossf/scorecard/blob/f6ed084d17c9236477efd66e5b258b9d4cc7b389/docs/checks.md#binary-artifacts"}},{"name":"Code-Review","score":1,"reason":"Found 4/25 approved changesets -- score normalized to 1","details":null,"documentation":{"short":"Determines if the project requires human code review before pull requests (aka merge requests) are merged.","url":"https://github.com/ossf/scorecard/blob/f6ed084d17c9236477efd66e5b258b9d4cc7b389/docs/checks.md#code-review"}},{"name":"Token-Permissions","score":0,"reason":"detected GitHub workflow tokens with excessive permissions","details":["Warn: no topLevel permission defined: .github/workflows/main.yml:1","Info: no jobLevel write permissions found"],"documentation":{"short":"Determines if the project's workflows follow the principle of least privilege.","url":"https://github.com/ossf/scorecard/blob/f6ed084d17c9236477efd66e5b258b9d4cc7b389/docs/checks.md#token-permissions"}},{"name":"Pinned-Dependencies","score":0,"reason":"dependency not pinned by hash detected -- score normalized to 0","details":["Warn: GitHub-owned GitHubAction not pinned by hash: .github/workflows/main.yml:23: update your workflow using https://app.stepsecurity.io/secureworkflow/pytest-dev/pytest-describe/main.yml/main?enable=pin","Warn: GitHub-owned GitHubAction not pinned by hash: .github/workflows/main.yml:28: update your workflow using https://app.stepsecurity.io/secureworkflow/pytest-dev/pytest-describe/main.yml/main?enable=pin","Warn: GitHub-owned GitHubAction not pinned by hash: .github/workflows/main.yml:85: update your workflow using https://app.stepsecurity.io/secureworkflow/pytest-dev/pytest-describe/main.yml/main?enable=pin","Warn: GitHub-owned GitHubAction not pinned by hash: .github/workflows/main.yml:89: update your workflow using https://app.stepsecurity.io/secureworkflow/pytest-dev/pytest-describe/main.yml/main?enable=pin","Warn: third-party GitHubAction not pinned by hash: .github/workflows/main.yml:102: update your workflow using https://app.stepsecurity.io/secureworkflow/pytest-dev/pytest-describe/main.yml/main?enable=pin","Warn: pipCommand not pinned by hash: .github/workflows/main.yml:34","Warn: pipCommand not pinned by hash: .github/workflows/main.yml:35","Warn: pipCommand not pinned by hash: .github/workflows/main.yml:95","Warn: pipCommand not pinned by hash: .github/workflows/main.yml:96","Info:   0 out of   4 GitHub-owned GitHubAction dependencies pinned","Info:   0 out of   1 third-party GitHubAction dependencies pinned","Info:   0 out of   4 pipCommand dependencies pinned"],"documentation":{"short":"Determines if the project has declared and pinned the dependencies of its build process.","url":"https://github.com/ossf/scorecard/blob/f6ed084d17c9236477efd66e5b258b9d4cc7b389/docs/checks.md#pinned-dependencies"}},{"name":"CII-Best-Practices","score":0,"reason":"no effort to earn an OpenSSF best practices badge detected","details":null,"documentation":{"short":"Determines if the project has an OpenSSF (formerly CII) Best Practices Badge.","url":"https://github.com/ossf/scorecard/blob/f6ed084d17c9236477efd66e5b258b9d4cc7b389/docs/checks.md#cii-best-practices"}},{"name":"Security-Policy","score":0,"reason":"security policy file not detected","details":["Warn: no security policy file detected","Warn: no security file to analyze","Warn: no security file to analyze","Warn: no security file to analyze"],"documentation":{"short":"Determines if the project has published a security policy.","url":"https://github.com/ossf/scorecard/blob/f6ed084d17c9236477efd66e5b258b9d4cc7b389/docs/checks.md#security-policy"}},{"name":"Vulnerabilities","score":10,"reason":"0 existing vulnerabilities detected","details":null,"documentation":{"short":"Determines if the project has open, known unfixed vulnerabilities.","url":"https://github.com/ossf/scorecard/blob/f6ed084d17c9236477efd66e5b258b9d4cc7b389/docs/checks.md#vulnerabilities"}},{"name":"Fuzzing","score":0,"reason":"project is not fuzzed","details":["Warn: no fuzzer integrations found"],"documentation":{"short":"Determines if the project uses fuzzing.","url":"https://github.com/ossf/scorecard/blob/f6ed084d17c9236477efd66e5b258b9d4cc7b389/docs/checks.md#fuzzing"}},{"name":"Packaging","score":-1,"reason":"packaging workflow not detected","details":["Warn: no GitHub/GitLab publishing workflow detected."],"documentation":{"short":"Determines if the project is published as a package that others can easily download, install, easily update, and uninstall.","url":"https://github.com/ossf/scorecard/blob/f6ed084d17c9236477efd66e5b258b9d4cc7b389/docs/checks.md#packaging"}},{"name":"License","score":10,"reason":"license file detected","details":["Info: project has a license file: LICENSE:0","Info: FSF or OSI recognized license: MIT License: LICENSE:0"],"documentation":{"short":"Determines if the project has defined a license.","url":"https://github.com/ossf/scorecard/blob/f6ed084d17c9236477efd66e5b258b9d4cc7b389/docs/checks.md#license"}},{"name":"Signed-Releases","score":-1,"reason":"no releases found","details":null,"documentation":{"short":"Determines if the project cryptographically signs release artifacts.","url":"https://github.com/ossf/scorecard/blob/f6ed084d17c9236477efd66e5b258b9d4cc7b389/docs/checks.md#signed-releases"}},{"name":"Branch-Protection","score":0,"reason":"branch protection not enabled on development/release branches","details":["Warn: branch protection not enabled for branch 'main'"],"documentation":{"short":"Determines if the default and release branches are protected with GitHub's branch protection settings.","url":"https://github.com/ossf/scorecard/blob/f6ed084d17c9236477efd66e5b258b9d4cc7b389/docs/checks.md#branch-protection"}},{"name":"SAST","score":0,"reason":"SAST tool is not run on all commits -- score normalized to 0","details":["Warn: 0 commits out of 10 are checked with a SAST tool"],"documentation":{"short":"Determines if the project uses static code analysis.","url":"https://github.com/ossf/scorecard/blob/f6ed084d17c9236477efd66e5b258b9d4cc7b389/docs/checks.md#sast"}}]},"last_synced_at":"2025-08-22T20:39:03.070Z","repository_id":20715981,"created_at":"2025-08-22T20:39:03.070Z","updated_at":"2025-08-22T20:39:03.070Z"},"host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":286080680,"owners_count":31444702,"icon_url":"https://github.com/github.png","version":null,"created_at":"2022-05-30T11:31:42.601Z","updated_at":"2026-04-05T15:22:31.103Z","status":"ssl_error","status_checked_at":"2026-04-05T15:22:00.205Z","response_time":75,"last_error":"SSL_connect returned=1 errno=0 peeraddr=140.82.121.5:443 state=error: unexpected eof while reading","robots_txt_status":"success","robots_txt_updated_at":"2025-07-24T06:49:26.215Z","robots_txt_url":"https://github.com/robots.txt","online":false,"can_crawl_api":true,"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":["pytest","pytest-plugin","python","testing"],"created_at":"2025-06-09T23:07:53.200Z","updated_at":"2026-04-05T17:37:51.804Z","avatar_url":"https://github.com/pytest-dev.png","language":"Python","readme":"[![PyPI version](https://badge.fury.io/py/pytest-describe.svg)](https://pypi.org/project/pytest-describe/)\n[![Workflow status](https://github.com/pytest-dev/pytest-describe/actions/workflows/main.yml/badge.svg)](https://github.com/pytest-dev/pytest-describe/actions)\n\n# Describe-style plugin for pytest\n\n**pytest-describe** is a plugin for [pytest](https://docs.pytest.org/)\nthat allows tests to be written in arbitrary nested describe-blocks,\nsimilar to RSpec (Ruby) and Jasmine (JavaScript).\n\nThe main inspiration for this was\na [video](https://www.youtube.com/watch?v=JJle8L8FRy0\u003e) by Gary Bernhardt.\n\n## Installation\n\nYou guessed it:\n\n```sh\npip install pytest-describe\n```\n\n## Usage\n\nPytest will automatically find the plugin and use it when you run pytest.\nRunning pytest will show that the plugin is loaded:\n\n```sh\n$ pytest \n...\nplugins: describe-3.0.0\n...\n```\n\nTests can now be written in describe-blocks.\nHere is an example for testing a Wallet class:\n\n```python \nimport pytest\n\n\nclass Wallet:\n\n    def __init__(self, initial_amount=0):\n        self.balance = initial_amount\n\n    def spend_cash(self, amount):\n        if self.balance \u003c amount:\n            raise ValueError(f'Not enough available to spend {amount}')\n        self.balance -= amount\n\n    def add_cash(self, amount):\n        self.balance += amount\n        \n        \ndef describe_wallet():\n    \n    def describe_start_empty():\n        \n        @pytest.fixture\n        def wallet():\n            return Wallet()\n\n        def initial_amount(wallet):\n            assert wallet.balance == 0\n    \n        def add_cash(wallet):\n            wallet.add_cash(80)\n            assert wallet.balance == 80\n\n        def spend_cash(wallet):\n            with pytest.raises(ValueError):\n                wallet.spend_cash(10)\n\n    def describe_with_starting_balance():\n        \n        @pytest.fixture\n        def wallet():\n            return Wallet(20)\n\n        def initial_amount(wallet):\n            assert wallet.balance == 20\n    \n        def describe_adding():\n            \n            def add_little_cash(wallet):\n                wallet.add_cash(5)\n                assert wallet.balance == 25\n    \n            def add_much_cash(wallet):\n                wallet.add_cash(980)\n                assert wallet.balance == 1000\n                \n        def describe_spending():\n            \n            def spend_cash(wallet):\n                wallet.spend_cash(15)\n                assert wallet.balance == 5\n        \n            def spend_too_much_cash(wallet):\n                with pytest.raises(ValueError):\n                    wallet.spend_cash(25)\n```\n\nThe default prefix for describe-blocks is `describe_`, but you can configure it \nin the pytest/python configuration file via `describe_prefixes` or\nvia the command line option `--describe-prefixes`.\n\nFor example in your `pyproject.toml`:\n\n```toml    \n[tool.pytest.ini_options]\ndescribe_prefixes = [\"custom_prefix_\"]\n```\n\nFunctions prefixed with `_` in the describe-block are not collected as tests. \nThis can be used to group helper functions. Otherwise, functions inside the \ndescribe-blocks need not follow any special naming convention.\n\n```python\ndef describe_function():\n\n    def _helper():\n        return \"something\"\n\n    def it_does_something():\n        value = _helper()\n        ...\n```\n\n\n## Why bother?\n\nI've found that quite often my tests have one \"dimension\" more than my production\ncode. The production code is organized into packages, modules, classes\n(sometimes), and functions. I like to organize my tests in the same way, but\ntests also have different *cases* for each function. This tends to end up with\na set of tests for each module (or class), where each test has to name both a\nfunction and a *case*. For instance:\n\n```python\ndef test_my_function_with_default_arguments():\ndef test_my_function_with_some_other_arguments():\ndef test_my_function_throws_exception():\ndef test_my_function_handles_exception():\ndef test_some_other_function_returns_true():\ndef test_some_other_function_returns_false():\n```\n\nIt's much nicer to do this:\n\n```python\ndef describe_my_function():\n    def with_default_arguments():\n    def with_some_other_arguments():\n    def it_throws_exception():\n    def it_handles_exception():\n\ndef describe_some_other_function():\n    def it_returns_true():\n    def it_returns_false():\n```\n\nIt has the additional advantage that you can have marks and fixtures that apply\nlocally to each group of test function.\n\nWith pytest, it's possible to organize tests in a similar way with classes.\nHowever, I think classes are awkward. I don't think the convention of using\ncamel-case names for classes fit very well when testing functions in different\ncases. In addition, every test function must take a \"self\" argument that is\nnever used.\n\nThe pytest-describe plugin allows organizing your tests in the nicer way shown\nabove using describe-blocks.\n\n## Shared Behaviors\n\nIf you've used rspec's shared examples or test class inheritance, then you may\nbe familiar with the benefit of having the same tests apply to\nmultiple \"subjects\" or \"suts\" (system under test).\n\n```python\nfrom pytest import fixture\nfrom pytest_describe import behaves_like\n\ndef a_duck():\n    def it_quacks(sound):\n        assert sound == \"quack\"\n\n@behaves_like(a_duck)\ndef describe_something_that_quacks():\n    @fixture\n    def sound():\n        return \"quack\"\n\n    # the it_quacks test in this describe will pass\n\n@behaves_like(a_duck)\ndef describe_something_that_barks():\n    @fixture\n    def sound():\n        return \"bark\"\n\n    # the it_quacks test in this describe will fail (as expected)\n```\n\nFixtures defined in the block that includes the shared behavior take precedence\nover fixtures defined in the shared behavior. This rule only applies to\nfixtures, not to other functions (nested describe blocks and tests). Instead,\nthey are all collected as separate tests.\n","funding_links":[],"categories":[],"sub_categories":[],"project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fpytest-dev%2Fpytest-describe","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Fpytest-dev%2Fpytest-describe","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fpytest-dev%2Fpytest-describe/lists"}