From 461def3d981dcd2adc1250770bb5dad7df95282e Mon Sep 17 00:00:00 2001 From: nullhack Date: Wed, 13 May 2026 14:37:17 -0400 Subject: [PATCH 1/2] fix(ci): use workflow_run trigger for PyPI publish (like beehave) --- .github/workflows/pypi-publish.yml | 36 ++++++++++++++++++++---------- 1 file changed, 24 insertions(+), 12 deletions(-) diff --git a/.github/workflows/pypi-publish.yml b/.github/workflows/pypi-publish.yml index 26d06f3..59d5083 100644 --- a/.github/workflows/pypi-publish.yml +++ b/.github/workflows/pypi-publish.yml @@ -1,9 +1,10 @@ name: PyPI Publish on: - push: - tags: - - "v*" + workflow_run: + workflows: ["Tag Release"] + types: + - completed permissions: contents: read @@ -12,12 +13,15 @@ jobs: build: name: Build distribution runs-on: ubuntu-latest + if: ${{ github.event.workflow_run.conclusion == 'success' }} permissions: contents: read steps: - name: Checkout code uses: actions/checkout@8ade135a41bc03ea155e62e844d188df1ea18608 # v4.1.1 + with: + fetch-tags: true - name: Install uv uses: astral-sh/setup-uv@cdfb2ee6dde255817c739680168ad81e184c4bfb # v4.0.0 @@ -25,8 +29,8 @@ jobs: enable-cache: true cache-dependency-glob: "uv.lock" - - name: Set up Python 3.13 - run: uv python install 3.13 + - name: Set up Python 3.14 + run: uv python install 3.14 - name: Clean dist run: rm -rf dist/ @@ -35,7 +39,7 @@ jobs: run: uv build - name: Upload dist artifacts - uses: actions/upload-artifact@v4 + uses: actions/upload-artifact@043fb46d1a93c77aae656e7c1c64a875d1fc6a0a # v7.0.1 with: name: dist path: dist/ @@ -46,11 +50,11 @@ jobs: needs: build environment: pypi permissions: - id-token: write # required for OIDC trusted publisher + id-token: write steps: - name: Download dist artifacts - uses: actions/download-artifact@v4 + uses: actions/download-artifact@b14cf4c92630c3507bd1d16c4e9b01de8732f597 # v4.0.0 with: name: dist path: dist/ @@ -63,14 +67,22 @@ jobs: runs-on: ubuntu-latest needs: publish permissions: - contents: write # required to create a release + contents: write steps: - name: Checkout code uses: actions/checkout@8ade135a41bc03ea155e62e844d188df1ea18608 # v4.1.1 + with: + fetch-tags: true + + - name: Get version tag + id: version + run: | + VERSION=$(grep '^version' pyproject.toml | head -1 | sed 's/version = "\(.*\)"/\1/') + echo "tag=v${VERSION}" >> "$GITHUB_OUTPUT" - name: Download dist artifacts - uses: actions/download-artifact@v4 + uses: actions/download-artifact@b14cf4c92630c3507bd1d16c4e9b01de8732f597 # v4.0.0 with: name: dist path: dist/ @@ -79,7 +91,7 @@ jobs: env: GH_TOKEN: ${{ github.token }} run: | - gh release create "${{ github.ref_name }}" \ - --title "${{ github.ref_name }}" \ + gh release create "${{ steps.version.outputs.tag }}" \ + --title "${{ steps.version.outputs.tag }}" \ --generate-notes \ dist/* From 42caf7df1feb7d1d1660f787092a81cab7031a8a Mon Sep 17 00:00:00 2001 From: nullhack Date: Wed, 13 May 2026 14:38:34 -0400 Subject: [PATCH 2/2] docs: add assets, branding.md, rewrite README with correct banner path --- README.md | 116 ++++++++++++++++------------------------- docs/assets/banner.svg | 70 +++++++++++++++++++++++++ docs/assets/logo.svg | 30 +++++++++++ docs/branding.md | 26 +++++++++ 4 files changed, 172 insertions(+), 70 deletions(-) create mode 100644 docs/assets/banner.svg create mode 100644 docs/assets/logo.svg create mode 100644 docs/branding.md diff --git a/README.md b/README.md index c103edf..9e1913d 100644 --- a/README.md +++ b/README.md @@ -1,43 +1,19 @@
- pytest-beehave -

