pytest-mark-ac 1.0.0__py3-none-any.whl

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.
@@ -0,0 +1,60 @@
1
+ """
2
+ Plugin callbacks to register and handle the markers.
3
+ """
4
+
5
+ from collections.abc import Sequence
6
+
7
+ import pytest
8
+
9
+ _MARK = "ac"
10
+
11
+
12
+ def pytest_configure(config: pytest.Config) -> None:
13
+ config.addinivalue_line(
14
+ "markers",
15
+ f"{_MARK}(story_id: int, criteria_ids: Sequence[int], "
16
+ 'ref_prefix: str = "ac", ref_suffix: str = ""): '
17
+ "appends suffixes in the form __<ref_prefix><story>_<criterion><ref_suffix> "
18
+ "to the test nodeid",
19
+ )
20
+
21
+
22
+ def _validate_and_expand(mark: pytest.Mark) -> list[str]:
23
+ if len(mark.args) != 2:
24
+ raise pytest.UsageError(
25
+ f"@pytest.mark.{_MARK} requires 2 positional arguments: "
26
+ "(story_id:int, criteria_ids:Sequence[int])"
27
+ )
28
+ story_id, criteria_ids = mark.args
29
+ if not isinstance(story_id, int):
30
+ raise pytest.UsageError(f"{_MARK}: story_id must be int, got {type(story_id).__name__}")
31
+ if isinstance(criteria_ids, (str, bytes)) or not isinstance(criteria_ids, Sequence):
32
+ raise pytest.UsageError(f"{_MARK}: criteria_ids must be an int sequence")
33
+ ref_prefix: str = str(mark.kwargs.get("ref_prefix", "ac"))
34
+ ref_suffix: str = str(mark.kwargs.get("ref_suffix", ""))
35
+ out = []
36
+ for idx, cid in enumerate(criteria_ids):
37
+ if not isinstance(cid, int):
38
+ raise pytest.UsageError(
39
+ f"{_MARK}: each criterion must be int, got {type(cid).__name__} at position {idx}"
40
+ )
41
+ out.append(f"__{ref_prefix}{story_id}_{cid}{ref_suffix}")
42
+ return out
43
+
44
+
45
+ def pytest_collection_modifyitems(
46
+ session: pytest.Session, config: pytest.Config, items: list[pytest.Item]
47
+ ) -> None:
48
+ for item in items:
49
+ marks = list(item.iter_markers(_MARK))
50
+ if not marks:
51
+ continue
52
+ suffixes: list[str] = []
53
+ for m in marks:
54
+ suffixes.extend(_validate_and_expand(m))
55
+
56
+ seen = set()
57
+ suffixes = [s for s in suffixes if not (s in seen or seen.add(s))]
58
+ suffixes = [s for s in suffixes if s not in item.nodeid]
59
+ if suffixes:
60
+ item._nodeid = item.nodeid + "".join(suffixes)
@@ -0,0 +1,206 @@
1
+ Metadata-Version: 2.4
2
+ Name: pytest-mark-ac
3
+ Version: 1.0.0
4
+ Summary: Provides a marker to reference acceptance criteria from PyTest tests through annotations
5
+ Project-URL: Homepage, https://gitlab.com/Kencho1/pytest-mark-ac
6
+ Project-URL: Documentation, https://gitlab.com/Kencho1/pytest-mark-ac/-/blob/main/README.md
7
+ Project-URL: Repository, https://gitlab.com/Kencho1/pytest-mark-ac.git
8
+ Project-URL: Issues, https://gitlab.com/Kencho1/pytest-mark-ac/-/issues
9
+ Author: Jesús Alonso Abad
10
+ License-Expression: MIT
11
+ License-File: LICENSE
12
+ Classifier: Development Status :: 5 - Production/Stable
13
+ Classifier: Environment :: Plugins
14
+ Classifier: Framework :: Pytest
15
+ Classifier: Intended Audience :: Developers
16
+ Classifier: License :: OSI Approved :: MIT License
17
+ Classifier: Natural Language :: English
18
+ Classifier: Operating System :: OS Independent
19
+ Classifier: Programming Language :: Python :: 3
20
+ Classifier: Programming Language :: Python :: 3.11
21
+ Classifier: Programming Language :: Python :: 3.12
22
+ Classifier: Programming Language :: Python :: 3.13
23
+ Classifier: Programming Language :: Python :: 3.14
24
+ Classifier: Topic :: Software Development :: Libraries :: Python Modules
25
+ Classifier: Topic :: Software Development :: Testing
26
+ Requires-Python: >=3.11
27
+ Requires-Dist: pytest>=9.0.0
28
+ Provides-Extra: dev
29
+ Requires-Dist: ruff>=0.8.0; extra == 'dev'
30
+ Description-Content-Type: text/markdown
31
+
32
+ # pytest-mark-ac
33
+
34
+ A pytest plugin that provides a marker to reference acceptance criteria from tests through annotations. This plugin modifies test node IDs to include acceptance criteria references, enabling traceability between tests and user stories/acceptance criteria.
35
+
36
+ ## Installation
37
+
38
+ ### From Source
39
+
40
+ ```bash
41
+ # Clone the repository
42
+ git clone <repository-url>
43
+ cd pytest-mark-test
44
+
45
+ # Install in development mode
46
+ pip install -e .
47
+
48
+ # Or build and install
49
+ pip install .
50
+ ```
51
+
52
+ ### From PyPI (when published)
53
+
54
+ ```bash
55
+ pip install pytest-mark-ac
56
+ ```
57
+
58
+ ## Usage
59
+
60
+ The plugin automatically registers when installed. Use the `@pytest.mark.ac` decorator to annotate tests with acceptance criteria references.
61
+
62
+ ### Basic Syntax
63
+
64
+ ```python
65
+ @pytest.mark.ac(story_id, criteria_ids, ref_prefix="ac", ref_suffix="")
66
+ ```
67
+
68
+ **Parameters:**
69
+ - `story_id` (int): The user story ID
70
+ - `criteria_ids` (Sequence[int]): List of acceptance criterion IDs
71
+ - `ref_prefix` (str, optional): Prefix for the reference (default: "ac")
72
+ - `ref_suffix` (str, optional): Suffix for the reference (default: "")
73
+
74
+ **Effect:** Appends `__<ref_prefix><story_id>_<criterion_id><ref_suffix>` to the test node ID
75
+
76
+ ## Use Cases and Examples
77
+
78
+ ### Use Case 1: Simple Function Test
79
+
80
+ Link a single test to multiple acceptance criteria:
81
+
82
+ ```python
83
+ import pytest
84
+
85
+ @pytest.mark.ac(1, [1, 2, 3])
86
+ def test_user_login():
87
+ # Test implementation
88
+ assert True
89
+ ```
90
+
91
+ **Result:** Test node ID becomes `test_file.py::test_user_login__ac1_1__ac1_2__ac1_3`
92
+
93
+ Note `range` can be used for sequential ACs as well:
94
+
95
+ ```python
96
+ import pytest
97
+
98
+ @pytest.mark.ac(1, *range(1, 4))
99
+ def test_user_login():
100
+ # Test implementation
101
+ assert True
102
+ ```
103
+
104
+ also becomes `test_file.py::test_user_login__ac1_1__ac1_2__ac1_3`
105
+
106
+ ### Use Case 2: Parametrized Tests with Different Criteria
107
+
108
+ Apply different acceptance criteria to individual parametrized test cases:
109
+
110
+ ```python
111
+ from contextlib import nullcontext
112
+
113
+ @pytest.mark.ac(1, [1]) # Applies to all test cases
114
+ @pytest.mark.parametrize(
115
+ 'a,b,expected',
116
+ [
117
+ pytest.param(4, 2, nullcontext(2), marks=pytest.mark.ac(2, [1])),
118
+ pytest.param(5, 1, nullcontext(5), marks=pytest.mark.ac(2, [1, 2])),
119
+ pytest.param(3, 2, nullcontext(1.5), marks=pytest.mark.ac(2, [3])),
120
+ pytest.param(3, 0, pytest.raises(ZeroDivisionError), marks=pytest.mark.ac(2, [4])),
121
+ ],
122
+ )
123
+ def test_division(a, b, expected):
124
+ with expected as exp_result:
125
+ assert (a / b) == exp_result
126
+ ```
127
+
128
+ **Result:**
129
+ - `test_division[4-2-expected0]__ac1_1__ac2_1`
130
+ - `test_division[5-1-expected1]__ac1_1__ac2_1__ac2_2`
131
+ - `test_division[3-2-expected2]__ac1_1__ac2_3`
132
+ - `test_division[3-0-expected3]__ac1_1__ac2_4`
133
+
134
+ ### Use Case 3: Custom Reference Format
135
+
136
+ Customize the reference format for your organization's conventions:
137
+
138
+ ```python
139
+ @pytest.mark.ac(123, [5, 6], ref_prefix="AC", ref_suffix="_v1")
140
+ def test_feature():
141
+ assert True
142
+ ```
143
+
144
+ **Result:** Test node ID becomes `test_file.py::test_feature__AC123_5_v1__AC123_6_v1`
145
+
146
+ ### Use Case 4: Testing Edge Cases with Acceptance Criteria
147
+
148
+ ```python
149
+ @pytest.mark.parametrize(
150
+ 'value,expected',
151
+ [
152
+ pytest.param(0, True, marks=pytest.mark.ac(5, [1])), # Zero case
153
+ pytest.param(-1, True, marks=pytest.mark.ac(5, [2])), # Negative case
154
+ pytest.param(100, False, marks=pytest.mark.ac(5, [3])), # Boundary case
155
+ ],
156
+ )
157
+ def test_validation(value, expected):
158
+ assert validate(value) == expected
159
+ ```
160
+
161
+ ### Use Case 5: Multiple Stories on One Test
162
+
163
+ A test can verify acceptance criteria from multiple user stories:
164
+
165
+ ```python
166
+ @pytest.mark.ac(10, [1, 2]) # Story 10, criteria 1-2
167
+ @pytest.mark.ac(11, [3]) # Story 11, criterion 3
168
+ def test_integrated_feature():
169
+ # Tests functionality spanning multiple stories
170
+ assert True
171
+ ```
172
+
173
+ **Result:** `test_file.py::test_integrated_feature__ac10_1__ac10_2__ac11_3`
174
+
175
+ ## Running Tests with Acceptance Criteria
176
+
177
+ ```bash
178
+ # Run all tests (shows modified node IDs)
179
+ pytest -v
180
+
181
+ # Run tests for specific acceptance criterion
182
+ pytest -v -k "ac1_1"
183
+
184
+ # Run tests for specific story
185
+ pytest -v -k "ac5"
186
+
187
+ # Show test collection without running
188
+ pytest --collect-only
189
+ ```
190
+
191
+ ## Benefits
192
+
193
+ - **Traceability**: Direct link between tests and acceptance criteria
194
+ - **Test Reports**: Node IDs in reports show which criteria are covered
195
+ - **Selective Execution**: Run tests by acceptance criteria using `-k` flag
196
+ - **Documentation**: Tests serve as executable documentation of acceptance criteria
197
+ - **Duplicate Prevention**: Automatically removes duplicate criteria references
198
+
199
+ ## Requirements
200
+
201
+ - Python >= 3.11
202
+ - pytest >= 9.0.0
203
+
204
+ ## License
205
+
206
+ MIT
@@ -0,0 +1,6 @@
1
+ pytest_mark_ac/__init__.py,sha256=LoJZA4Tn7dzsqMWIqqXwiPVhjx7ACZ-gdypbiBKnPnY,2118
2
+ pytest_mark_ac-1.0.0.dist-info/METADATA,sha256=ABsiOECOl6uOMsaDNRtjzkIPte9Ezpem7U-8MH-BZic,5977
3
+ pytest_mark_ac-1.0.0.dist-info/WHEEL,sha256=qtCwoSJWgHk21S1Kb4ihdzI2rlJ1ZKaIurTj_ngOhyQ,87
4
+ pytest_mark_ac-1.0.0.dist-info/entry_points.txt,sha256=tFmAUv6rDus0WNRSfOqyQpgLNnXp1KIFD-Bp2e1SDCk,43
5
+ pytest_mark_ac-1.0.0.dist-info/licenses/LICENSE,sha256=bvnxQii3m3E-t9iSPXaWxmygmXsnPTePBvvalvvwAZA,1075
6
+ pytest_mark_ac-1.0.0.dist-info/RECORD,,
@@ -0,0 +1,4 @@
1
+ Wheel-Version: 1.0
2
+ Generator: hatchling 1.27.0
3
+ Root-Is-Purelib: true
4
+ Tag: py3-none-any
@@ -0,0 +1,2 @@
1
+ [pytest11]
2
+ pytest_mark_ac = pytest_mark_ac
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2025 Jesús Alonso Abad
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.