pytest-pyspec 0.5.3__py3-none-any.whl → 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.

Potentially problematic release.


This version of pytest-pyspec might be problematic. Click here for more details.

@@ -0,0 +1,202 @@
1
+ Metadata-Version: 2.4
2
+ Name: pytest-pyspec
3
+ Version: 1.0.0
4
+ Summary: A plugin that transforms the pytest output into a result similar to the RSpec. It enables the use of docstrings to display results and also enables the use of the prefixes "describe", "with" and "it".
5
+ Project-URL: Repository, https://github.com/felipecrp/pytest-pyspec
6
+ Project-URL: Issues, https://github.com/felipecrp/pytest-pyspec/issues
7
+ Author-email: Felipe Curty <felipecrp@gmail.com>
8
+ License-Expression: MIT
9
+ License-File: LICENSE
10
+ Classifier: Development Status :: 5 - Production/Stable
11
+ Classifier: Intended Audience :: Developers
12
+ Classifier: Operating System :: MacOS
13
+ Classifier: Operating System :: Microsoft :: Windows
14
+ Classifier: Operating System :: POSIX
15
+ Classifier: Operating System :: Unix
16
+ Classifier: Programming Language :: Python :: 3 :: Only
17
+ Classifier: Programming Language :: Python :: 3.10
18
+ Classifier: Programming Language :: Python :: 3.11
19
+ Classifier: Programming Language :: Python :: 3.12
20
+ Classifier: Programming Language :: Python :: 3.13
21
+ Classifier: Programming Language :: Python :: 3.14
22
+ Classifier: Topic :: Software Development :: Libraries
23
+ Classifier: Topic :: Software Development :: Testing
24
+ Classifier: Topic :: Utilities
25
+ Requires-Python: <4,>=3.10
26
+ Requires-Dist: pytest<10,>=9
27
+ Description-Content-Type: text/markdown
28
+
29
+ [![](https://github.com/felipecrp/pytest-pyspec/actions/workflows/pytest.yml/badge.svg)](https://github.com/felipecrp/pytest-pyspec/actions/workflows/pytest.yml)
30
+
31
+ # pytest-pyspec
32
+
33
+ The **pytest-pyspec** plugin transforms pytest output into a beautiful, readable format similar to RSpec. It provides semantic meaning to your tests by organizing them into descriptive hierarchies.
34
+
35
+ ## Features
36
+
37
+ - **Semantic Output**: Transform pytest's default output into readable, hierarchical descriptions
38
+ - **Multiple Prefixes**: Support for `describe/test` (objects), `with/without/when` (contexts), and `it/test` (tests)
39
+ - **Docstring Support**: Override test descriptions using docstrings
40
+ - **Consolidated Output**: Smart grouping that avoids repeating parent headers
41
+ - **Natural Language**: Automatic lowercase formatting of common words (the, is, are, etc.)
42
+
43
+ ## Quick Start
44
+
45
+ ### Installation
46
+
47
+ ```bash
48
+ pip install pytest pytest-pyspec
49
+ ```
50
+
51
+ ### Running
52
+
53
+ ```bash
54
+ pytest --pyspec
55
+ ```
56
+
57
+ ## Examples
58
+
59
+ ### Car Scenario
60
+
61
+ A minimal car example with properties and behaviors:
62
+
63
+ ```python
64
+ class DescribeCar:
65
+ def test_has_engine(self):
66
+ assert True
67
+
68
+ class WithFullTank:
69
+ def test_drive_long_distance(self):
70
+ assert True
71
+
72
+ class WithoutFuel:
73
+ def test_cannot_start_engine(self):
74
+ assert True
75
+
76
+ class WhenTheEngineIsRunning:
77
+ def test_consumes_fuel(self):
78
+ assert True
79
+ ```
80
+
81
+ With **pytest-pyspec**, this produces:
82
+
83
+ ```
84
+ a Car
85
+ ✓ has engine
86
+
87
+ with Full Tank
88
+ ✓ drive long distance
89
+
90
+ without Fuel
91
+ ✓ cannot start engine
92
+
93
+ when the Engine is Running
94
+ ✓ consumes fuel
95
+ ```
96
+
97
+ ### Available Prefixes
98
+
99
+ **pytest-pyspec** supports three types of prefixes to create semantic test hierarchies:
100
+
101
+ #### 1. Object Classes (use `describe` or `test`)
102
+
103
+ Define what you're testing:
104
+
105
+ ```python
106
+ class DescribeCar: # or class TestCar:
107
+ def test_has_four_wheels(self):
108
+ assert True
109
+ ```
110
+
111
+ Output:
112
+ ```
113
+ a Car
114
+ ✓ has four wheels
115
+ ```
116
+
117
+ #### 2. Context Classes (use `with`, `without`, or `when`)
118
+
119
+ Define the context or state:
120
+
121
+ ```python
122
+ class DescribeCar:
123
+ class WithFullTank:
124
+ def test_can_drive_long_distances(self):
125
+ assert True
126
+
127
+ class WithoutFuel:
128
+ def test_cannot_start_engine(self):
129
+ assert True
130
+
131
+ class WhenTheEngineIsRunning:
132
+ def test_consumes_fuel(self):
133
+ assert True
134
+ ```
135
+
136
+ Output:
137
+ ```
138
+ a Car
139
+ with Full Tank
140
+ ✓ can drive long distances
141
+
142
+ without Fuel
143
+ ✓ cannot start engine
144
+
145
+ when the Engine is Running
146
+ ✓ consumes fuel
147
+ ```
148
+
149
+ #### 3. Test Functions (use `it_` or `test_`)
150
+
151
+ Define the expected behavior:
152
+
153
+ ```python
154
+ class DescribeCar:
155
+ def it_has_four_wheels(self):
156
+ assert True
157
+
158
+ def test_has_engine(self):
159
+ assert True
160
+ ```
161
+
162
+ Output:
163
+ ```
164
+ a Car
165
+ ✓ has four wheels
166
+ ✓ has engine
167
+ ```
168
+
169
+ ### Using Docstrings
170
+
171
+ Override automatic naming with custom descriptions:
172
+
173
+ ```python
174
+ class TestCar:
175
+ """sports car"""
176
+
177
+ def test_top_speed(self):
178
+ """reaches 200 mph"""
179
+ assert True
180
+
181
+ class WhenTheNitroIsActivated:
182
+ """when nitro boost is activated"""
183
+
184
+ def test_acceleration(self):
185
+ """accelerates rapidly"""
186
+ assert True
187
+ ```
188
+
189
+ Output:
190
+ ```
191
+ a sports car
192
+ ✓ reaches 200 mph
193
+
194
+ when nitro boost is activated
195
+ ✓ accelerates rapidly
196
+ ```
197
+
198
+ ## Configuration
199
+
200
+ The plugin is automatically enabled when you use the `--pyspec` flag. No additional configuration is required.
201
+
202
+ For more information, see the [documentation](https://github.com/felipecrp/pytest-pyspec/blob/main/doc/README.md).
@@ -0,0 +1,8 @@
1
+ pytest_pyspec/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
2
+ pytest_pyspec/plugin.py,sha256=HS3iA2Xiz9LzdiB_k-2K0YpLtz5vF72Q7yS6l7LmhRk,6869
3
+ pytest_pyspec/tree.py,sha256=1Qg0oh7T-__vQEC9w828GI79lLAPrfzZx8wcPDY8JsE,14812
4
+ pytest_pyspec-1.0.0.dist-info/METADATA,sha256=O8ZEekcDGnnxQm0rOjxmET5GMmgWoK_tQY6_uyB1uz4,4916
5
+ pytest_pyspec-1.0.0.dist-info/WHEEL,sha256=qtCwoSJWgHk21S1Kb4ihdzI2rlJ1ZKaIurTj_ngOhyQ,87
6
+ pytest_pyspec-1.0.0.dist-info/entry_points.txt,sha256=6YScWbxnkpduYs2Ef3gKOe6rV8Cjbj-OxCGMDNdV4Kc,48
7
+ pytest_pyspec-1.0.0.dist-info/licenses/LICENSE,sha256=PXKqh3rDWRsfhWsjBQ27qqYzE5E05n-eFRH95LsKRT4,1094
8
+ pytest_pyspec-1.0.0.dist-info/RECORD,,
@@ -1,4 +1,4 @@
1
1
  Wheel-Version: 1.0
2
- Generator: poetry-core 1.5.1
2
+ Generator: hatchling 1.27.0
3
3
  Root-Is-Purelib: true
4
4
  Tag: py3-none-any
@@ -0,0 +1,2 @@
1
+ [pytest11]
2
+ pytest_pyspec = pytest_pyspec.plugin
@@ -0,0 +1,9 @@
1
+ The MIT License (MIT)
2
+
3
+ Copyright 2024 Felipe Curty and others
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the “Software”), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions:
6
+
7
+ The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software.
8
+
9
+ THE SOFTWARE IS PROVIDED “AS IS”, WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
pytest_pyspec/item.py DELETED
@@ -1,141 +0,0 @@
1
- from types import ModuleType
2
- from typing import Dict, List
3
- import pytest
4
-
5
- class Item:
6
- def __init__(self, item: pytest.Item) -> None:
7
- self._item = item
8
-
9
- @property
10
- def description(self):
11
- description = self._item.obj.__doc__
12
-
13
- if not description:
14
- description = self._item.name
15
- description = self._parse_description(description)
16
-
17
- description = description.splitlines()[0]
18
- description = description.capitalize()
19
- return description
20
-
21
- def _parse_description(self, description: str):
22
- split = False
23
- if description.lower().startswith("it_"):
24
- description = description.replace('it_', '')
25
- split = True
26
-
27
- if description.lower().startswith("test_it_"):
28
- description = description.replace('test_it_', '')
29
- split = True
30
-
31
- if description.lower().startswith("test_describe_"):
32
- description = description.replace('test_describe_', '')
33
- split = True
34
-
35
- if description.lower().startswith("describe_"):
36
- description = description.replace('describe_', '')
37
- split = True
38
-
39
- if split:
40
- description = description.replace('_', ' ')
41
-
42
- return description
43
-
44
- def __repr__(self):
45
- return self._item.__repr__()
46
-
47
-
48
- class Test(Item):
49
- def __init__(self, item: pytest.Item) -> None:
50
- super().__init__(item)
51
- self.container: Container = None
52
- self.outcome: str = None
53
-
54
- @property
55
- def level(self) -> int:
56
- if not self.container:
57
- return 0
58
- return self.container.level +1
59
-
60
-
61
- class Container(Item):
62
- def __init__(self, item: pytest.Item):
63
- super().__init__(item)
64
- self.tests: List[Test] = list()
65
- self.containers: List[Container] = list()
66
- self.parent = None
67
-
68
- def add(self, test: Test):
69
- self.tests.append(test)
70
- test.container = self
71
-
72
- def add_container(self, container: 'Container'):
73
- self.containers.append(container)
74
- container.parent = self
75
-
76
- def flat_list(self):
77
- containers = []
78
- container = self
79
- while container:
80
- containers.insert(0, container)
81
- container = container.parent
82
- return containers
83
-
84
- @property
85
- def level(self) -> int:
86
- level = 0
87
-
88
- item = self._item
89
- while item.parent and not isinstance(item.parent.obj, ModuleType):
90
- level += 1
91
- item = item.parent
92
-
93
- return level
94
-
95
-
96
- class ItemFactory:
97
- def __init__(self) -> None:
98
- self.container_factory = ContainerFactory()
99
-
100
- def create(self, item: pytest.Item) -> Test:
101
- test_item = Test(item)
102
-
103
- container_item = item.parent
104
- container = self.container_factory.create(container_item)
105
- if container:
106
- container.add(test_item)
107
-
108
- return test_item
109
-
110
-
111
- class ContainerFactory:
112
- def __init__(self) -> None:
113
- self.containers: Dict[str, Container] = dict()
114
-
115
- def create(self, item) -> Container:
116
- containers = self._create_containers(item)
117
- if not containers:
118
- return None
119
-
120
- return containers[-1]
121
-
122
- def _create_unique_container(self, item):
123
- if item not in self.containers:
124
- container = Container(item)
125
- self.containers[item] = container
126
-
127
- container = self.containers.get(item)
128
- return container
129
-
130
- def _create_containers(self, item):
131
- containers = []
132
- child_container = None
133
- while item and not isinstance(item.obj, ModuleType):
134
- container = self._create_unique_container(item)
135
- if child_container:
136
- container.add_container(child_container)
137
-
138
- containers.insert(0, container)
139
- child_container = container
140
- item = item.parent
141
- return containers
pytest_pyspec/output.py DELETED
@@ -1,30 +0,0 @@
1
- import pytest
2
- from .item import Container, Test
3
-
4
-
5
- def print_container(container: Container):
6
- output = "\n"
7
- if container:
8
- for container in container.flat_list():
9
- output += print_parent_container(container)
10
- container = container.parent
11
- return output
12
-
13
- def print_parent_container(container: Container):
14
- ident = " " * container.level
15
- output = f"\n{ident}{container.description}"
16
- return output
17
-
18
- def print_test(test: Test):
19
- ident = " " * test.level
20
- status = print_test_status(test)
21
- output = f"\n{ident}{status} {test.description}"
22
- return output
23
-
24
- def print_test_status(test: Test):
25
- if test.outcome == 'passed':
26
- return '✓'
27
- elif test.outcome == 'failed':
28
- return '✗'
29
- else:
30
- return '»'