+pytest-beehave -

Generates test stubs from Gherkin .feature files, keeps them in sync, and displays BDD steps in pytest output — automatically, every time pytest runs.

+[![Python](https://img.shields.io/badge/python-%E2%89%A53.14-blue?style=for-the-badge)](https://www.python.org/downloads/) +[![License](https://img.shields.io/badge/license-MIT-green?style=for-the-badge)](LICENSE) +[![PyPI](https://img.shields.io/pypi/v/pytest-beehave?style=for-the-badge)](https://pypi.org/project/pytest-beehave/) +[![CI](https://img.shields.io/github/actions/workflow/status/nullhack/pytest-beehave/ci.yml?style=for-the-badge&label=CI)](https://github.com/nullhack/pytest-beehave/actions/workflows/ci.yml) - [![Contributors][contributors-shield]][contributors-url] - [![Forks][forks-shield]][forks-url] - [![Stargazers][stars-shield]][stars-url] - [![Issues][issues-shield]][issues-url] - [![MIT License][license-shield]][license-url] - [![CI](https://img.shields.io/github/actions/workflow/status/nullhack/pytest-beehave/ci.yml?style=for-the-badge&label=CI)](https://github.com/nullhack/pytest-beehave/actions/workflows/ci.yml) - [![Python](https://img.shields.io/badge/python-3.14-blue?style=for-the-badge)](https://www.python.org/downloads/) -
- ---- - -## What it does - -pytest-beehave is a pytest plugin powered by the [beehave](https://pypi.org/project/beehave/) library. Every time you run `pytest`, it reads your Gherkin `.feature` files and keeps your test stubs in sync: - -- **New scenario?** It generates a typed, Hypothesis-compatible test stub — already marked `@pytest.mark.skip(reason="not implemented")` so it doesn't pollute your results. -- **Scenario Outline?** It generates `@example()` and `@given()` decorators with the right parameters. -- **Test drift?** `check_all()` detects orphan tests, misplaced tests, missing placeholders, and example mismatches — and reports them as real test failures. -- **Want to see your steps?** Run with `-v` and BDD steps appear under each test name. Install `pytest-html` and a "Scenario" column appears in the report. - -All of this happens in `pytest_configure` — before pytest collects a single test. - ---- - -## Why pytest-beehave? +**Generates pytest test stubs from Gherkin `.feature` files, checks consistency, and displays BDD steps — automatically, every time pytest runs.** -BDD frameworks sold a compelling promise: human-readable specifications that live alongside your tests, kept honest by the test suite itself. The promise is real. The implementation is the problem. Every scenario explodes into a constellation of `@given`, `@when`, and `@then` step functions scattered across multiple files, wired together by fragile string matching. Refactor one step and you're hunting across the codebase. The ceremony grows with every feature, and the spec drifts from reality anyway. - -pytest-beehave is the middle ground. Write your acceptance criteria in plain Gherkin — business-readable, version-controlled, owned by the team. The plugin generates test stubs with the right names and structure, marks unimplemented ones as skipped, and flags drift before it silently rots. You implement the test body however you like, in plain pytest, with no step files and no glue code. + --- -## Installation +**pytest-beehave** is a pytest plugin powered by [beehave](https://pypi.org/project/beehave/). It reads your Gherkin `.feature` files and generates [Hypothesis](https://hypothesis.readthedocs.io/)-compatible test stubs with the right names, decorators, and parameters — then verifies that your test code stays consistent with your spec. New scenarios get `@pytest.mark.skip`; drift becomes a real test failure. ```bash pip install pytest-beehave @@ -55,7 +31,7 @@ pip install "pytest-beehave[html]" ## Quick start -**1. Write a feature file:** +### 1. Write a feature file ```gherkin # docs/features/checkout/shopping_cart.feature @@ -72,15 +48,21 @@ Feature: Shopping cart Then the order total is $120 ``` -**2. Run pytest:** +### 2. Run pytest ```bash pytest --collect-only ``` -**3. A test stub was created at `tests/features/shopping_cart/tax_calculation_test.py`:** +### 3. A test stub is created + +``` +tests/features/shopping_cart/ +├── tax_calculation_test.py +``` ```python +# tests/features/shopping_cart/tax_calculation_test.py import pytest @pytest.mark.skip(reason="not implemented") @@ -88,7 +70,7 @@ def test_VAT_is_applied_at_the_correct_rate(): ... ``` -**4. Run pytest again:** +### 4. See BDD steps ```bash pytest -v @@ -102,15 +84,15 @@ tests/features/shopping_cart/tax_calculation_test.py::test_VAT_is_applied_at_the Then the order total is $120 ``` -**5. Implement the test and ship.** +### 5. Implement and ship -Remove the `@pytest.mark.skip` decorator, replace `...` with your test logic, and run `pytest` again. The steps display stays in sync with your feature file. +Remove `@pytest.mark.skip`, write the test body, run `pytest` again. The steps display stays in sync with your feature file. --- ## How it works -pytest-beehave hooks into `pytest_configure`, the earliest possible entry point. Every stub exists on disk before pytest begins collection. +The plugin hooks into `pytest_configure` — every stub exists on disk before collection begins. ``` pytest invoked @@ -121,9 +103,8 @@ pytest invoked ├─ _add_skip_markers() → mark unimplemented stubs with @pytest.mark.skip ├─ check_all() → detect drift between features and tests └─ register display plugins → StepsReporter (-v) and/or HtmlStepsPlugin - └─ pytest_collection_modifyitems → inject synthetic failing tests for ERROR violations + └─ pytest_collection_modifyitems → inject failing tests for ERROR violations └─ Collection begins — every stub is already present - └─ Tests run ``` --- @@ -139,22 +120,22 @@ tests/features/ ← configured via tests_dir _test.py ← one file per Rule: block (or default_test.py) ``` -Each test function name follows the convention `test_`. The mapping is exact string equality — no `@id` tags, no step definitions, no glue code. +Each test function name follows `test_`. The mapping is exact string equality — no `@id` tags, no step definitions, no glue code. --- -## Consistency checking +## What `check_all` enforces -After stub generation, the plugin runs `check_all()` to detect drift between feature files and test code. Violations produce real test failures: +After stub generation, the plugin runs `check_all()` to detect drift between feature files and test code. ERROR violations produce real test failures via synthetic test items: -| Type | Severity | Meaning | -|------|----------|---------| -| `unmapped-scenario` | ERROR (fails run) | Scenario has no matching test function | -| `unmapped-test` | ERROR (fails run) | Test function has no matching scenario | -| `misplaced-test` | WARNING | Test is in the wrong rule file | -| `missing-placeholder` | ERROR (fails run) | Test body missing a placeholder | -| `missing-literal` | ERROR (fails run) | Test body missing a literal value | -| `example-mismatch` | ERROR (fails run) | Examples rows don't match `@example()` decorators | +| Type | Severity | What it catches | +|------|----------|-----------------| +| `unmapped-scenario` | ERROR | Scenario has no matching test function | +| `unmapped-test` | ERROR | Test function has no matching scenario | +| `misplaced-test` | WARNING | Function is in the wrong rule file | +| `missing-placeholder` | ERROR | Test body missing a `` | +| `missing-literal` | ERROR | Test body missing a `"string"` or numeric literal | +| `example-mismatch` | ERROR | Examples rows don't match `@example()` decorators | ``` $ pytest @@ -162,13 +143,22 @@ $ pytest ========================= 1 failed, 3 passed, 2 skipped ========================= ``` -Stub functions (body is `...`) are excluded from placeholder and literal checks — they are expected to be incomplete. +Stub functions (body is `...`) are excluded from placeholder and literal checks. + +--- + +## How it maps + +- **Scenario title → function name:** `VAT Is Applied At The Correct Rate` → `test_VAT_is_applied_at_the_correct_rate`. Lowercased. Globally unique. +- **Rule → test file:** Top-level scenarios go to `default_test.py`. Scenarios inside a Rule go to `_test.py`. +- **Feature title → directory:** `Shopping Cart` → `tests/features/shopping_cart/`. +- **Scenario Outline → decorators:** `` columns become `@given()` parameters with inferred Hypothesis strategies. Example rows become `@example()` decorators. --- ## TDD workflow -1. `pytest --collect-only` → stubs generated with `@pytest.mark.skip` → all skipped +1. `pytest --collect-only` → stubs generated with `@pytest.mark.skip` 2. Remove `@pytest.mark.skip`, write the test body → test runs and fails (red) 3. Fix the implementation → test passes (green) 4. Add new scenarios to `.feature` files → only new stubs get the skip marker @@ -188,7 +178,7 @@ background_check_numeric = true # default: true background_check_string = true # default: true ``` -If `features_dir` does not exist, the plugin exits silently (no error, no stub generation). +If `features_dir` does not exist, the plugin exits silently. --- @@ -211,24 +201,10 @@ uv sync --all-extras uv run task test && uv run task lint && uv run task static-check ``` -Bug reports and pull requests are welcome on [GitHub](https://github.com/nullhack/pytest-beehave/issues). +Bug reports and pull requests welcome on [GitHub](https://github.com/nullhack/pytest-beehave/issues). --- ## License MIT — see [LICENSE](LICENSE). - -**Author:** eol ([@nullhack](https://github.com/nullhack)) · [Documentation](https://nullhack.github.io/pytest-beehave) - - -[contributors-shield]: https://img.shields.io/github/contributors/nullhack/pytest-beehave.svg?style=for-the-badge -[contributors-url]: https://github.com/nullhack/pytest-beehave/graphs/contributors -[forks-shield]: https://img.shields.io/github/forks/nullhack/pytest-beehave.svg?style=for-the-badge -[forks-url]: https://github.com/nullhack/pytest-beehave/network/members -[stars-shield]: https://img.shields.io/github/stars/nullhack/pytest-beehave.svg?style=for-the-badge -[stars-url]: https://github.com/nullhack/pytest-beehave/stargazers -[issues-shield]: https://img.shields.io/github/issues/nullhack/pytest-beehave.svg?style=for-the-badge -[issues-url]: https://github.com/nullhack/pytest-beehave/issues -[license-shield]: https://img.shields.io/badge/license-MIT-green?style=for-the-badge -[license-url]: https://github.com/nullhack/pytest-beehave/blob/main/LICENSE diff --git a/docs/assets/banner.svg b/docs/assets/banner.svg new file mode 100644 index 0000000..a20f232 --- /dev/null +++ b/docs/assets/banner.svg @@ -0,0 +1,70 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + pytest + + + + + Beehave + + + + + + diff --git a/docs/assets/logo.svg b/docs/assets/logo.svg new file mode 100644 index 0000000..ad639d5 --- /dev/null +++ b/docs/assets/logo.svg @@ -0,0 +1,30 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/docs/branding.md b/docs/branding.md new file mode 100644 index 0000000..63c40a9 --- /dev/null +++ b/docs/branding.md @@ -0,0 +1,26 @@ +# Branding — pytest-beehave + +## Identity + +- **Project name:** pytest-beehave +- **Tagline:** Gherkin to pytest, automatically +- **Tone of voice:** Friendly, precise, industrious, collaborative + +## Visual + +- **Primary color:** `#1e2128` — deep charcoal (dark backgrounds, banner fill) +- **Accent bright:** `#f5a800` — amber gold (brand mark, "Bee" wordmark, highlights) +- **Accent deep:** `#c87d00` — burnt amber (stripes, secondary accents) +- **Text color:** `#e8eaef` — off-white (primary typography on dark) +- **Muted text:** `#a0a5b0` — cool grey (secondary typography, "pytest" label) + +> Colors meet WCAG 2.1 AA when white text is placed on the primary. + +## Assets + +- **`docs/assets/logo.svg`** — Brand mark inspired by the pytest logo. A horizontal bar with four descending vertical bars (echoing pytest's test-result motif) in amber and burnt amber, topped with curved bee antennae and bulb tips. Rendered in bright amber on dark charcoal. +- **`docs/assets/banner.svg`** — GitHub README banner. Dark charcoal background with the amber bar-chart bee mark on the left, small grey "pytest" label above the large "Beehave" wordmark (amber "Bee", off-white "have"). + +## Release Naming + +- **Convention:** Adjective-animal (e.g., "Busy Badger", "Diligent Dolphin")