Skip to content

Commit 49e87b0

Browse files
committed
refactor(workflow): relax tests/unit/ Hypothesis mandate to guidance
Replace the hard rule that tests/unit/ must use @given with guidance: use Hypothesis for properties spanning many inputs, plain pytest for specific behaviors or single edge cases. @pytest.mark.slow remains mandatory on all @given-decorated tests. Affected files: AGENTS.md, tdd/SKILL.md, implementation/SKILL.md, verify/SKILL.md
1 parent 25ce58a commit 49e87b0

File tree

4 files changed

+15
-16
lines changed

4 files changed

+15
-16
lines changed

.opencode/skills/implementation/SKILL.md

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -309,7 +309,7 @@ If during implementation you discover a behavior not covered by existing accepta
309309
- Note the gap in TODO.md under `## Next`
310310
- The PO will decide whether to add a new Example to the `.feature` file
311311

312-
Extra tests in `tests/unit/` are allowed freely (coverage, edge cases, etc.) — these do not need `@id` traceability. **Every test in `tests/unit/` must be a Hypothesis property test: `@given` is required, `@pytest.mark.slow` is mandatory, plain `assert` tests without `@given` are forbidden.**
312+
Extra tests in `tests/unit/` are allowed freely (coverage, edge cases, etc.) — these do not need `@id` traceability. Use Hypothesis (`@given`) for properties that hold across many inputs; use plain pytest for specific behaviors or single edge cases. `@pytest.mark.slow` is mandatory on every `@given`-decorated test.
313313

314314
## Signature Design
315315

.opencode/skills/tdd/SKILL.md

Lines changed: 8 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -105,21 +105,21 @@ The correct test asserts on the return value. The wrong test breaks if you renam
105105

106106
Tests in `tests/features/` are generated from `@id` criteria — use plain pytest there.
107107

108-
Tests in `tests/unit/` cover gaps not represented by any acceptance criterion. **Every test in `tests/unit/` must be a Hypothesis property test.** No plain `assert` tests without `@given` are permitted in `tests/unit/`.
108+
Tests in `tests/unit/` cover gaps not represented by any acceptance criterion. Any test style is valid — plain `assert` or Hypothesis `@given`. Use Hypothesis when the test covers a **property** that holds across many inputs (mathematical invariants, parsing contracts, value object constraints). Use plain pytest for specific behaviors or single edge cases discovered during refactoring.
109109

110110
| Situation | Location | Tool |
111111
|---|---|---|
112112
| Deterministic scenario from a `.feature` `@id` | `tests/features/` | Plain pytest (generated) |
113-
| Pure function needing many input combinations | `tests/unit/` | Hypothesis `@given` |
113+
| Property holding across many input values | `tests/unit/` | Hypothesis `@given` |
114+
| Specific behavior or single edge case | `tests/unit/` | Plain pytest |
114115
| Stateful system with sequences of operations | `tests/unit/` | Hypothesis stateful testing |
115116

116117
**Never use Hypothesis for**: I/O, side effects, network calls, database writes.
117118

118119
### `tests/unit/` Rules
119120

120-
- `@given(...)` is **required** on every test — no exceptions
121-
- `@pytest.mark.slow` is **mandatory** on every Hypothesis test
122-
- `@example(...)` is optional but encouraged for known corner cases
121+
- `@pytest.mark.slow` is **mandatory** on every `@given`-decorated test (Hypothesis is genuinely slow)
122+
- `@example(...)` is optional but encouraged when using `@given` to document known corner cases
123123
- `@pytest.mark.unit` or `@pytest.mark.integration` still required (one each)
124124

125125
## Markers (4 total)
@@ -147,11 +147,11 @@ When in doubt, start with `unit`. Upgrade to `integration` if the implementation
147147

148148
## Hypothesis Tests
149149

150-
Required decorator order for every `tests/unit/` test:
150+
When using `@given` in `tests/unit/`, the required decorator order is:
151151

152152
```python
153153
@pytest.mark.unit # required: exactly one of unit or integration
154-
@pytest.mark.slow # required: mandatory on all Hypothesis tests
154+
@pytest.mark.slow # required: mandatory on all @given tests
155155
@given(x=st.floats(min_value=-100, max_value=100, allow_nan=False))
156156
@example(x=0.0) # optional: document known corner cases
157157
@settings(max_examples=200)
@@ -166,7 +166,7 @@ def test_wall_bounce_c4d5e6f7(x: float) -> None:
166166
assert result >= 0
167167
```
168168

169-
A test missing `@given` or `@pytest.mark.slow` in `tests/unit/` is a FAIL at Step 5 review.
169+
A `@given`-decorated test missing `@pytest.mark.slow` is a FAIL at Step 5 review.
170170

171171
### Meaningful vs. Tautological Property Tests
172172

.opencode/skills/verify/SKILL.md

Lines changed: 0 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -129,7 +129,6 @@ Read the source files changed in this feature. **Do this before running lint/sta
129129
| Every `@id` has a mapped test | Match `@id` tags in `.feature` files to test functions | All mapped | Missing test | Write the missing test |
130130
| No `@id` used by two functions | Check for duplicate `@id` hex in test function names | None | Duplicate found | Consolidate into Hypothesis `@given` + `@example` or escalate to PO |
131131
| Function naming | Test names match `test_<rule_slug>_<8char_hex>` | All match | Mismatch | Rename function |
132-
| All `tests/unit/` tests use `@given` | Read every test in `tests/unit/`; check for `@given` decorator | All have `@given` | Any test lacks `@given` | Rewrite as a Hypothesis property test |
133132
| All Hypothesis tests have `@pytest.mark.slow` | Read every `@given`-decorated test for the `@slow` marker | All present | Any missing | Add `@pytest.mark.slow` |
134133

135134
#### 4g. Code Quality — any FAIL → REJECTED
@@ -225,5 +224,4 @@ OR
225224
| Duplicate `@id` in tests | 0 |
226225
| Empty evidence cells | 0 |
227226
| Orphaned tests | 0 |
228-
| Plain tests without `@given` in `tests/unit/` | 0 |
229227
| Hypothesis tests missing `@pytest.mark.slow` | 0 |

AGENTS.md

Lines changed: 6 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -84,13 +84,14 @@ tests/
8484
features/<feature-name>/
8585
<rule-slug>_test.py ← one per Rule: block, stubs from gen-tests
8686
unit/
87-
<anything>_test.py ← developer-authored extras (Hypothesis only)
87+
<anything>_test.py ← developer-authored extras (no @id traceability)
8888
```
8989

90-
Every test in `tests/unit/` **must** be a Hypothesis property test:
91-
- `@given(...)` is required — no plain `assert` tests without `@given`
92-
- `@pytest.mark.slow` is mandatory on every Hypothesis test
93-
- `@example(...)` is optional but encouraged for known corner cases
90+
Tests in `tests/unit/` are developer-authored extras not covered by any `@id` criterion. Any test style is valid — plain `assert` or Hypothesis `@given`. Use Hypothesis when the test covers a **property** that holds across many inputs (mathematical invariants, parsing contracts, value object constraints). Use plain pytest for specific behaviors or single edge cases discovered during refactoring.
91+
92+
- `@pytest.mark.slow` is mandatory on every `@given`-decorated test (Hypothesis is genuinely slow)
93+
- `@example(...)` is optional but encouraged when using `@given` to document known corner cases
94+
- No `@id` tags — tests with `@id` belong in `tests/features/`, generated by `gen-tests`
9495

9596
## Gherkin Format
9697

0 commit comments

Comments
 (0)