pUnit 1.2.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.
Files changed (33) hide show
  1. punit-1.2.0/.scripts/punit +7 -0
  2. punit-1.2.0/LICENSE +21 -0
  3. punit-1.2.0/PKG-INFO +207 -0
  4. punit-1.2.0/README.md +161 -0
  5. punit-1.2.0/pyproject.toml +48 -0
  6. punit-1.2.0/setup.cfg +4 -0
  7. punit-1.2.0/src/pUnit.egg-info/PKG-INFO +207 -0
  8. punit-1.2.0/src/pUnit.egg-info/SOURCES.txt +31 -0
  9. punit-1.2.0/src/pUnit.egg-info/dependency_links.txt +1 -0
  10. punit-1.2.0/src/pUnit.egg-info/requires.txt +5 -0
  11. punit-1.2.0/src/pUnit.egg-info/top_level.txt +1 -0
  12. punit-1.2.0/src/punit/TestResult.py +186 -0
  13. punit-1.2.0/src/punit/__init__.py +21 -0
  14. punit-1.2.0/src/punit/__main__.py +59 -0
  15. punit-1.2.0/src/punit/assertions/__init__.py +11 -0
  16. punit-1.2.0/src/punit/assertions/collections.py +76 -0
  17. punit-1.2.0/src/punit/assertions/exceptions.py +37 -0
  18. punit-1.2.0/src/punit/assertions/strings.py +55 -0
  19. punit-1.2.0/src/punit/cli.py +294 -0
  20. punit-1.2.0/src/punit/discovery/TestModuleDiscovery.py +113 -0
  21. punit-1.2.0/src/punit/discovery/__init__.py +9 -0
  22. punit-1.2.0/src/punit/facts/Fact.py +82 -0
  23. punit-1.2.0/src/punit/facts/FactManager.py +94 -0
  24. punit-1.2.0/src/punit/facts/__init__.py +11 -0
  25. punit-1.2.0/src/punit/reports/HtmlReportGenerator.py +93 -0
  26. punit-1.2.0/src/punit/reports/JUnitReportGenerator.py +196 -0
  27. punit-1.2.0/src/punit/reports/__init__.py +5 -0
  28. punit-1.2.0/src/punit/runner.py +118 -0
  29. punit-1.2.0/src/punit/theories/Theory.py +100 -0
  30. punit-1.2.0/src/punit/theories/TheoryManager.py +110 -0
  31. punit-1.2.0/src/punit/theories/__init__.py +11 -0
  32. punit-1.2.0/src/punit/traits/Trait.py +34 -0
  33. punit-1.2.0/src/punit/traits/__init__.py +9 -0
