pytest-inline-tdd 1.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_inline_tdd-1.1.0/.github/workflows/python-publish.yml +36 -0
- pytest_inline_tdd-1.1.0/.github/workflows/python-test.yml +40 -0
- pytest_inline_tdd-1.1.0/.gitignore +14 -0
- pytest_inline_tdd-1.1.0/LICENSE +19 -0
- pytest_inline_tdd-1.1.0/PKG-INFO +289 -0
- pytest_inline_tdd-1.1.0/README.md +235 -0
- pytest_inline_tdd-1.1.0/changelog +32 -0
- pytest_inline_tdd-1.1.0/convert_tests.py +68 -0
- pytest_inline_tdd-1.1.0/convert_to_tdd.py +73 -0
- pytest_inline_tdd-1.1.0/demo/README.md +26 -0
- pytest_inline_tdd-1.1.0/demo/example.py +36 -0
- pytest_inline_tdd-1.1.0/demo/features.py +84 -0
- pytest_inline_tdd-1.1.0/demo/parallel/a.py +15 -0
- pytest_inline_tdd-1.1.0/demo/parallel/b.py +17 -0
- pytest_inline_tdd-1.1.0/demo/parallel/c.py +17 -0
- pytest_inline_tdd-1.1.0/demo/parallel/d.py +17 -0
- pytest_inline_tdd-1.1.0/demo_tdd.py +45 -0
- pytest_inline_tdd-1.1.0/integration-tests/parallelization/test_files/a.py +13 -0
- pytest_inline_tdd-1.1.0/integration-tests/parallelization/test_files/a.py.backup +15 -0
- pytest_inline_tdd-1.1.0/integration-tests/parallelization/test_files/b.py +17 -0
- pytest_inline_tdd-1.1.0/integration-tests/parallelization/test_files/c.py +17 -0
- pytest_inline_tdd-1.1.0/integration-tests/parallelization/test_files/d.py +17 -0
- pytest_inline_tdd-1.1.0/integration-tests/parallelization/test_files/e.py +17 -0
- pytest_inline_tdd-1.1.0/integration-tests/parallelization/test_files/f.py +17 -0
- pytest_inline_tdd-1.1.0/integration-tests/parallelization/test_files/g.py +17 -0
- pytest_inline_tdd-1.1.0/integration-tests/parallelization/test_files/h.py +17 -0
- pytest_inline_tdd-1.1.0/integration-tests/parallelization/test_files/i.py +17 -0
- pytest_inline_tdd-1.1.0/integration-tests/parallelization/test_files/j.py +17 -0
- pytest_inline_tdd-1.1.0/integration-tests/parallelization/time-parallel-tests.sh +25 -0
- pytest_inline_tdd-1.1.0/integration-tests/tdd/a.py +517 -0
- pytest_inline_tdd-1.1.0/integration-tests/tdd/db.py +184 -0
- pytest_inline_tdd-1.1.0/integration-tests/tdd/np_ops.py +425 -0
- pytest_inline_tdd-1.1.0/prepare-conda-env.sh +60 -0
- pytest_inline_tdd-1.1.0/pyproject.toml +88 -0
- pytest_inline_tdd-1.1.0/src/inline_tdd/__about__.py +4 -0
- pytest_inline_tdd-1.1.0/src/inline_tdd/__init__.py +1 -0
- pytest_inline_tdd-1.1.0/src/inline_tdd/ast_future.py +982 -0
- pytest_inline_tdd-1.1.0/src/inline_tdd/inline.py +141 -0
- pytest_inline_tdd-1.1.0/src/inline_tdd/plugin.py +1529 -0
- pytest_inline_tdd-1.1.0/tdd_summary.py +36 -0
- pytest_inline_tdd-1.1.0/test_tdd.py +55 -0
- pytest_inline_tdd-1.1.0/test_tdd_verification.py +83 -0
- pytest_inline_tdd-1.1.0/tests/__init__.py +0 -0
- pytest_inline_tdd-1.1.0/tests/test_plugin.py +761 -0
- pytest_inline_tdd-1.1.0/tests/test_plugin.py.bak +767 -0
- pytest_inline_tdd-1.1.0/tox.ini +20 -0
|
@@ -0,0 +1,36 @@
|
|
|
1
|
+
# This workflow will upload a Python Package using Twine when a release is created
|
|
2
|
+
# For more information see: https://help.github.com/en/actions/language-and-framework-guides/using-python-with-github-actions#publishing-to-package-registries
|
|
3
|
+
|
|
4
|
+
# This workflow uses actions that are not certified by GitHub.
|
|
5
|
+
# They are provided by a third-party and are governed by
|
|
6
|
+
# separate terms of service, privacy policy, and support
|
|
7
|
+
# documentation.
|
|
8
|
+
|
|
9
|
+
name: Upload Python Package
|
|
10
|
+
|
|
11
|
+
on:
|
|
12
|
+
release:
|
|
13
|
+
types: [published]
|
|
14
|
+
|
|
15
|
+
jobs:
|
|
16
|
+
deploy:
|
|
17
|
+
|
|
18
|
+
runs-on: ubuntu-latest
|
|
19
|
+
|
|
20
|
+
steps:
|
|
21
|
+
- uses: actions/checkout@v3
|
|
22
|
+
- name: Set up Python
|
|
23
|
+
uses: actions/setup-python@v3
|
|
24
|
+
with:
|
|
25
|
+
python-version: '3.x'
|
|
26
|
+
- name: Install dependencies
|
|
27
|
+
run: |
|
|
28
|
+
python -m pip install --upgrade pip
|
|
29
|
+
pip install build
|
|
30
|
+
- name: Build package
|
|
31
|
+
run: python -m build
|
|
32
|
+
- name: Publish package
|
|
33
|
+
uses: pypa/gh-action-pypi-publish@27b31702a0e7fc50959f5ad993c78deac1bdfc29
|
|
34
|
+
with:
|
|
35
|
+
user: __token__
|
|
36
|
+
password: ${{ secrets.PYPI_API_TOKEN }}
|
|
@@ -0,0 +1,40 @@
|
|
|
1
|
+
name: Python Test
|
|
2
|
+
on:
|
|
3
|
+
push:
|
|
4
|
+
branches: [main]
|
|
5
|
+
pull_request:
|
|
6
|
+
branches: [main]
|
|
7
|
+
|
|
8
|
+
jobs:
|
|
9
|
+
build-linux:
|
|
10
|
+
runs-on: ${{ matrix.runs-on }}
|
|
11
|
+
strategy:
|
|
12
|
+
fail-fast: false
|
|
13
|
+
matrix:
|
|
14
|
+
python-version: ['3.8', '3.9', '3.10']
|
|
15
|
+
runs-on: [ubuntu-latest]
|
|
16
|
+
include:
|
|
17
|
+
- python-version: '3.7'
|
|
18
|
+
runs-on: ubuntu-22.04
|
|
19
|
+
|
|
20
|
+
steps:
|
|
21
|
+
- uses: actions/checkout@v4
|
|
22
|
+
- name: Set up Python ${{ matrix.python-version }}
|
|
23
|
+
uses: actions/setup-python@v5
|
|
24
|
+
with:
|
|
25
|
+
python-version: ${{ matrix.python-version }}
|
|
26
|
+
- name: Install dependencies
|
|
27
|
+
run: |
|
|
28
|
+
python -m pip install --upgrade pip
|
|
29
|
+
pip install -e .[dev]
|
|
30
|
+
- name: Test with tox
|
|
31
|
+
run: |
|
|
32
|
+
tox run
|
|
33
|
+
env:
|
|
34
|
+
TOX_GH_MAJOR_MINOR: ${{ matrix.python-version }}
|
|
35
|
+
- name: Test parallel execution
|
|
36
|
+
run: |
|
|
37
|
+
cd integration-tests/parallelization
|
|
38
|
+
bash time-parallel-tests.sh .
|
|
39
|
+
- name: Publish Unit Test Results
|
|
40
|
+
uses: EnricoMi/publish-unit-test-result-action@v2
|
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
Copyright (c) 2022-present Inline Tests Dev Team
|
|
2
|
+
|
|
3
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
4
|
+
of this software and associated documentation files (the "Software"), to deal
|
|
5
|
+
in the Software without restriction, including without limitation the rights
|
|
6
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
7
|
+
copies of the Software, and to permit persons to whom the Software is
|
|
8
|
+
furnished to do so, subject to the following conditions:
|
|
9
|
+
|
|
10
|
+
The above copyright notice and this permission notice shall be included in all
|
|
11
|
+
copies or substantial portions of the Software.
|
|
12
|
+
|
|
13
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
14
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
15
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
16
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
17
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
18
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
19
|
+
SOFTWARE.
|
|
@@ -0,0 +1,289 @@
|
|
|
1
|
+
Metadata-Version: 2.4
|
|
2
|
+
Name: pytest-inline-tdd
|
|
3
|
+
Version: 1.1.0
|
|
4
|
+
Summary: A pytest plugin for writing inline tests
|
|
5
|
+
Project-URL: Issues, https://github.com/zzctmac/inline-tdd/issues
|
|
6
|
+
Project-URL: Source, https://github.com/zzctmac/inline-tdd
|
|
7
|
+
Author-email: Zhichao Zhou <zhouzhch@shanghaitech.edu.cn>
|
|
8
|
+
Maintainer-email: Zhichao Zhou <zhouzhch@shanghaitech.edu.cn>
|
|
9
|
+
License: Copyright (c) 2022-present Inline Tests Dev Team
|
|
10
|
+
|
|
11
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
12
|
+
of this software and associated documentation files (the "Software"), to deal
|
|
13
|
+
in the Software without restriction, including without limitation the rights
|
|
14
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
15
|
+
copies of the Software, and to permit persons to whom the Software is
|
|
16
|
+
furnished to do so, subject to the following conditions:
|
|
17
|
+
|
|
18
|
+
The above copyright notice and this permission notice shall be included in all
|
|
19
|
+
copies or substantial portions of the Software.
|
|
20
|
+
|
|
21
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
22
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
23
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
24
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
25
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
26
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
27
|
+
SOFTWARE.
|
|
28
|
+
License-File: LICENSE
|
|
29
|
+
Classifier: Development Status :: 4 - Beta
|
|
30
|
+
Classifier: Framework :: Pytest
|
|
31
|
+
Classifier: Intended Audience :: Developers
|
|
32
|
+
Classifier: License :: OSI Approved :: MIT License
|
|
33
|
+
Classifier: Operating System :: OS Independent
|
|
34
|
+
Classifier: Programming Language :: Python
|
|
35
|
+
Classifier: Programming Language :: Python :: 3
|
|
36
|
+
Classifier: Programming Language :: Python :: 3 :: Only
|
|
37
|
+
Classifier: Programming Language :: Python :: 3.7
|
|
38
|
+
Classifier: Programming Language :: Python :: 3.8
|
|
39
|
+
Classifier: Programming Language :: Python :: 3.9
|
|
40
|
+
Classifier: Programming Language :: Python :: 3.10
|
|
41
|
+
Classifier: Programming Language :: Python :: Implementation :: CPython
|
|
42
|
+
Classifier: Programming Language :: Python :: Implementation :: PyPy
|
|
43
|
+
Classifier: Topic :: Software Development :: Testing
|
|
44
|
+
Requires-Python: >=3.7
|
|
45
|
+
Requires-Dist: pytest<9.0,>=7.0
|
|
46
|
+
Provides-Extra: dev
|
|
47
|
+
Requires-Dist: black; extra == 'dev'
|
|
48
|
+
Requires-Dist: coverage[toml]; extra == 'dev'
|
|
49
|
+
Requires-Dist: hatch; extra == 'dev'
|
|
50
|
+
Requires-Dist: ruff; extra == 'dev'
|
|
51
|
+
Requires-Dist: tox; extra == 'dev'
|
|
52
|
+
Requires-Dist: tox-gh; extra == 'dev'
|
|
53
|
+
Description-Content-Type: text/markdown
|
|
54
|
+
|
|
55
|
+
# pytest-inline-tdd
|
|
56
|
+
|
|
57
|
+
A [pytest](http://pytest.org) plugin for **test-driven inline testing** — write tests *before* the code they verify, right inside your production source files.
|
|
58
|
+
|
|
59
|
+
## Motivation
|
|
60
|
+
|
|
61
|
+
### The Problem with Traditional Unit Tests
|
|
62
|
+
|
|
63
|
+
Traditional unit tests live in separate `test_*.py` files, isolated from production code. This creates several issues:
|
|
64
|
+
|
|
65
|
+
1. **Distance breeds neglect** — Tests and source code reside in different files (or directories), making it easy to forget updating tests after code changes.
|
|
66
|
+
2. **Coarse granularity** — Unit tests typically target entire functions or methods, lacking focused verification of individual statements within a function.
|
|
67
|
+
3. **Lost context** — Reading test code requires constant switching between test files and source files, making intent harder to follow.
|
|
68
|
+
|
|
69
|
+
### Original pytest-inline (Inline Testing)
|
|
70
|
+
|
|
71
|
+
[pytest-inline](https://github.com/EngineeringSoftware/pytest-inline) introduced the concept of **inline testing**: placing tests right next to the source code to directly verify a statement's output.
|
|
72
|
+
|
|
73
|
+
```python
|
|
74
|
+
# Original inline testing: test comes AFTER the statement under test
|
|
75
|
+
def example(a):
|
|
76
|
+
b = a + 1
|
|
77
|
+
itest().given(a, 1).check_eq(b, 2) # Code first, test second
|
|
78
|
+
```
|
|
79
|
+
|
|
80
|
+
This solves the separation problem, but it is still a **code-first, test-later** workflow.
|
|
81
|
+
|
|
82
|
+
### pytest-inline-tdd: TDD Mode for Inline Testing
|
|
83
|
+
|
|
84
|
+
**pytest-inline-tdd** brings **Test-Driven Development (TDD)** to inline testing. The core idea:
|
|
85
|
+
|
|
86
|
+
> **Write the test (expectation) first, then write the implementation. Tests and code are tightly coupled in the same file, the same function.**
|
|
87
|
+
|
|
88
|
+
```python
|
|
89
|
+
# TDD inline testing: test comes BEFORE the statement under test
|
|
90
|
+
from inline_tdd import itestdd
|
|
91
|
+
|
|
92
|
+
def example(a):
|
|
93
|
+
itestdd().given(a, 1).check_eq(b, 2) # Test first: expect b=2 when a=1
|
|
94
|
+
b = a + 1 # Then implement
|
|
95
|
+
```
|
|
96
|
+
|
|
97
|
+
### Key Differences from Original pytest-inline
|
|
98
|
+
|
|
99
|
+
| Feature | pytest-inline | pytest-inline-tdd |
|
|
100
|
+
|---------|-------------|-------------------|
|
|
101
|
+
| Test position | **After** the statement under test | **Before** the statement under test |
|
|
102
|
+
| Workflow | Code first, test later | **Test first, code later (TDD)** |
|
|
103
|
+
| Package name | `inline` | `inline_tdd` |
|
|
104
|
+
| API name | `itest()` | `itestdd()` |
|
|
105
|
+
| Mindset | Verify already-written code | Declare expected behavior, then implement |
|
|
106
|
+
| Compatibility | Supports both pre/post modes | Supports both pre (TDD) and post modes |
|
|
107
|
+
|
|
108
|
+
## Use Cases
|
|
109
|
+
|
|
110
|
+
### 1. Statement-Level TDD
|
|
111
|
+
|
|
112
|
+
Write your expectation for a statement first, then implement it — testing and coding happen together:
|
|
113
|
+
|
|
114
|
+
```python
|
|
115
|
+
from inline_tdd import itestdd
|
|
116
|
+
|
|
117
|
+
def calculate_discount(price, rate):
|
|
118
|
+
itestdd().given(price, 100).given(rate, 0.2).check_eq(discount, 20.0)
|
|
119
|
+
discount = price * rate
|
|
120
|
+
|
|
121
|
+
itestdd().given(price, 100).given(discount, 20.0).check_eq(final, 80.0)
|
|
122
|
+
final = price - discount
|
|
123
|
+
|
|
124
|
+
return final
|
|
125
|
+
```
|
|
126
|
+
|
|
127
|
+
### 2. Branch Verification for Complex Control Flow
|
|
128
|
+
|
|
129
|
+
Independently verify each branch of if/elif/else, for, and while statements:
|
|
130
|
+
|
|
131
|
+
```python
|
|
132
|
+
def classify(a):
|
|
133
|
+
itestdd().given(a, 15).check_eq(b, "large")
|
|
134
|
+
itestdd().given(a, 5).check_eq(b, "medium")
|
|
135
|
+
itestdd().given(a, -1).check_eq(b, "small")
|
|
136
|
+
if a > 10:
|
|
137
|
+
b = "large"
|
|
138
|
+
elif a > 0:
|
|
139
|
+
b = "medium"
|
|
140
|
+
else:
|
|
141
|
+
b = "small"
|
|
142
|
+
```
|
|
143
|
+
|
|
144
|
+
### 3. Step-by-Step Data Pipeline Verification
|
|
145
|
+
|
|
146
|
+
Test each transformation step in place, ensuring every stage of a pipeline behaves as expected:
|
|
147
|
+
|
|
148
|
+
```python
|
|
149
|
+
import numpy as np
|
|
150
|
+
from inline_tdd import itestdd
|
|
151
|
+
|
|
152
|
+
def normalize(data):
|
|
153
|
+
itestdd().given(data, np.array([2.0, 4.0, 6.0])).check_eq(m, 4.0)
|
|
154
|
+
m = np.mean(data)
|
|
155
|
+
|
|
156
|
+
itestdd().given(data, np.array([2.0, 4.0, 6.0])).given(m, 4.0).check_eq(
|
|
157
|
+
centered.tolist(), [-2.0, 0.0, 2.0])
|
|
158
|
+
centered = data - m
|
|
159
|
+
|
|
160
|
+
return centered
|
|
161
|
+
```
|
|
162
|
+
|
|
163
|
+
### 4. Database Operation Verification
|
|
164
|
+
|
|
165
|
+
Inline tests work well for verifying the results of SQL queries and operations:
|
|
166
|
+
|
|
167
|
+
```python
|
|
168
|
+
import sqlite3
|
|
169
|
+
from inline_tdd import itestdd
|
|
170
|
+
|
|
171
|
+
def count_users(conn):
|
|
172
|
+
itestdd().check_eq(n, 3)
|
|
173
|
+
n = conn.execute("SELECT COUNT(*) FROM users").fetchone()[0]
|
|
174
|
+
return n
|
|
175
|
+
```
|
|
176
|
+
|
|
177
|
+
### 5. Parameterized Tests
|
|
178
|
+
|
|
179
|
+
Cover multiple input-output pairs in a single statement:
|
|
180
|
+
|
|
181
|
+
```python
|
|
182
|
+
def double(a):
|
|
183
|
+
itestdd(parameterized=True).given(a, [1, 2, 3]).check_eq(b, [2, 4, 6])
|
|
184
|
+
b = a * 2
|
|
185
|
+
```
|
|
186
|
+
|
|
187
|
+
## Install
|
|
188
|
+
|
|
189
|
+
```bash
|
|
190
|
+
pip install pytest-inline-tdd
|
|
191
|
+
```
|
|
192
|
+
|
|
193
|
+
## Use
|
|
194
|
+
|
|
195
|
+
```bash
|
|
196
|
+
# Run all inline tests in the current directory
|
|
197
|
+
pytest .
|
|
198
|
+
|
|
199
|
+
# Run inline tests in a specific file
|
|
200
|
+
pytest path/to/file.py
|
|
201
|
+
|
|
202
|
+
# Run tests with a specific tag
|
|
203
|
+
pytest . --inline-group tag_name
|
|
204
|
+
```
|
|
205
|
+
|
|
206
|
+
## API
|
|
207
|
+
|
|
208
|
+
### Declaring an Inline Test
|
|
209
|
+
|
|
210
|
+
```python
|
|
211
|
+
itestdd(test_name, parameterized, repeated, tag, disabled, timeout)
|
|
212
|
+
```
|
|
213
|
+
|
|
214
|
+
| Parameter | Type | Default | Description |
|
|
215
|
+
|-----------|------|---------|-------------|
|
|
216
|
+
| `test_name` | str | filename + line number | Name of the test |
|
|
217
|
+
| `parameterized` | bool | False | Whether the test is parameterized |
|
|
218
|
+
| `repeated` | int | 1 | Number of times to repeat the test |
|
|
219
|
+
| `tag` | list | [] | Tags for grouping and filtering |
|
|
220
|
+
| `disabled` | bool | False | Whether the test is disabled |
|
|
221
|
+
| `timeout` | float | -1.0 | Timeout in seconds (-1 for no limit) |
|
|
222
|
+
|
|
223
|
+
### Preconditions: assume
|
|
224
|
+
|
|
225
|
+
```python
|
|
226
|
+
itestdd().assume(condition).given(...).check_eq(...)
|
|
227
|
+
```
|
|
228
|
+
|
|
229
|
+
If `condition` is False, the test is skipped. Must appear before any `given()` calls, and only one `assume()` is allowed per test.
|
|
230
|
+
|
|
231
|
+
### Test Inputs: given
|
|
232
|
+
|
|
233
|
+
```python
|
|
234
|
+
itestdd().given(variable, value)
|
|
235
|
+
```
|
|
236
|
+
|
|
237
|
+
Multiple `given()` calls can be chained. Assigns test input values to variables used in the statement under test.
|
|
238
|
+
|
|
239
|
+
### Test Assertions: check_*
|
|
240
|
+
|
|
241
|
+
| Method | Description |
|
|
242
|
+
|--------|-------------|
|
|
243
|
+
| `check_eq(actual, expected)` | Equal |
|
|
244
|
+
| `check_neq(actual, expected)` | Not equal |
|
|
245
|
+
| `check_true(expr)` | Expression is true |
|
|
246
|
+
| `check_false(expr)` | Expression is false |
|
|
247
|
+
| `check_none(var)` | Value is None |
|
|
248
|
+
| `check_not_none(var)` | Value is not None |
|
|
249
|
+
| `check_same(a, b)` | Same object (`is`) |
|
|
250
|
+
| `check_not_same(a, b)` | Different objects |
|
|
251
|
+
| `fail()` | Force failure |
|
|
252
|
+
|
|
253
|
+
Only one check assertion is allowed per inline test.
|
|
254
|
+
|
|
255
|
+
## Performance
|
|
256
|
+
|
|
257
|
+
Inline tests are fast — each test verifies only a single statement. In non-testing mode (i.e., normal production execution), all `itestdd()` calls behave as no-op function calls with negligible overhead.
|
|
258
|
+
|
|
259
|
+
## Citation
|
|
260
|
+
|
|
261
|
+
This project builds on the following research:
|
|
262
|
+
|
|
263
|
+
Title: [Inline Tests](https://dl.acm.org/doi/abs/10.1145/3551349.3556952)
|
|
264
|
+
|
|
265
|
+
Authors: [Yu Liu](https://sweetstreet.github.io/), [Pengyu Nie](https://pengyunie.github.io/), [Owolabi Legunsen](https://mir.cs.illinois.edu/legunsen/), [Milos Gligoric](http://users.ece.utexas.edu/~gligoric/)
|
|
266
|
+
|
|
267
|
+
```bibtex
|
|
268
|
+
@inproceedings{LiuASE22InlineTests,
|
|
269
|
+
title = {Inline Tests},
|
|
270
|
+
author = {Yu Liu and Pengyu Nie and Owolabi Legunsen and Milos Gligoric},
|
|
271
|
+
pages = {1--13},
|
|
272
|
+
booktitle = {International Conference on Automated Software Engineering},
|
|
273
|
+
year = {2022},
|
|
274
|
+
}
|
|
275
|
+
```
|
|
276
|
+
|
|
277
|
+
Title: [pytest-inline](https://pengyunie.github.io/p/LiuETAL23pytest-inline.pdf)
|
|
278
|
+
|
|
279
|
+
Authors: [Yu Liu](https://sweetstreet.github.io/), [Zachary Thurston](), [Alan Han](), [Pengyu Nie](https://pengyunie.github.io/), [Milos Gligoric](http://users.ece.utexas.edu/~gligoric/), [Owolabi Legunsen](https://mir.cs.illinois.edu/legunsen/)
|
|
280
|
+
|
|
281
|
+
```bibtex
|
|
282
|
+
@inproceedings{LiuICSE23PytestInline,
|
|
283
|
+
title = {pytest-inline: An Inline Testing Tool for Python},
|
|
284
|
+
author = {Yu Liu and Zachary Thurston and Alan Han and Pengyu Nie and Milos Gligoric and Owolabi Legunsen},
|
|
285
|
+
pages = {1--4},
|
|
286
|
+
booktitle = {International Conference on Software Engineering, DEMO},
|
|
287
|
+
year = {2023},
|
|
288
|
+
}
|
|
289
|
+
```
|
|
@@ -0,0 +1,235 @@
|
|
|
1
|
+
# pytest-inline-tdd
|
|
2
|
+
|
|
3
|
+
A [pytest](http://pytest.org) plugin for **test-driven inline testing** — write tests *before* the code they verify, right inside your production source files.
|
|
4
|
+
|
|
5
|
+
## Motivation
|
|
6
|
+
|
|
7
|
+
### The Problem with Traditional Unit Tests
|
|
8
|
+
|
|
9
|
+
Traditional unit tests live in separate `test_*.py` files, isolated from production code. This creates several issues:
|
|
10
|
+
|
|
11
|
+
1. **Distance breeds neglect** — Tests and source code reside in different files (or directories), making it easy to forget updating tests after code changes.
|
|
12
|
+
2. **Coarse granularity** — Unit tests typically target entire functions or methods, lacking focused verification of individual statements within a function.
|
|
13
|
+
3. **Lost context** — Reading test code requires constant switching between test files and source files, making intent harder to follow.
|
|
14
|
+
|
|
15
|
+
### Original pytest-inline (Inline Testing)
|
|
16
|
+
|
|
17
|
+
[pytest-inline](https://github.com/EngineeringSoftware/pytest-inline) introduced the concept of **inline testing**: placing tests right next to the source code to directly verify a statement's output.
|
|
18
|
+
|
|
19
|
+
```python
|
|
20
|
+
# Original inline testing: test comes AFTER the statement under test
|
|
21
|
+
def example(a):
|
|
22
|
+
b = a + 1
|
|
23
|
+
itest().given(a, 1).check_eq(b, 2) # Code first, test second
|
|
24
|
+
```
|
|
25
|
+
|
|
26
|
+
This solves the separation problem, but it is still a **code-first, test-later** workflow.
|
|
27
|
+
|
|
28
|
+
### pytest-inline-tdd: TDD Mode for Inline Testing
|
|
29
|
+
|
|
30
|
+
**pytest-inline-tdd** brings **Test-Driven Development (TDD)** to inline testing. The core idea:
|
|
31
|
+
|
|
32
|
+
> **Write the test (expectation) first, then write the implementation. Tests and code are tightly coupled in the same file, the same function.**
|
|
33
|
+
|
|
34
|
+
```python
|
|
35
|
+
# TDD inline testing: test comes BEFORE the statement under test
|
|
36
|
+
from inline_tdd import itestdd
|
|
37
|
+
|
|
38
|
+
def example(a):
|
|
39
|
+
itestdd().given(a, 1).check_eq(b, 2) # Test first: expect b=2 when a=1
|
|
40
|
+
b = a + 1 # Then implement
|
|
41
|
+
```
|
|
42
|
+
|
|
43
|
+
### Key Differences from Original pytest-inline
|
|
44
|
+
|
|
45
|
+
| Feature | pytest-inline | pytest-inline-tdd |
|
|
46
|
+
|---------|-------------|-------------------|
|
|
47
|
+
| Test position | **After** the statement under test | **Before** the statement under test |
|
|
48
|
+
| Workflow | Code first, test later | **Test first, code later (TDD)** |
|
|
49
|
+
| Package name | `inline` | `inline_tdd` |
|
|
50
|
+
| API name | `itest()` | `itestdd()` |
|
|
51
|
+
| Mindset | Verify already-written code | Declare expected behavior, then implement |
|
|
52
|
+
| Compatibility | Supports both pre/post modes | Supports both pre (TDD) and post modes |
|
|
53
|
+
|
|
54
|
+
## Use Cases
|
|
55
|
+
|
|
56
|
+
### 1. Statement-Level TDD
|
|
57
|
+
|
|
58
|
+
Write your expectation for a statement first, then implement it — testing and coding happen together:
|
|
59
|
+
|
|
60
|
+
```python
|
|
61
|
+
from inline_tdd import itestdd
|
|
62
|
+
|
|
63
|
+
def calculate_discount(price, rate):
|
|
64
|
+
itestdd().given(price, 100).given(rate, 0.2).check_eq(discount, 20.0)
|
|
65
|
+
discount = price * rate
|
|
66
|
+
|
|
67
|
+
itestdd().given(price, 100).given(discount, 20.0).check_eq(final, 80.0)
|
|
68
|
+
final = price - discount
|
|
69
|
+
|
|
70
|
+
return final
|
|
71
|
+
```
|
|
72
|
+
|
|
73
|
+
### 2. Branch Verification for Complex Control Flow
|
|
74
|
+
|
|
75
|
+
Independently verify each branch of if/elif/else, for, and while statements:
|
|
76
|
+
|
|
77
|
+
```python
|
|
78
|
+
def classify(a):
|
|
79
|
+
itestdd().given(a, 15).check_eq(b, "large")
|
|
80
|
+
itestdd().given(a, 5).check_eq(b, "medium")
|
|
81
|
+
itestdd().given(a, -1).check_eq(b, "small")
|
|
82
|
+
if a > 10:
|
|
83
|
+
b = "large"
|
|
84
|
+
elif a > 0:
|
|
85
|
+
b = "medium"
|
|
86
|
+
else:
|
|
87
|
+
b = "small"
|
|
88
|
+
```
|
|
89
|
+
|
|
90
|
+
### 3. Step-by-Step Data Pipeline Verification
|
|
91
|
+
|
|
92
|
+
Test each transformation step in place, ensuring every stage of a pipeline behaves as expected:
|
|
93
|
+
|
|
94
|
+
```python
|
|
95
|
+
import numpy as np
|
|
96
|
+
from inline_tdd import itestdd
|
|
97
|
+
|
|
98
|
+
def normalize(data):
|
|
99
|
+
itestdd().given(data, np.array([2.0, 4.0, 6.0])).check_eq(m, 4.0)
|
|
100
|
+
m = np.mean(data)
|
|
101
|
+
|
|
102
|
+
itestdd().given(data, np.array([2.0, 4.0, 6.0])).given(m, 4.0).check_eq(
|
|
103
|
+
centered.tolist(), [-2.0, 0.0, 2.0])
|
|
104
|
+
centered = data - m
|
|
105
|
+
|
|
106
|
+
return centered
|
|
107
|
+
```
|
|
108
|
+
|
|
109
|
+
### 4. Database Operation Verification
|
|
110
|
+
|
|
111
|
+
Inline tests work well for verifying the results of SQL queries and operations:
|
|
112
|
+
|
|
113
|
+
```python
|
|
114
|
+
import sqlite3
|
|
115
|
+
from inline_tdd import itestdd
|
|
116
|
+
|
|
117
|
+
def count_users(conn):
|
|
118
|
+
itestdd().check_eq(n, 3)
|
|
119
|
+
n = conn.execute("SELECT COUNT(*) FROM users").fetchone()[0]
|
|
120
|
+
return n
|
|
121
|
+
```
|
|
122
|
+
|
|
123
|
+
### 5. Parameterized Tests
|
|
124
|
+
|
|
125
|
+
Cover multiple input-output pairs in a single statement:
|
|
126
|
+
|
|
127
|
+
```python
|
|
128
|
+
def double(a):
|
|
129
|
+
itestdd(parameterized=True).given(a, [1, 2, 3]).check_eq(b, [2, 4, 6])
|
|
130
|
+
b = a * 2
|
|
131
|
+
```
|
|
132
|
+
|
|
133
|
+
## Install
|
|
134
|
+
|
|
135
|
+
```bash
|
|
136
|
+
pip install pytest-inline-tdd
|
|
137
|
+
```
|
|
138
|
+
|
|
139
|
+
## Use
|
|
140
|
+
|
|
141
|
+
```bash
|
|
142
|
+
# Run all inline tests in the current directory
|
|
143
|
+
pytest .
|
|
144
|
+
|
|
145
|
+
# Run inline tests in a specific file
|
|
146
|
+
pytest path/to/file.py
|
|
147
|
+
|
|
148
|
+
# Run tests with a specific tag
|
|
149
|
+
pytest . --inline-group tag_name
|
|
150
|
+
```
|
|
151
|
+
|
|
152
|
+
## API
|
|
153
|
+
|
|
154
|
+
### Declaring an Inline Test
|
|
155
|
+
|
|
156
|
+
```python
|
|
157
|
+
itestdd(test_name, parameterized, repeated, tag, disabled, timeout)
|
|
158
|
+
```
|
|
159
|
+
|
|
160
|
+
| Parameter | Type | Default | Description |
|
|
161
|
+
|-----------|------|---------|-------------|
|
|
162
|
+
| `test_name` | str | filename + line number | Name of the test |
|
|
163
|
+
| `parameterized` | bool | False | Whether the test is parameterized |
|
|
164
|
+
| `repeated` | int | 1 | Number of times to repeat the test |
|
|
165
|
+
| `tag` | list | [] | Tags for grouping and filtering |
|
|
166
|
+
| `disabled` | bool | False | Whether the test is disabled |
|
|
167
|
+
| `timeout` | float | -1.0 | Timeout in seconds (-1 for no limit) |
|
|
168
|
+
|
|
169
|
+
### Preconditions: assume
|
|
170
|
+
|
|
171
|
+
```python
|
|
172
|
+
itestdd().assume(condition).given(...).check_eq(...)
|
|
173
|
+
```
|
|
174
|
+
|
|
175
|
+
If `condition` is False, the test is skipped. Must appear before any `given()` calls, and only one `assume()` is allowed per test.
|
|
176
|
+
|
|
177
|
+
### Test Inputs: given
|
|
178
|
+
|
|
179
|
+
```python
|
|
180
|
+
itestdd().given(variable, value)
|
|
181
|
+
```
|
|
182
|
+
|
|
183
|
+
Multiple `given()` calls can be chained. Assigns test input values to variables used in the statement under test.
|
|
184
|
+
|
|
185
|
+
### Test Assertions: check_*
|
|
186
|
+
|
|
187
|
+
| Method | Description |
|
|
188
|
+
|--------|-------------|
|
|
189
|
+
| `check_eq(actual, expected)` | Equal |
|
|
190
|
+
| `check_neq(actual, expected)` | Not equal |
|
|
191
|
+
| `check_true(expr)` | Expression is true |
|
|
192
|
+
| `check_false(expr)` | Expression is false |
|
|
193
|
+
| `check_none(var)` | Value is None |
|
|
194
|
+
| `check_not_none(var)` | Value is not None |
|
|
195
|
+
| `check_same(a, b)` | Same object (`is`) |
|
|
196
|
+
| `check_not_same(a, b)` | Different objects |
|
|
197
|
+
| `fail()` | Force failure |
|
|
198
|
+
|
|
199
|
+
Only one check assertion is allowed per inline test.
|
|
200
|
+
|
|
201
|
+
## Performance
|
|
202
|
+
|
|
203
|
+
Inline tests are fast — each test verifies only a single statement. In non-testing mode (i.e., normal production execution), all `itestdd()` calls behave as no-op function calls with negligible overhead.
|
|
204
|
+
|
|
205
|
+
## Citation
|
|
206
|
+
|
|
207
|
+
This project builds on the following research:
|
|
208
|
+
|
|
209
|
+
Title: [Inline Tests](https://dl.acm.org/doi/abs/10.1145/3551349.3556952)
|
|
210
|
+
|
|
211
|
+
Authors: [Yu Liu](https://sweetstreet.github.io/), [Pengyu Nie](https://pengyunie.github.io/), [Owolabi Legunsen](https://mir.cs.illinois.edu/legunsen/), [Milos Gligoric](http://users.ece.utexas.edu/~gligoric/)
|
|
212
|
+
|
|
213
|
+
```bibtex
|
|
214
|
+
@inproceedings{LiuASE22InlineTests,
|
|
215
|
+
title = {Inline Tests},
|
|
216
|
+
author = {Yu Liu and Pengyu Nie and Owolabi Legunsen and Milos Gligoric},
|
|
217
|
+
pages = {1--13},
|
|
218
|
+
booktitle = {International Conference on Automated Software Engineering},
|
|
219
|
+
year = {2022},
|
|
220
|
+
}
|
|
221
|
+
```
|
|
222
|
+
|
|
223
|
+
Title: [pytest-inline](https://pengyunie.github.io/p/LiuETAL23pytest-inline.pdf)
|
|
224
|
+
|
|
225
|
+
Authors: [Yu Liu](https://sweetstreet.github.io/), [Zachary Thurston](), [Alan Han](), [Pengyu Nie](https://pengyunie.github.io/), [Milos Gligoric](http://users.ece.utexas.edu/~gligoric/), [Owolabi Legunsen](https://mir.cs.illinois.edu/legunsen/)
|
|
226
|
+
|
|
227
|
+
```bibtex
|
|
228
|
+
@inproceedings{LiuICSE23PytestInline,
|
|
229
|
+
title = {pytest-inline: An Inline Testing Tool for Python},
|
|
230
|
+
author = {Yu Liu and Zachary Thurston and Alan Han and Pengyu Nie and Milos Gligoric and Owolabi Legunsen},
|
|
231
|
+
pages = {1--4},
|
|
232
|
+
booktitle = {International Conference on Software Engineering, DEMO},
|
|
233
|
+
year = {2023},
|
|
234
|
+
}
|
|
235
|
+
```
|
|
@@ -0,0 +1,32 @@
|
|
|
1
|
+
# Changelog
|
|
2
|
+
## [1.1.0]
|
|
3
|
+
- Change: Support pytest 8
|
|
4
|
+
|
|
5
|
+
## [1.0.5]
|
|
6
|
+
- Change: Remove unused code
|
|
7
|
+
- Change: Capture a general Exception (not limited to ImportError and ModuleNotFoundError) when attempting to import modules during collection phase
|
|
8
|
+
|
|
9
|
+
## [1.0.4]
|
|
10
|
+
- Change: Update README
|
|
11
|
+
|
|
12
|
+
## [1.0.3]
|
|
13
|
+
### Changed
|
|
14
|
+
- Change: Use `itest` instead of `Here` for inline test constructor
|
|
15
|
+
|
|
16
|
+
## [1.0.2]
|
|
17
|
+
### Fix
|
|
18
|
+
- Fix: inlinetest-only skips collecting inline tests
|
|
19
|
+
|
|
20
|
+
### Added
|
|
21
|
+
- Add badges in README
|
|
22
|
+
|
|
23
|
+
## [1.0.1]
|
|
24
|
+
### Added
|
|
25
|
+
- New README
|
|
26
|
+
|
|
27
|
+
## [1.0.0]
|
|
28
|
+
### Added
|
|
29
|
+
|
|
30
|
+
- JUnit 5 features: timeout, more types of assertions, assumptions, and running tests in order
|
|
31
|
+
- Option "inlinetest-only"
|
|
32
|
+
- Integration tests of running tests in parallel with pytest-xdist
|