pytest-bdd-property 0.1.0__tar.gz
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- pytest_bdd_property-0.1.0/LICENSE +21 -0
- pytest_bdd_property-0.1.0/PKG-INFO +422 -0
- pytest_bdd_property-0.1.0/README.md +395 -0
- pytest_bdd_property-0.1.0/pyproject.toml +96 -0
- pytest_bdd_property-0.1.0/pytest_bdd_property/__init__.py +47 -0
- pytest_bdd_property-0.1.0/pytest_bdd_property/assumptions.py +133 -0
- pytest_bdd_property-0.1.0/pytest_bdd_property/context.py +24 -0
- pytest_bdd_property-0.1.0/pytest_bdd_property/helpers.py +82 -0
- pytest_bdd_property-0.1.0/pytest_bdd_property/plugin.py +199 -0
- pytest_bdd_property-0.1.0/pytest_bdd_property/registry.py +98 -0
- pytest_bdd_property-0.1.0/pytest_bdd_property/runner.py +65 -0
- pytest_bdd_property-0.1.0/pytest_bdd_property/types.py +27 -0
- pytest_bdd_property-0.1.0/pytest_bdd_property.egg-info/PKG-INFO +422 -0
- pytest_bdd_property-0.1.0/pytest_bdd_property.egg-info/SOURCES.txt +17 -0
- pytest_bdd_property-0.1.0/pytest_bdd_property.egg-info/dependency_links.txt +1 -0
- pytest_bdd_property-0.1.0/pytest_bdd_property.egg-info/entry_points.txt +2 -0
- pytest_bdd_property-0.1.0/pytest_bdd_property.egg-info/requires.txt +3 -0
- pytest_bdd_property-0.1.0/pytest_bdd_property.egg-info/top_level.txt +1 -0
- pytest_bdd_property-0.1.0/setup.cfg +4 -0
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2025-2026 pytest-bdd-property Contributors
|
|
4
|
+
|
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
6
|
+
of this software and associated documentation files (the "Software"), to deal
|
|
7
|
+
in the Software without restriction, including without limitation the rights
|
|
8
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
9
|
+
copies of the Software, and to permit persons to whom the Software is
|
|
10
|
+
furnished to do so, subject to the following conditions:
|
|
11
|
+
|
|
12
|
+
The above copyright notice and this permission notice shall be included in all
|
|
13
|
+
copies or substantial portions of the Software.
|
|
14
|
+
|
|
15
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
16
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
17
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
18
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
19
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
20
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
21
|
+
SOFTWARE.
|
|
@@ -0,0 +1,422 @@
|
|
|
1
|
+
Metadata-Version: 2.4
|
|
2
|
+
Name: pytest-bdd-property
|
|
3
|
+
Version: 0.1.0
|
|
4
|
+
Summary: Property-based testing plugin for pytest-bdd — express universal invariants in standard Gherkin, executed by Hypothesis
|
|
5
|
+
Author: pytest-bdd-property Contributors
|
|
6
|
+
License: MIT
|
|
7
|
+
Project-URL: Homepage, https://github.com/bryonjacob/pytest-bdd-property
|
|
8
|
+
Project-URL: Repository, https://github.com/bryonjacob/pytest-bdd-property
|
|
9
|
+
Project-URL: Issues, https://github.com/bryonjacob/pytest-bdd-property/issues
|
|
10
|
+
Keywords: pytest,bdd,property-based-testing,gherkin,hypothesis,pbt
|
|
11
|
+
Classifier: Development Status :: 3 - Alpha
|
|
12
|
+
Classifier: Framework :: Pytest
|
|
13
|
+
Classifier: Intended Audience :: Developers
|
|
14
|
+
Classifier: License :: OSI Approved :: MIT License
|
|
15
|
+
Classifier: Programming Language :: Python :: 3
|
|
16
|
+
Classifier: Programming Language :: Python :: 3.11
|
|
17
|
+
Classifier: Programming Language :: Python :: 3.12
|
|
18
|
+
Classifier: Programming Language :: Python :: 3.13
|
|
19
|
+
Classifier: Topic :: Software Development :: Testing
|
|
20
|
+
Requires-Python: >=3.11
|
|
21
|
+
Description-Content-Type: text/markdown
|
|
22
|
+
License-File: LICENSE
|
|
23
|
+
Requires-Dist: pytest>=8.0
|
|
24
|
+
Requires-Dist: pytest-bdd>=7.0
|
|
25
|
+
Requires-Dist: hypothesis>=6.0
|
|
26
|
+
Dynamic: license-file
|
|
27
|
+
|
|
28
|
+
# pytest-bdd-property
|
|
29
|
+
|
|
30
|
+
Property-based testing for Gherkin scenarios, powered by [Hypothesis](https://hypothesis.readthedocs.io/).
|
|
31
|
+
|
|
32
|
+
Express universal invariants in standard Gherkin, and have Hypothesis generate 100+ inputs to find counterexamples automatically.
|
|
33
|
+
|
|
34
|
+
## Quick Start
|
|
35
|
+
|
|
36
|
+
### 1. Tag your scenario with `@property-based`
|
|
37
|
+
|
|
38
|
+
```gherkin
|
|
39
|
+
@property-based
|
|
40
|
+
Feature: Password Hashing Properties
|
|
41
|
+
|
|
42
|
+
Scenario: A password always verifies against its own hash
|
|
43
|
+
Given any valid password <P>
|
|
44
|
+
When <P> is hashed producing <H>
|
|
45
|
+
Then <P> verifies against <H>
|
|
46
|
+
```
|
|
47
|
+
|
|
48
|
+
### 2. Register your conftest step definitions
|
|
49
|
+
|
|
50
|
+
In your root `conftest.py`:
|
|
51
|
+
|
|
52
|
+
```python
|
|
53
|
+
import pytest_bdd_property.plugin # registers pytest hooks
|
|
54
|
+
|
|
55
|
+
from pytest_bdd import given, when, then, parsers
|
|
56
|
+
from pytest_bdd_property.plugin import (
|
|
57
|
+
handle_given_step,
|
|
58
|
+
handle_when_step,
|
|
59
|
+
handle_then_step,
|
|
60
|
+
)
|
|
61
|
+
|
|
62
|
+
@given(parsers.re(r"(?P<text>any .+|<.+)"))
|
|
63
|
+
def property_given_step(text):
|
|
64
|
+
handle_given_step(text)
|
|
65
|
+
|
|
66
|
+
@when(parsers.re(r"(?P<text><.+)"))
|
|
67
|
+
def property_when_step(text):
|
|
68
|
+
handle_when_step(text)
|
|
69
|
+
|
|
70
|
+
@then(parsers.re(r"(?P<text><.+)"))
|
|
71
|
+
def property_then_step(text):
|
|
72
|
+
handle_then_step(text)
|
|
73
|
+
```
|
|
74
|
+
|
|
75
|
+
### 3. Write property step definitions
|
|
76
|
+
|
|
77
|
+
```python
|
|
78
|
+
from pytest_bdd import scenarios
|
|
79
|
+
from pytest_bdd_property import register_strategy, property_when, property_then
|
|
80
|
+
from hypothesis import strategies as st
|
|
81
|
+
|
|
82
|
+
scenarios("my_feature.feature")
|
|
83
|
+
|
|
84
|
+
register_strategy("valid password", lambda: st.text(min_size=8, max_size=128))
|
|
85
|
+
|
|
86
|
+
@property_when(r"<(\w+)> is hashed producing <(\w+)>")
|
|
87
|
+
def hash_step(vals, results, pw_var, hash_var):
|
|
88
|
+
results[hash_var] = hash_password(vals[pw_var])
|
|
89
|
+
|
|
90
|
+
@property_then(r"<(\w+)> verifies against <(\w+)>")
|
|
91
|
+
def verify_step(vals, results, pw_var, hash_var):
|
|
92
|
+
assert verify_password(vals[pw_var], results[hash_var])
|
|
93
|
+
```
|
|
94
|
+
|
|
95
|
+
### 4. Run tests
|
|
96
|
+
|
|
97
|
+
```bash
|
|
98
|
+
pytest tests/ -v
|
|
99
|
+
```
|
|
100
|
+
|
|
101
|
+
Hypothesis runs each `@property-based` scenario 100 times with generated inputs. On failure, it **shrinks** to find the minimal counterexample.
|
|
102
|
+
|
|
103
|
+
---
|
|
104
|
+
|
|
105
|
+
## How It Works
|
|
106
|
+
|
|
107
|
+
### Two-Phase Execution Model
|
|
108
|
+
|
|
109
|
+
**Phase 1 (Registration):** During normal pytest-bdd step execution:
|
|
110
|
+
- `Given any <type> <var>` → registers a Hypothesis strategy
|
|
111
|
+
- `And <A> is not equal to <B>` → registers an assumption (filter)
|
|
112
|
+
- `When`/`Then` steps → register callbacks (don't execute yet)
|
|
113
|
+
|
|
114
|
+
**Phase 2 (Execution):** After all steps run, a pytest hook triggers:
|
|
115
|
+
- Hypothesis builds a composite strategy from all registered strategies
|
|
116
|
+
- Runs the property 100+ times with generated inputs
|
|
117
|
+
- Applies assumptions via `hypothesis.assume()`
|
|
118
|
+
- Executes When callbacks (actions), then Then callbacks (assertions)
|
|
119
|
+
- On failure: shrinks to find the minimal counterexample
|
|
120
|
+
|
|
121
|
+
### Architecture
|
|
122
|
+
|
|
123
|
+
```
|
|
124
|
+
conftest.py test_my_feature.py
|
|
125
|
+
├── plugin hooks ├── scenarios("my.feature")
|
|
126
|
+
└── catch-all steps ├── register_strategy(...)
|
|
127
|
+
│ ├── @property_when(...)
|
|
128
|
+
▼ └── @property_then(...)
|
|
129
|
+
plugin.py
|
|
130
|
+
├── pytest_runtest_setup → detect @property-based tag
|
|
131
|
+
├── handle_given_step → strategy + assumption registration
|
|
132
|
+
├── handle_when_step → action callback registration
|
|
133
|
+
├── handle_then_step → assertion callback registration
|
|
134
|
+
└── pytest_runtest_call → run_property_test() via Hypothesis
|
|
135
|
+
```
|
|
136
|
+
|
|
137
|
+
---
|
|
138
|
+
|
|
139
|
+
## Built-in Strategies
|
|
140
|
+
|
|
141
|
+
Use these directly in your feature files with `Given any <type> <var>`:
|
|
142
|
+
|
|
143
|
+
### Primitives
|
|
144
|
+
|
|
145
|
+
| Strategy Name | Description | Hypothesis Equivalent |
|
|
146
|
+
|---------------|-------------|-----------------------|
|
|
147
|
+
| `text` | Arbitrary Unicode strings | `st.text()` |
|
|
148
|
+
| `non-empty text` | Strings with length ≥ 1 | `st.text(min_size=1)` |
|
|
149
|
+
| `integer` | Arbitrary integers | `st.integers()` |
|
|
150
|
+
| `positive integer` | Integers ≥ 1 | `st.integers(min_value=1)` |
|
|
151
|
+
| `negative integer` | Integers ≤ -1 | `st.integers(max_value=-1)` |
|
|
152
|
+
| `natural` | Integers ≥ 0 | `st.integers(min_value=0)` |
|
|
153
|
+
| `float` | Floats (no NaN/Infinity) | `st.floats(allow_nan=False, allow_infinity=False)` |
|
|
154
|
+
| `boolean` | True or False | `st.booleans()` |
|
|
155
|
+
|
|
156
|
+
### Strings
|
|
157
|
+
|
|
158
|
+
| Strategy Name | Description | Hypothesis Equivalent |
|
|
159
|
+
|---------------|-------------|-----------------------|
|
|
160
|
+
| `ascii text` | ASCII-only strings | `st.text(alphabet=st.characters(codec="ascii"))` |
|
|
161
|
+
| `alphanumeric` | `[a-z0-9]+` strings | `st.from_regex(r"[a-z0-9]+")` |
|
|
162
|
+
| `hex string` | `[0-9a-f]+` strings | `st.from_regex(r"[0-9a-f]+")` |
|
|
163
|
+
|
|
164
|
+
### Identifiers
|
|
165
|
+
|
|
166
|
+
| Strategy Name | Description | Hypothesis Equivalent |
|
|
167
|
+
|---------------|-------------|-----------------------|
|
|
168
|
+
| `uuid` | UUID v4 strings | `st.uuids().map(str)` |
|
|
169
|
+
| `email` | Email-shaped strings | `st.emails()` |
|
|
170
|
+
| `url` | URL-shaped strings | `st.from_regex(...)` |
|
|
171
|
+
|
|
172
|
+
### Temporal
|
|
173
|
+
|
|
174
|
+
| Strategy Name | Description | Hypothesis Equivalent |
|
|
175
|
+
|---------------|-------------|-----------------------|
|
|
176
|
+
| `date` | Date objects | `st.dates()` |
|
|
177
|
+
|
|
178
|
+
### Structured
|
|
179
|
+
|
|
180
|
+
| Strategy Name | Description | Hypothesis Equivalent |
|
|
181
|
+
|---------------|-------------|-----------------------|
|
|
182
|
+
| `json value` | Recursive JSON values | `st.recursive(...)` |
|
|
183
|
+
| `json object` | `dict[str, str]` | `st.dictionaries(st.text(), st.text())` |
|
|
184
|
+
|
|
185
|
+
### Domain Defaults
|
|
186
|
+
|
|
187
|
+
| Strategy Name | Description | Hypothesis Equivalent |
|
|
188
|
+
|---------------|-------------|-----------------------|
|
|
189
|
+
| `password` | Text, 8-128 chars | `st.text(min_size=8, max_size=128)` |
|
|
190
|
+
| `username` | `[a-z0-9_]{3,32}` | `st.from_regex(...)` |
|
|
191
|
+
|
|
192
|
+
### Custom Strategies
|
|
193
|
+
|
|
194
|
+
Register domain-specific strategies in your test file:
|
|
195
|
+
|
|
196
|
+
```python
|
|
197
|
+
from pytest_bdd_property import register_strategy
|
|
198
|
+
from hypothesis import strategies as st
|
|
199
|
+
|
|
200
|
+
register_strategy("valid password", lambda: (
|
|
201
|
+
st.tuples(
|
|
202
|
+
st.text(alphabet="abcdefghijklmnopqrstuvwxyz", min_size=2, max_size=10),
|
|
203
|
+
st.text(alphabet="ABCDEFGHIJKLMNOPQRSTUVWXYZ", min_size=2, max_size=10),
|
|
204
|
+
st.text(alphabet="0123456789", min_size=2, max_size=5),
|
|
205
|
+
st.text(alphabet="!@#$%^&*", min_size=2, max_size=3),
|
|
206
|
+
).map(lambda parts: parts[0] + parts[1] + parts[2] + parts[3])
|
|
207
|
+
))
|
|
208
|
+
```
|
|
209
|
+
|
|
210
|
+
Then in Gherkin:
|
|
211
|
+
```gherkin
|
|
212
|
+
Given any valid password <P>
|
|
213
|
+
```
|
|
214
|
+
|
|
215
|
+
---
|
|
216
|
+
|
|
217
|
+
## Built-in Assumptions
|
|
218
|
+
|
|
219
|
+
Use these in `Given`/`And` steps to filter generated inputs:
|
|
220
|
+
|
|
221
|
+
### Equality
|
|
222
|
+
|
|
223
|
+
| Pattern | Meaning | Maps To |
|
|
224
|
+
|---------|---------|---------|
|
|
225
|
+
| `<A> is not equal to <B>` | A ≠ B | `assume(A != B)` |
|
|
226
|
+
| `<A> is equal to <B>` | A = B | `assume(A == B)` |
|
|
227
|
+
|
|
228
|
+
### Numeric Comparison
|
|
229
|
+
|
|
230
|
+
| Pattern | Meaning | Maps To |
|
|
231
|
+
|---------|---------|---------|
|
|
232
|
+
| `<A> is greater than <B>` | A > B | `assume(A > B)` |
|
|
233
|
+
| `<A> is less than <B>` | A < B | `assume(A < B)` |
|
|
234
|
+
| `<A> is greater than or equal to <B>` | A ≥ B | `assume(A >= B)` |
|
|
235
|
+
| `<A> is less than or equal to <B>` | A ≤ B | `assume(A <= B)` |
|
|
236
|
+
|
|
237
|
+
### Emptiness
|
|
238
|
+
|
|
239
|
+
| Pattern | Meaning | Maps To |
|
|
240
|
+
|---------|---------|---------|
|
|
241
|
+
| `<A> is not empty` | len(A) > 0 | `assume(len(A) > 0)` |
|
|
242
|
+
| `<A> is empty` | len(A) = 0 | `assume(len(A) == 0)` |
|
|
243
|
+
|
|
244
|
+
### Length
|
|
245
|
+
|
|
246
|
+
| Pattern | Meaning | Maps To |
|
|
247
|
+
|---------|---------|---------|
|
|
248
|
+
| `<A> has length greater than N` | len(A) > N | `assume(len(A) > N)` |
|
|
249
|
+
| `<A> has length less than N` | len(A) < N | `assume(len(A) < N)` |
|
|
250
|
+
|
|
251
|
+
### Containment
|
|
252
|
+
|
|
253
|
+
| Pattern | Meaning | Maps To |
|
|
254
|
+
|---------|---------|---------|
|
|
255
|
+
| `<A> contains <B>` | B in A | `assume(B in A)` |
|
|
256
|
+
| `<A> does not contain <B>` | B not in A | `assume(B not in A)` |
|
|
257
|
+
|
|
258
|
+
### Type Checks
|
|
259
|
+
|
|
260
|
+
| Pattern | Meaning | Maps To |
|
|
261
|
+
|---------|---------|---------|
|
|
262
|
+
| `<A> is a number` | isinstance(A, (int, float)) | `assume(isinstance(A, (int, float)))` |
|
|
263
|
+
| `<A> is a string` | isinstance(A, str) | `assume(isinstance(A, str))` |
|
|
264
|
+
|
|
265
|
+
### Custom Assumptions
|
|
266
|
+
|
|
267
|
+
```python
|
|
268
|
+
from pytest_bdd_property import register_assumption
|
|
269
|
+
|
|
270
|
+
register_assumption(
|
|
271
|
+
r"^<(\w+)> is a valid email$",
|
|
272
|
+
lambda var: lambda vals: "@" in str(vals[var])
|
|
273
|
+
)
|
|
274
|
+
```
|
|
275
|
+
|
|
276
|
+
---
|
|
277
|
+
|
|
278
|
+
## Configuration via Tags
|
|
279
|
+
|
|
280
|
+
```gherkin
|
|
281
|
+
@property-based @num-runs:500 @seed:42 @verbose
|
|
282
|
+
Scenario: Stress test hashing
|
|
283
|
+
...
|
|
284
|
+
```
|
|
285
|
+
|
|
286
|
+
| Tag | Effect |
|
|
287
|
+
|-----|--------|
|
|
288
|
+
| `@num-runs:<n>` | Override number of generated examples (default: 100) |
|
|
289
|
+
| `@seed:<n>` | Fix the random seed for reproducibility |
|
|
290
|
+
| `@verbose` | Enable verbose output |
|
|
291
|
+
|
|
292
|
+
---
|
|
293
|
+
|
|
294
|
+
## Common Patterns
|
|
295
|
+
|
|
296
|
+
### Round-trip (Serialization)
|
|
297
|
+
|
|
298
|
+
```gherkin
|
|
299
|
+
@property-based
|
|
300
|
+
Scenario: JSON round-trip preserves data
|
|
301
|
+
Given any json object <D>
|
|
302
|
+
When <D> is serialized to JSON producing <J>
|
|
303
|
+
And <J> is deserialized producing <D2>
|
|
304
|
+
Then <D> is equal to <D2>
|
|
305
|
+
```
|
|
306
|
+
|
|
307
|
+
### Idempotency
|
|
308
|
+
|
|
309
|
+
```gherkin
|
|
310
|
+
@property-based
|
|
311
|
+
Scenario: Normalizing email twice gives the same result
|
|
312
|
+
Given any email <E>
|
|
313
|
+
When <E> is normalized producing <N1>
|
|
314
|
+
And <N1> is normalized producing <N2>
|
|
315
|
+
Then <N1> is equal to <N2>
|
|
316
|
+
```
|
|
317
|
+
|
|
318
|
+
### No False Positives
|
|
319
|
+
|
|
320
|
+
```gherkin
|
|
321
|
+
@property-based
|
|
322
|
+
Scenario: Wrong password never verifies
|
|
323
|
+
Given any valid password <P>
|
|
324
|
+
And any valid password <Q>
|
|
325
|
+
And <P> is not equal to <Q>
|
|
326
|
+
When <P> is hashed producing <H>
|
|
327
|
+
Then <Q> does not verify against <H>
|
|
328
|
+
```
|
|
329
|
+
|
|
330
|
+
### No Information Leakage
|
|
331
|
+
|
|
332
|
+
```gherkin
|
|
333
|
+
@property-based
|
|
334
|
+
Scenario: Hash never contains plaintext
|
|
335
|
+
Given any valid password <P>
|
|
336
|
+
When <P> is hashed producing <H>
|
|
337
|
+
Then <H> does not contain <P>
|
|
338
|
+
```
|
|
339
|
+
|
|
340
|
+
---
|
|
341
|
+
|
|
342
|
+
## API Reference
|
|
343
|
+
|
|
344
|
+
### `register_strategy(name, factory)`
|
|
345
|
+
|
|
346
|
+
Register a named strategy for `Given any <name> <var>`.
|
|
347
|
+
|
|
348
|
+
- `name`: Case-insensitive strategy name
|
|
349
|
+
- `factory`: `() -> SearchStrategy` callable
|
|
350
|
+
|
|
351
|
+
### `property_when(pattern)`
|
|
352
|
+
|
|
353
|
+
Decorator to register a When step for property-based scenarios.
|
|
354
|
+
|
|
355
|
+
```python
|
|
356
|
+
@property_when(r"<(\w+)> is hashed producing <(\w+)>")
|
|
357
|
+
def hash_step(vals, results, pw_var, hash_var):
|
|
358
|
+
results[hash_var] = hash_password(vals[pw_var])
|
|
359
|
+
```
|
|
360
|
+
|
|
361
|
+
- `vals`: `dict[str, Any]` — generated values keyed by variable name
|
|
362
|
+
- `results`: `dict[str, Any]` — intermediate results from When steps
|
|
363
|
+
- Additional args: regex capture groups from the pattern
|
|
364
|
+
|
|
365
|
+
### `property_then(pattern)`
|
|
366
|
+
|
|
367
|
+
Decorator to register a Then step for property-based scenarios.
|
|
368
|
+
|
|
369
|
+
```python
|
|
370
|
+
@property_then(r"<(\w+)> verifies against <(\w+)>")
|
|
371
|
+
def verify_step(vals, results, pw_var, hash_var):
|
|
372
|
+
assert verify_password(vals[pw_var], results[hash_var])
|
|
373
|
+
```
|
|
374
|
+
|
|
375
|
+
Raise `AssertionError` to signal a property violation. Hypothesis shrinks to minimal counterexample.
|
|
376
|
+
|
|
377
|
+
### `register_assumption(pattern, builder)`
|
|
378
|
+
|
|
379
|
+
Register a custom assumption pattern.
|
|
380
|
+
|
|
381
|
+
```python
|
|
382
|
+
register_assumption(
|
|
383
|
+
r"^<(\w+)> is a valid email$",
|
|
384
|
+
lambda var: lambda vals: "@" in str(vals[var])
|
|
385
|
+
)
|
|
386
|
+
```
|
|
387
|
+
|
|
388
|
+
### `resolve_strategy(name)`
|
|
389
|
+
|
|
390
|
+
Resolve a strategy name to a Hypothesis `SearchStrategy`. Raises `ValueError` if not found.
|
|
391
|
+
|
|
392
|
+
### `list_strategies()`
|
|
393
|
+
|
|
394
|
+
Return all registered strategy names, sorted.
|
|
395
|
+
|
|
396
|
+
---
|
|
397
|
+
|
|
398
|
+
## Coexistence with Behavioral Scenarios
|
|
399
|
+
|
|
400
|
+
Property-based and behavioral scenarios coexist naturally:
|
|
401
|
+
|
|
402
|
+
```gherkin
|
|
403
|
+
Feature: User Authentication
|
|
404
|
+
|
|
405
|
+
# Behavioral (concrete examples)
|
|
406
|
+
Scenario: User can log in with correct password
|
|
407
|
+
Given a user with password "hunter2"
|
|
408
|
+
When they log in with "hunter2"
|
|
409
|
+
Then they are authenticated
|
|
410
|
+
|
|
411
|
+
# Property (universal invariant)
|
|
412
|
+
@property-based
|
|
413
|
+
Scenario: Wrong password never authenticates
|
|
414
|
+
Given any text <P>
|
|
415
|
+
And any text <Q>
|
|
416
|
+
And <P> is not equal to <Q>
|
|
417
|
+
Given a user with password <P>
|
|
418
|
+
When they log in with <Q>
|
|
419
|
+
Then they are rejected
|
|
420
|
+
```
|
|
421
|
+
|
|
422
|
+
The `@property-based` tag tells the plugin to intercept execution. Scenarios without the tag run normally through pytest-bdd.
|