@@ -0,0 +1,7 @@
1
+ #!/usr/bin/env python
2
+ # SPDX-FileCopyrightText: © Shaun Wilson
3
+ # SPDX-License-Identifier: MIT
4
+ ##
5
+ from subprocess import run
6
+ from sys import executable, argv
7
+ run([ executable, "-m", "punit" ] + argv[1:], check=True)
punit-1.2.0/LICENSE ADDED
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) Shaun Wilson
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.
punit-1.2.0/PKG-INFO ADDED
@@ -0,0 +1,207 @@
1
+ Metadata-Version: 2.4
2
+ Name: pUnit
3
+ Version: 1.2.0
4
+ Summary: A modernized unit-test framework for Python.
5
+ Author-email: Shaun Wilson <mrshaunwilson@msn.com>
6
+ License: MIT License
7
+
8
+ Copyright (c) Shaun Wilson
9
+
10
+ Permission is hereby granted, free of charge, to any person obtaining a copy
11
+ of this software and associated documentation files (the "Software"), to deal
12
+ in the Software without restriction, including without limitation the rights
13
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
14
+ copies of the Software, and to permit persons to whom the Software is
15
+ furnished to do so, subject to the following conditions:
16
+
17
+ The above copyright notice and this permission notice shall be included in all
18
+ copies or substantial portions of the Software.
19
+
20
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
21
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
22
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
23
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
24
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
25
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
26
+ SOFTWARE.
27
+
28
+ Project-URL: Documentation, https://punit.readthedocs.io/
29
+ Project-URL: Homepage, https://github.com/wilson0x4d/punit
30
+ Project-URL: Repository, https://github.com/wilson0x4d/punit.git
31
+ Keywords: test,unittest,unit-test,xUnit,nUnit,pytest
32
+ Classifier: Operating System :: OS Independent
33
+ Classifier: Programming Language :: Python :: 3.12
34
+ Classifier: Programming Language :: Python :: 3.13
35
+ Classifier: Programming Language :: Python :: 3.14
36
+ Classifier: Topic :: Software Development :: Libraries :: Python Modules
37
+ Classifier: Intended Audience :: Developers
38
+ Requires-Python: >=3.12
39
+ Description-Content-Type: text/markdown
40
+ License-File: LICENSE
41
+ Provides-Extra: dev
42
+ Requires-Dist: build; extra == "dev"
43
+ Requires-Dist: setuptools; extra == "dev"
44
+ Requires-Dist: twine; extra == "dev"
45
+ Dynamic: license-file
46
+
47
+ `pUnit` is a modernized unit-testing framework for Python, inspired by xUnit.
48
+
49
+ This README is only a high-level introduction to **pUnit**. For more detailed documentation, please view the official docs at [https://pUnit.readthedocs.io](https://pUnit.readthedocs.io).
50
+
51
+ ## Command-Line Usage
52
+
53
+ Running pUnit with no arguments will perform test auto-discovery and execution:
54
+
55
+ ```bash
56
+ python3 -m punit
57
+ ```
58
+
59
+ By default it will look for tests in the `tests/` directory. You can override this behavior by providing a `--test-package` argument:
60
+
61
+ ```bash
62
+ python3 -m punit --test-package elsewhere
63
+ ```
64
+
65
+ In the above example, test discovery will instead occur in an `elsewhere/` directory.
66
+
67
+ ### Report Generation
68
+
69
+ `pUnit` can generate "Test Results" reports. Currently supporting HTML and jUnit formats:
70
+
71
+ ```bash
72
+ python3 -m punit --report html
73
+ ```
74
+
75
+ Reports can also be output to a file where they can be lifted as part of a CI/CD pipeline and stored as an artifact or used for other purposes:
76
+
77
+ ```bash
78
+ python3 -m punit --report junit --output results.xml
79
+ ```
80
+
81
+ ### Syntax Help
82
+
83
+ There are more options available, passing a `--help` argument will print help text:
84
+
85
+ ```bash
86
+ python3 -m punit --help
87
+ ```
88
+ Outputs:
89
+ ```plaintext
90
+ Usage: python3 -m punit [-h|--help]
91
+ [-q|--quiet] [-v|--verbose]
92
+ [-z|--failfast]
93
+ [-p|--test-package NAME]
94
+ [-i|--include PATTERN]
95
+ [-e|--exclude PATTERN]
96
+ [-f|--filter PATTERN]
97
+ [-t|--trait [!]NAME[=VALUE]]
98
+ [-w|--workdir DIRECTORY]
99
+ [-n|--no-default-patterns]
100
+ [-r|--report {junit|json}]
101
+ [-o|--output FILENAME]
102
+
103
+ Options:
104
+ -h, --help Show this help text and exit
105
+ -q, --quiet Quiet output
106
+ -v, --verbose Verbose output
107
+ -z, --failfast Stop on first failure or error
108
+ -p, --test-package NAME
109
+ Use NAME as the test package, all tests should
110
+ be locatable as modules in the named package.
111
+ Default: 'tests'
112
+ -i, --include PATTERN
113
+ Include any tests matching PATTERN
114
+ Default: '*.py'
115
+ -e, --exclude PATTERN
116
+ Exclude any tests matching PATTERN, overriding --include
117
+ Default: '__*__' (dunder files), '/.*/' (dot-directories)
118
+ -f, --filter PATTERN
119
+ Only execute tests matching PATTERN
120
+ Default: '*'
121
+ -t, --trait [!]NAME[=VALUE]
122
+ Execute tests with the specified trait, negated by prefixing with '!'.
123
+ If VALUE is specified, matches tests with the trait having specified value.
124
+ If VALUE is not specified, matches any test with the trait having any value.
125
+ Default: No filtering based on traits.
126
+ -w, --working-directory DIRECTORY
127
+ Working directory (defaults to start directory)
128
+ -n, --no-default-patterns
129
+ Do not apply any default include/exclude patterns.
130
+ -r, --report {html|junit}
131
+ Generate a report to stdout using either an "html"
132
+ or "junit" format. When generating a report to stdout
133
+ all other output is suppressed, unless `--output`
134
+ is also specified.
135
+ -o, --output FILENAME
136
+ If `--report` is used, instead of writing to stdout
137
+ write to FILENAME. In this case `--report` does not
138
+ suppress any program output.
139
+ ```
140
+
141
+ ## Writing Tests
142
+
143
+ You can write tests as functions, class methods, instance methods, or static methods with all forms offering identical functionality. You can also utilize async/await syntax without any additional overhead.
144
+
145
+ **pUnit** is based upon the fundamental concepts of `Facts` and `Theories`. These are codified using decorators, aptly named `@fact` and `@theory`. Consider these examples:
146
+
147
+ ```python
148
+
149
+ from punit import fact, theory, inlinedata
150
+
151
+ @fact
152
+ async def MyLibrary_WhenInitialized_TouchMustReturnTrue:
153
+ mylib = MyLibrary()
154
+ mylib.initialize()
155
+ await asyncio.sleep(1)
156
+ assert mylib.touch(), "Expecting touch() to return true, because initialize() was called."
157
+
158
+ class MyTestFixture:
159
+
160
+ def calc(number:int|None = None):
161
+ if number == None:
162
+ raise Exception('Invalid value "None".')
163
+ return number * number
164
+
165
+ @theory
166
+ @inlinedata(0, 0)
167
+ @inlinedata(1, 1)
168
+ @inlinedata(2, 4)
169
+ @inlinedata(3, 9)
170
+ @inlinedata(5, 25)
171
+ @inlinedata(8, 64)
172
+ def verifyCalcAssumptions(self, number:int, expected:int) -> None:
173
+ assert expected == self.calc(number)
174
+
175
+ @fact
176
+ def verifyCalcErrorCondition(self) -> None:
177
+ from punit.exceptions import raises
178
+ # assert errors are raised, or not
179
+ def calc_None():
180
+ self.calc(None)
181
+ def calc_1()
182
+ self.calc(1)
183
+ assert raises[Exception](calc_None)
184
+ assert not raises[Exception](calc_1)
185
+ ```
186
+
187
+ Because `pUnit` is a decorative framework you are afforded the utmost freedom in how you structure and implement your tests.
188
+
189
+ Unlike other testing frameworks, the names you use for functions, classes, and methods is not relevant. There is no requirement to inherit classes from specific abstract/base classes for any particular functionality. There is no requirement that your tests be organized into modules with `__init__.py` files.
190
+
191
+ You will want to take particular note of the `--exclude` command-line parameter which allows you to restrict what `pUnit` will consider to be a valid test file. While the default behavior will fit 99% of use-cases, you _can_ exercise more control over the discovery process.
192
+
193
+ ## Vision, Future, and LTS
194
+
195
+ The long-term vision is to provide both imperative and declarative syntaxes for testing while keeping `pUnit` as simple as possible in its implementation.
196
+
197
+ `pUnit` is a Python 3.12+ package and there are no plans to backport it to earlier versions of Python, however, user contributions to support backward compatibility _will be accepted_ when it makes sense.
198
+
199
+ As Python progresses so will `pUnit` and SEMVER rules will be respected to provide developers with assurance that a major version of `pUnit` is fit for a particular purpose, thus, if there is ever a breaking change in Python that requires a breaking change in `pUnit` you can expect `pUnit` versioning to reflect this.
200
+
201
+ In any situation where an undocumented feature may be used maintainers will actively keep watch on deprecation notices and removals, will clearly identify these dependencies in the docs, and most importantly will provide a LTS alternative to any undocumented feature or such features will not be included in `pUnit`.
202
+
203
+ With respect to long-term support, we will commit to maintaining major versions for 3 years from the date they were superceded by a new major version. This should align well with Python Core Development.
204
+
205
+ ## Contact
206
+
207
+ You can reach me on [Discord](https://discordapp.com/users/307684202080501761) or [open an Issue on Github](https://github.com/wilson0x4d/punit/issues/new/choose).
punit-1.2.0/README.md ADDED
@@ -0,0 +1,161 @@
1
+ `pUnit` is a modernized unit-testing framework for Python, inspired by xUnit.
2
+
3
+ This README is only a high-level introduction to **pUnit**. For more detailed documentation, please view the official docs at [https://pUnit.readthedocs.io](https://pUnit.readthedocs.io).
4
+
5
+ ## Command-Line Usage
6
+
7
+ Running pUnit with no arguments will perform test auto-discovery and execution:
8
+
9
+ ```bash
10
+ python3 -m punit
11
+ ```
12
+
13
+ By default it will look for tests in the `tests/` directory. You can override this behavior by providing a `--test-package` argument:
14
+
15
+ ```bash
16
+ python3 -m punit --test-package elsewhere
17
+ ```
18
+
19
+ In the above example, test discovery will instead occur in an `elsewhere/` directory.
20
+
21
+ ### Report Generation
22
+
23
+ `pUnit` can generate "Test Results" reports. Currently supporting HTML and jUnit formats:
24
+
25
+ ```bash
26
+ python3 -m punit --report html
27
+ ```
28
+
29
+ Reports can also be output to a file where they can be lifted as part of a CI/CD pipeline and stored as an artifact or used for other purposes:
30
+
31
+ ```bash
32
+ python3 -m punit --report junit --output results.xml
33
+ ```
34
+
35
+ ### Syntax Help
36
+
37
+ There are more options available, passing a `--help` argument will print help text:
38
+
39
+ ```bash
40
+ python3 -m punit --help
41
+ ```
42
+ Outputs:
43
+ ```plaintext
44
+ Usage: python3 -m punit [-h|--help]
45
+ [-q|--quiet] [-v|--verbose]
46
+ [-z|--failfast]
47
+ [-p|--test-package NAME]
48
+ [-i|--include PATTERN]
49
+ [-e|--exclude PATTERN]
50
+ [-f|--filter PATTERN]
51
+ [-t|--trait [!]NAME[=VALUE]]
52
+ [-w|--workdir DIRECTORY]
53
+ [-n|--no-default-patterns]
54
+ [-r|--report {junit|json}]
55
+ [-o|--output FILENAME]
56
+
57
+ Options:
58
+ -h, --help Show this help text and exit
59
+ -q, --quiet Quiet output
60
+ -v, --verbose Verbose output
61
+ -z, --failfast Stop on first failure or error
62
+ -p, --test-package NAME
63
+ Use NAME as the test package, all tests should
64
+ be locatable as modules in the named package.
65
+ Default: 'tests'
66
+ -i, --include PATTERN
67
+ Include any tests matching PATTERN
68
+ Default: '*.py'
69
+ -e, --exclude PATTERN
70
+ Exclude any tests matching PATTERN, overriding --include
71
+ Default: '__*__' (dunder files), '/.*/' (dot-directories)
72
+ -f, --filter PATTERN
73
+ Only execute tests matching PATTERN
74
+ Default: '*'
75
+ -t, --trait [!]NAME[=VALUE]
76
+ Execute tests with the specified trait, negated by prefixing with '!'.
77
+ If VALUE is specified, matches tests with the trait having specified value.
78
+ If VALUE is not specified, matches any test with the trait having any value.
79
+ Default: No filtering based on traits.
80
+ -w, --working-directory DIRECTORY
81
+ Working directory (defaults to start directory)
82
+ -n, --no-default-patterns
83
+ Do not apply any default include/exclude patterns.
84
+ -r, --report {html|junit}
85
+ Generate a report to stdout using either an "html"
86
+ or "junit" format. When generating a report to stdout
87
+ all other output is suppressed, unless `--output`
88
+ is also specified.
89
+ -o, --output FILENAME
90
+ If `--report` is used, instead of writing to stdout
91
+ write to FILENAME. In this case `--report` does not
92
+ suppress any program output.
93
+ ```
94
+
95
+ ## Writing Tests
96
+
97
+ You can write tests as functions, class methods, instance methods, or static methods with all forms offering identical functionality. You can also utilize async/await syntax without any additional overhead.
98
+
99
+ **pUnit** is based upon the fundamental concepts of `Facts` and `Theories`. These are codified using decorators, aptly named `@fact` and `@theory`. Consider these examples:
100
+
101
+ ```python
102
+
103
+ from punit import fact, theory, inlinedata
104
+
105
+ @fact
106
+ async def MyLibrary_WhenInitialized_TouchMustReturnTrue:
107
+ mylib = MyLibrary()
108
+ mylib.initialize()
109
+ await asyncio.sleep(1)
110
+ assert mylib.touch(), "Expecting touch() to return true, because initialize() was called."
111
+
112
+ class MyTestFixture:
113
+
114
+ def calc(number:int|None = None):
115
+ if number == None:
116
+ raise Exception('Invalid value "None".')
117
+ return number * number
118
+
119
+ @theory
120
+ @inlinedata(0, 0)
121
+ @inlinedata(1, 1)
122
+ @inlinedata(2, 4)
123
+ @inlinedata(3, 9)
124
+ @inlinedata(5, 25)
125
+ @inlinedata(8, 64)
126
+ def verifyCalcAssumptions(self, number:int, expected:int) -> None:
127
+ assert expected == self.calc(number)
128
+
129
+ @fact
130
+ def verifyCalcErrorCondition(self) -> None:
131
+ from punit.exceptions import raises
132
+ # assert errors are raised, or not
133
+ def calc_None():
134
+ self.calc(None)
135
+ def calc_1()
136
+ self.calc(1)
137
+ assert raises[Exception](calc_None)
138
+ assert not raises[Exception](calc_1)
139
+ ```
140
+
141
+ Because `pUnit` is a decorative framework you are afforded the utmost freedom in how you structure and implement your tests.
142
+
143
+ Unlike other testing frameworks, the names you use for functions, classes, and methods is not relevant. There is no requirement to inherit classes from specific abstract/base classes for any particular functionality. There is no requirement that your tests be organized into modules with `__init__.py` files.
144
+
145
+ You will want to take particular note of the `--exclude` command-line parameter which allows you to restrict what `pUnit` will consider to be a valid test file. While the default behavior will fit 99% of use-cases, you _can_ exercise more control over the discovery process.
146
+
147
+ ## Vision, Future, and LTS
148
+
149
+ The long-term vision is to provide both imperative and declarative syntaxes for testing while keeping `pUnit` as simple as possible in its implementation.
150
+
151
+ `pUnit` is a Python 3.12+ package and there are no plans to backport it to earlier versions of Python, however, user contributions to support backward compatibility _will be accepted_ when it makes sense.
152
+
153
+ As Python progresses so will `pUnit` and SEMVER rules will be respected to provide developers with assurance that a major version of `pUnit` is fit for a particular purpose, thus, if there is ever a breaking change in Python that requires a breaking change in `pUnit` you can expect `pUnit` versioning to reflect this.
154
+
155
+ In any situation where an undocumented feature may be used maintainers will actively keep watch on deprecation notices and removals, will clearly identify these dependencies in the docs, and most importantly will provide a LTS alternative to any undocumented feature or such features will not be included in `pUnit`.
156
+
157
+ With respect to long-term support, we will commit to maintaining major versions for 3 years from the date they were superceded by a new major version. This should align well with Python Core Development.
158
+
159
+ ## Contact
160
+
161
+ You can reach me on [Discord](https://discordapp.com/users/307684202080501761) or [open an Issue on Github](https://github.com/wilson0x4d/punit/issues/new/choose).
@@ -0,0 +1,48 @@
1
+ [project]
2
+ name = "pUnit"
3
+ version = "1.2.0"
4
+ description = "A modernized unit-test framework for Python."
5
+ keywords = ["test", "unittest", "unit-test", "xUnit", "nUnit", "pytest"]
6
+ authors = [
7
+ { name="Shaun Wilson", email="mrshaunwilson@msn.com" }
8
+ ]
9
+ readme = "README.md"
10
+ license = {file = "LICENSE"}
11
+ requires-python = ">=3.12"
12
+ classifiers = [
13
+ "Operating System :: OS Independent",
14
+ "Programming Language :: Python :: 3.12",
15
+ "Programming Language :: Python :: 3.13",
16
+ "Programming Language :: Python :: 3.14",
17
+ "Topic :: Software Development :: Libraries :: Python Modules",
18
+ "Intended Audience :: Developers"
19
+ ]
20
+ dependencies = [
21
+ ]
22
+
23
+ [project.optional-dependencies]
24
+ dev = [
25
+ "build",
26
+ "setuptools",
27
+ "twine"
28
+ ]
29
+
30
+ [project.urls]
31
+ Documentation = "https://punit.readthedocs.io/"
32
+ Homepage = "https://github.com/wilson0x4d/punit"
33
+ Repository = "https://github.com/wilson0x4d/punit.git"
34
+
35
+ [build-system]
36
+ requires = ["setuptools"]
37
+ build-backend = "setuptools.build_meta"
38
+
39
+ [tool.setuptools.packages.find]
40
+ where = ["src"]
41
+ exclude = [
42
+ "docs",
43
+ "tests",
44
+ "tests.*"
45
+ ]
46
+
47
+ [tool.setuptools.data-files]
48
+ bin = [".scripts/punit"]
punit-1.2.0/setup.cfg ADDED
@@ -0,0 +1,4 @@
1
+ [egg_info]
2
+ tag_build =
3
+ tag_date = 0
4
+
@@ -0,0 +1,207 @@
1
+ Metadata-Version: 2.4
2
+ Name: pUnit
3
+ Version: 1.2.0
4
+ Summary: A modernized unit-test framework for Python.
5
+ Author-email: Shaun Wilson <mrshaunwilson@msn.com>
6
+ License: MIT License
7
+
8
+ Copyright (c) Shaun Wilson
9
+
10
+ Permission is hereby granted, free of charge, to any person obtaining a copy
11
+ of this software and associated documentation files (the "Software"), to deal
12
+ in the Software without restriction, including without limitation the rights
13
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
14
+ copies of the Software, and to permit persons to whom the Software is
15
+ furnished to do so, subject to the following conditions:
16
+
17
+ The above copyright notice and this permission notice shall be included in all
18
+ copies or substantial portions of the Software.
19
+
20
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
21
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
22
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
23
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
24
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
25
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
26
+ SOFTWARE.
27
+
28
+ Project-URL: Documentation, https://punit.readthedocs.io/
29
+ Project-URL: Homepage, https://github.com/wilson0x4d/punit
30
+ Project-URL: Repository, https://github.com/wilson0x4d/punit.git
31
+ Keywords: test,unittest,unit-test,xUnit,nUnit,pytest
32
+ Classifier: Operating System :: OS Independent
33
+ Classifier: Programming Language :: Python :: 3.12
34
+ Classifier: Programming Language :: Python :: 3.13
35
+ Classifier: Programming Language :: Python :: 3.14
36
+ Classifier: Topic :: Software Development :: Libraries :: Python Modules
37
+ Classifier: Intended Audience :: Developers
38
+ Requires-Python: >=3.12
39
+ Description-Content-Type: text/markdown
40
+ License-File: LICENSE
41
+ Provides-Extra: dev
42
+ Requires-Dist: build; extra == "dev"
43
+ Requires-Dist: setuptools; extra == "dev"
44
+ Requires-Dist: twine; extra == "dev"
45
+ Dynamic: license-file
46
+
47
+ `pUnit` is a modernized unit-testing framework for Python, inspired by xUnit.
48
+
49
+ This README is only a high-level introduction to **pUnit**. For more detailed documentation, please view the official docs at [https://pUnit.readthedocs.io](https://pUnit.readthedocs.io).
50
+
51
+ ## Command-Line Usage
52
+
53
+ Running pUnit with no arguments will perform test auto-discovery and execution:
54
+
55
+ ```bash
56
+ python3 -m punit
57
+ ```
58
+
59
+ By default it will look for tests in the `tests/` directory. You can override this behavior by providing a `--test-package` argument:
60
+
61
+ ```bash
62
+ python3 -m punit --test-package elsewhere
63
+ ```
64
+
65
+ In the above example, test discovery will instead occur in an `elsewhere/` directory.
66
+
67
+ ### Report Generation
68
+
69
+ `pUnit` can generate "Test Results" reports. Currently supporting HTML and jUnit formats:
70
+
71
+ ```bash
72
+ python3 -m punit --report html
73
+ ```
74
+
75
+ Reports can also be output to a file where they can be lifted as part of a CI/CD pipeline and stored as an artifact or used for other purposes:
76
+
77
+ ```bash
78
+ python3 -m punit --report junit --output results.xml
79
+ ```
80
+
81
+ ### Syntax Help
82
+
83
+ There are more options available, passing a `--help` argument will print help text:
84
+
85
+ ```bash
86
+ python3 -m punit --help
87
+ ```
88
+ Outputs:
89
+ ```plaintext
90
+ Usage: python3 -m punit [-h|--help]
91
+ [-q|--quiet] [-v|--verbose]
92
+ [-z|--failfast]
93
+ [-p|--test-package NAME]
94
+ [-i|--include PATTERN]
95
+ [-e|--exclude PATTERN]
96
+ [-f|--filter PATTERN]
97
+ [-t|--trait [!]NAME[=VALUE]]
98
+ [-w|--workdir DIRECTORY]
99
+ [-n|--no-default-patterns]
100
+ [-r|--report {junit|json}]
101
+ [-o|--output FILENAME]
102
+
103
+ Options:
104
+ -h, --help Show this help text and exit
105
+ -q, --quiet Quiet output
106
+ -v, --verbose Verbose output
107
+ -z, --failfast Stop on first failure or error
108
+ -p, --test-package NAME
109
+ Use NAME as the test package, all tests should
110
+ be locatable as modules in the named package.
111
+ Default: 'tests'
112
+ -i, --include PATTERN
113
+ Include any tests matching PATTERN
114
+ Default: '*.py'
115
+ -e, --exclude PATTERN
116
+ Exclude any tests matching PATTERN, overriding --include
117
+ Default: '__*__' (dunder files), '/.*/' (dot-directories)
118
+ -f, --filter PATTERN
119
+ Only execute tests matching PATTERN
120
+ Default: '*'
121
+ -t, --trait [!]NAME[=VALUE]
122
+ Execute tests with the specified trait, negated by prefixing with '!'.
123
+ If VALUE is specified, matches tests with the trait having specified value.
124
+ If VALUE is not specified, matches any test with the trait having any value.
125
+ Default: No filtering based on traits.
126
+ -w, --working-directory DIRECTORY
127
+ Working directory (defaults to start directory)
128
+ -n, --no-default-patterns
129
+ Do not apply any default include/exclude patterns.
130
+ -r, --report {html|junit}
131
+ Generate a report to stdout using either an "html"
132
+ or "junit" format. When generating a report to stdout
133
+ all other output is suppressed, unless `--output`
134
+ is also specified.
135
+ -o, --output FILENAME
136
+ If `--report` is used, instead of writing to stdout
137
+ write to FILENAME. In this case `--report` does not
138
+ suppress any program output.
139
+ ```
140
+
141
+ ## Writing Tests
142
+
143
+ You can write tests as functions, class methods, instance methods, or static methods with all forms offering identical functionality. You can also utilize async/await syntax without any additional overhead.
144
+
145
+ **pUnit** is based upon the fundamental concepts of `Facts` and `Theories`. These are codified using decorators, aptly named `@fact` and `@theory`. Consider these examples:
146
+
147
+ ```python
148
+
149
+ from punit import fact, theory, inlinedata
150
+
151
+ @fact
152
+ async def MyLibrary_WhenInitialized_TouchMustReturnTrue:
153
+ mylib = MyLibrary()
154
+ mylib.initialize()
155
+ await asyncio.sleep(1)
156
+ assert mylib.touch(), "Expecting touch() to return true, because initialize() was called."
157
+
158
+ class MyTestFixture:
159
+
160
+ def calc(number:int|None = None):
161
+ if number == None:
162
+ raise Exception('Invalid value "None".')
163
+ return number * number
164
+
165
+ @theory
166
+ @inlinedata(0, 0)
167
+ @inlinedata(1, 1)
168
+ @inlinedata(2, 4)
169
+ @inlinedata(3, 9)
170
+ @inlinedata(5, 25)
171
+ @inlinedata(8, 64)
172
+ def verifyCalcAssumptions(self, number:int, expected:int) -> None:
173
+ assert expected == self.calc(number)
174
+
175
+ @fact
176
+ def verifyCalcErrorCondition(self) -> None:
177
+ from punit.exceptions import raises
178
+ # assert errors are raised, or not
179
+ def calc_None():
180
+ self.calc(None)
181
+ def calc_1()
182
+ self.calc(1)
183
+ assert raises[Exception](calc_None)
184
+ assert not raises[Exception](calc_1)
185
+ ```
186
+
187
+ Because `pUnit` is a decorative framework you are afforded the utmost freedom in how you structure and implement your tests.
188
+
189
+ Unlike other testing frameworks, the names you use for functions, classes, and methods is not relevant. There is no requirement to inherit classes from specific abstract/base classes for any particular functionality. There is no requirement that your tests be organized into modules with `__init__.py` files.
190
+
191
+ You will want to take particular note of the `--exclude` command-line parameter which allows you to restrict what `pUnit` will consider to be a valid test file. While the default behavior will fit 99% of use-cases, you _can_ exercise more control over the discovery process.
192
+
193
+ ## Vision, Future, and LTS
194
+
195
+ The long-term vision is to provide both imperative and declarative syntaxes for testing while keeping `pUnit` as simple as possible in its implementation.
196
+
197
+ `pUnit` is a Python 3.12+ package and there are no plans to backport it to earlier versions of Python, however, user contributions to support backward compatibility _will be accepted_ when it makes sense.
198
+
199
+ As Python progresses so will `pUnit` and SEMVER rules will be respected to provide developers with assurance that a major version of `pUnit` is fit for a particular purpose, thus, if there is ever a breaking change in Python that requires a breaking change in `pUnit` you can expect `pUnit` versioning to reflect this.
200
+
201
+ In any situation where an undocumented feature may be used maintainers will actively keep watch on deprecation notices and removals, will clearly identify these dependencies in the docs, and most importantly will provide a LTS alternative to any undocumented feature or such features will not be included in `pUnit`.
202
+
203
+ With respect to long-term support, we will commit to maintaining major versions for 3 years from the date they were superceded by a new major version. This should align well with Python Core Development.
204
+
205
+ ## Contact
206
+
207
+ You can reach me on [Discord](https://discordapp.com/users/307684202080501761) or [open an Issue on Github](https://github.com/wilson0x4d/punit/issues/new/choose).