testlens 0.3.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.
@@ -0,0 +1,16 @@
1
+ # 本插件作为独立发行包时的忽略规则(仓库根另有一份覆盖全局的)
2
+ __pycache__/
3
+ *.py[cod]
4
+ .venv/
5
+ build/
6
+ dist/
7
+ *.egg-info/
8
+ .pytest_cache/
9
+ .ruff_cache/
10
+
11
+ # 本地运行状态(默认在宿主仓库根的 testlens.data/,这里兜底)
12
+ testlens.data/
13
+ .data/
14
+
15
+ # 库不锁依赖:lock 由使用方生成
16
+ uv.lock
@@ -0,0 +1,13 @@
1
+ # Changelog
2
+
3
+ ## 0.3.0 — 2026-11-07
4
+
5
+ 首个公开版本。
6
+
7
+ - 脚本清单、逐字变更检测(不依赖 git)
8
+ - 浏览器里勾选运行 pytest,按包隔离并发,实时输出
9
+ - 运行历史与结构化结果(用例统计 / 每脚本耗时 / 最慢用例 / 失败用例)
10
+ - 覆盖率页签:数据文件解析、未覆盖行、面板内全量统计
11
+ - 覆盖率四项自动推导(Makefile / `.coveragerc` / `pyproject.toml`),支持 `[coverage] warmup`
12
+ - 声明式 `testlens.toml` 与代码式 `testlens_config.py` 两种配置,`dev/serve/list/plan/page`
13
+ - 静态页导出(三件套或单文件),零第三方运行时依赖
testlens-0.3.0/LICENSE ADDED
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2026 yanjike
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,241 @@
1
+ Metadata-Version: 2.5
2
+ Name: testlens
3
+ Version: 0.3.0
4
+ Summary: 本地测试面板:脚本清单、变更检测、运行历史、代码覆盖率,零运行时依赖
5
+ Project-URL: Homepage, https://github.com/yanjike/testlens
6
+ Project-URL: Repository, https://github.com/yanjike/testlens
7
+ Project-URL: Issues, https://github.com/yanjike/testlens/issues
8
+ Project-URL: Changelog, https://github.com/yanjike/testlens/blob/main/CHANGELOG.md
9
+ Author: yanjike
10
+ License-Expression: MIT
11
+ License-File: LICENSE
12
+ Keywords: coverage,developer-tools,pytest,test-dashboard,testing
13
+ Classifier: Development Status :: 4 - Beta
14
+ Classifier: Environment :: Console
15
+ Classifier: Intended Audience :: Developers
16
+ Classifier: Operating System :: OS Independent
17
+ Classifier: Programming Language :: Python :: 3
18
+ Classifier: Programming Language :: Python :: 3 :: Only
19
+ Classifier: Programming Language :: Python :: 3.11
20
+ Classifier: Programming Language :: Python :: 3.12
21
+ Classifier: Programming Language :: Python :: 3.13
22
+ Classifier: Programming Language :: Python :: 3.14
23
+ Classifier: Topic :: Software Development :: Quality Assurance
24
+ Classifier: Topic :: Software Development :: Testing
25
+ Classifier: Typing :: Typed
26
+ Requires-Python: >=3.11
27
+ Description-Content-Type: text/markdown
28
+
29
+ # testlens
30
+
31
+ [![PyPI](https://img.shields.io/pypi/v/testlens.svg)](https://pypi.org/project/testlens/)
32
+ [![Python](https://img.shields.io/pypi/pyversions/testlens.svg)](https://pypi.org/project/testlens/)
33
+ [![License: MIT](https://img.shields.io/badge/license-MIT-blue.svg)](LICENSE)
34
+
35
+ 一个零第三方依赖的本地测试面板:列出测试脚本、检测脚本内容变化、在浏览器里勾选运行
36
+ pytest、查看运行历史与代码覆盖率,并标出哪些行没跑到。
37
+
38
+ 它只做工具,不是业务模块:不建表、不写接口、不改业务代码。变更检测基于源码缓存逐字
39
+ 对比,不依赖 git。可以独立使用,也可以作为插件嵌进任何 Python 项目。
40
+
41
+ ## 特性
42
+
43
+ - **脚本清单与变更检测**:按包分组列出 `test_*.py`,逐字对比缓存,标出「新增 / 已修改 /
44
+ 已删除」,不依赖 git。
45
+ - **浏览器里跑 pytest**:勾选脚本直接运行,实时输出;按包隔离并发,同一包内不会有两个
46
+ pytest 抢同一批 fixture。
47
+ - **运行历史**:每次运行的输出、耗时、用例统计落盘,重启面板后仍能翻回去看。
48
+ - **覆盖率页签**:读 coverage 的数据文件,给出总览与逐文件未覆盖行,支持只跑失败用例。
49
+ - **零运行时依赖**:只用标准库;`pytest` / `coverage` 是业务 dev 依赖,按需装。
50
+ - **只监听回环地址**:校验 Origin、包归属与脚本路径,命令用列表传参、不经 shell。
51
+
52
+ ## 安装
53
+
54
+ ```bash
55
+ # uv(推荐)
56
+ uv add --dev testlens
57
+
58
+ # pip
59
+ pip install testlens
60
+ ```
61
+
62
+ 要求 Python ≥ 3.11。
63
+
64
+ ## 快速开始
65
+
66
+ 在项目根放一份 `testlens.toml`:
67
+
68
+ ```toml
69
+ [server]
70
+ port = 8787
71
+
72
+ [[package]]
73
+ name = "api"
74
+ label = "API"
75
+ root = "." # 包根就是本目录:测试在 ./test
76
+ source_pkg = "app" # 想让覆盖率页签工作就写;不写 = 不统计覆盖率
77
+ ```
78
+
79
+ 起面板:
80
+
81
+ ```bash
82
+ testlens dev # 读当前目录的配置,自动开浏览器
83
+ # 或者
84
+ testlens serve
85
+ ```
86
+
87
+ 浏览器打开 `http://127.0.0.1:8787/`:左边是脚本清单,右边是运行面板。
88
+ 终端会打印「这次读的是哪份配置、几个包、多少个脚本」,包数量对不上时先看这一行。
89
+
90
+ monorepo 里一套配置看多个包:
91
+
92
+ ```toml
93
+ [[package]]
94
+ name = "api"
95
+ label = "API"
96
+ root = "services/api"
97
+ source_pkg = "app"
98
+
99
+ [[package]]
100
+ name = "shared"
101
+ label = "Shared"
102
+ root = "packages/shared"
103
+ source_pkg = "shared"
104
+ ```
105
+
106
+ > 多个包的 `root` 都相对同一个 `repo_root`(默认是配置文件所在目录),不能越界;
107
+ > 只有一个包时可以用 `root = "."`。
108
+
109
+ ## 覆盖率从哪来
110
+
111
+ 只要写了 `source_pkg`,其余四项会按项目现状推导,不用重复配置:
112
+
113
+ | 字段 | 推导规则 |
114
+ | --- | --- |
115
+ | `coverage_config` | 显式写的(或 `coverage_args` 里 `--cov-config=`)优先;否则取 Makefile 里跑 `--cov=<source_pkg>` 的命令用的 `--cov-config=`;再没有就按包名挑 `.coveragerc.<后缀>` |
116
+ | `coverage_data_file` | 该配置的 `[run] data_file`(相对配置文件所在目录)→ `pyproject.toml` 的 `[tool.coverage.run].data_file` → `.coverage.<包目录名>` |
117
+ | `coverage_args` | `--cov-config=<rc>` + `--cov=<source_pkg>` + `--cov-report=` |
118
+ | `coverage_hint` | Makefile 命中就是 `make <目标>`,否则给一条等价的 pytest 命令 |
119
+
120
+ 四个字段都可以显式覆盖;显式写了就以你写的为准。
121
+
122
+ 开启 `[coverage] warmup = true` 后,启动面板时会先配对:某个包的数据文件不存在就先跑
123
+ 一遍带 `--cov` 的测试生成它,已存在就跳过。`--warm-coverage` / `--no-warm-coverage`
124
+ 可临时覆盖。
125
+
126
+ ## 命令行
127
+
128
+ ```bash
129
+ testlens list [--package NAME] # 脚本清单与测试函数数
130
+ testlens changed [--package NAME] # 与缓存基线不一致的脚本
131
+ testlens plan [--package NAME] [--run] # 该跑哪些;--run 直接跑
132
+ testlens page [--output PATH] [--single] # 生成可双击的静态页
133
+ testlens serve [--port 8787] [--no-browser] [--warm-coverage]
134
+ testlens dev [TARGET] [--port 8787] [--no-browser] [--warm-coverage]
135
+ ```
136
+
137
+ - 默认读**当前目录**的 `testlens.toml` 或 `testlens_config.py`(toml 优先);
138
+ `--config PATH` 可以指向任意一份,写在子命令前后都行。
139
+ - `dev TARGET`:给目录就先读目录里的配置、没有才按目录推断(一个包、`root="."`、
140
+ 测试在 `<TARGET>/test`);给 `.py` 就直接读模块里的 `config`。
141
+ - 没有配置文件、推断出的 `test/` 又是空的时候,`dev` 会直接报错并列出附近找到的配置,
142
+ 不会起一个空面板。
143
+
144
+ ## 嵌入到代码里
145
+
146
+ ```python
147
+ from pathlib import Path
148
+
149
+ from testlens import PackageSpec, Toolkit, build_config
150
+
151
+ config = build_config(
152
+ packages=(
153
+ PackageSpec(
154
+ name="api",
155
+ label="API",
156
+ root="services/api",
157
+ source_pkg="app", # 覆盖率四项自动推导
158
+ ),
159
+ ),
160
+ repo_root=Path(__file__).parent,
161
+ port=9000,
162
+ )
163
+
164
+ toolkit = Toolkit(config)
165
+ toolkit.scan() # 清单 + 变更检测
166
+ toolkit.build_payload() # 页面数据
167
+ toolkit.serve() # 起面板,阻塞到 Ctrl+C
168
+ ```
169
+
170
+ `Toolkit` 一个实例持有一份配置与全部服务(扫描、历史、覆盖率、运行器),同一进程可以
171
+ 建多个实例,互不共享状态。
172
+
173
+ ## 配置参考
174
+
175
+ `testlens.toml` 支持这些段(全部可选,除 `[[package]]`):
176
+
177
+ ```toml
178
+ [page]
179
+ dir = "reports" # 静态页目录;省略 = testlens.data/page
180
+ name = "testlens.html"
181
+ single = false # true = 合成单个自包含 HTML
182
+
183
+ [paths]
184
+ data_dir = "testlens.data" # 运行数据目录
185
+ # template_path = "ui/mine.html" # 换模板(同目录放同名 .css / .js)
186
+
187
+ [server]
188
+ host = "127.0.0.1" # 只允许回环
189
+ port = 8787
190
+ open_browser = true
191
+ run_timeout = 900
192
+ max_output_lines = 20000
193
+ tail_lines = 800
194
+
195
+ [history]
196
+ max_entries = 50
197
+
198
+ [coverage]
199
+ warmup = false # 启动时补生成缺失的覆盖率数据
200
+
201
+ [commands]
202
+ pytest_cmd = "uv run pytest" # 不用 uv 就改成 python -m pytest
203
+ coverage_json_cmd = "uv run coverage json"
204
+
205
+ [[package]]
206
+ name = "api" # 唯一 id:URL 段、缓存文件名;只允许小写字母/数字/连字符
207
+ label = "API" # 界面 tab 显示名
208
+ root = "services/api" # 相对 repo_root
209
+ tests_dir = "test" # 测试目录
210
+ test_glob = "test_*.py" # 测试文件名
211
+ func_prefix = "test_" # 统计测试函数用的前缀
212
+ source_pkg = "app" # 覆盖率顶层模块;不写 = 不统计覆盖率
213
+ coverage_data_file = ".coverage.api"
214
+ coverage_config = ".coveragerc.api"
215
+ coverage_args = ["--cov-config=.coveragerc.api", "--cov=app", "--cov-report="]
216
+ coverage_hint = "make test-api"
217
+ ```
218
+
219
+ 覆盖率定位顺序:显式 `coverage_data_file` > 显式 `coverage_config` / args 里
220
+ `--cov-config=` 指向的配置 > Makefile 里 `--cov=<pkg>` 那行的 `--cov-config` >
221
+ 按包名挑的 `.coveragerc*` > `.coverage.<包目录名>`。生成数据时统一用 `COVERAGE_FILE`
222
+ 锁定到最终路径,保证「写哪儿就读哪儿」。
223
+
224
+ 运行数据默认写在 `<repo>/testlens.data/`(脚本基线、运行历史、覆盖率缓存、临时目录、
225
+ 静态页),整个目录可随时删掉重建。
226
+
227
+ ## 开发
228
+
229
+ ```bash
230
+ git clone https://github.com/yanjike/testlens
231
+ cd testlens
232
+ python -m pip install -e . pytest
233
+ python -m pytest -q
234
+ ```
235
+
236
+ 插件自身没有运行时依赖;前端是三个静态文件(`testlens/web/dashboard.{html,css,js}`),
237
+ 没有构建步骤,改完重启面板即生效。
238
+
239
+ ## License
240
+
241
+ [MIT](LICENSE)
@@ -0,0 +1,213 @@
1
+ # testlens
2
+
3
+ [![PyPI](https://img.shields.io/pypi/v/testlens.svg)](https://pypi.org/project/testlens/)
4
+ [![Python](https://img.shields.io/pypi/pyversions/testlens.svg)](https://pypi.org/project/testlens/)
5
+ [![License: MIT](https://img.shields.io/badge/license-MIT-blue.svg)](LICENSE)
6
+
7
+ 一个零第三方依赖的本地测试面板:列出测试脚本、检测脚本内容变化、在浏览器里勾选运行
8
+ pytest、查看运行历史与代码覆盖率,并标出哪些行没跑到。
9
+
10
+ 它只做工具,不是业务模块:不建表、不写接口、不改业务代码。变更检测基于源码缓存逐字
11
+ 对比,不依赖 git。可以独立使用,也可以作为插件嵌进任何 Python 项目。
12
+
13
+ ## 特性
14
+
15
+ - **脚本清单与变更检测**:按包分组列出 `test_*.py`,逐字对比缓存,标出「新增 / 已修改 /
16
+ 已删除」,不依赖 git。
17
+ - **浏览器里跑 pytest**:勾选脚本直接运行,实时输出;按包隔离并发,同一包内不会有两个
18
+ pytest 抢同一批 fixture。
19
+ - **运行历史**:每次运行的输出、耗时、用例统计落盘,重启面板后仍能翻回去看。
20
+ - **覆盖率页签**:读 coverage 的数据文件,给出总览与逐文件未覆盖行,支持只跑失败用例。
21
+ - **零运行时依赖**:只用标准库;`pytest` / `coverage` 是业务 dev 依赖,按需装。
22
+ - **只监听回环地址**:校验 Origin、包归属与脚本路径,命令用列表传参、不经 shell。
23
+
24
+ ## 安装
25
+
26
+ ```bash
27
+ # uv(推荐)
28
+ uv add --dev testlens
29
+
30
+ # pip
31
+ pip install testlens
32
+ ```
33
+
34
+ 要求 Python ≥ 3.11。
35
+
36
+ ## 快速开始
37
+
38
+ 在项目根放一份 `testlens.toml`:
39
+
40
+ ```toml
41
+ [server]
42
+ port = 8787
43
+
44
+ [[package]]
45
+ name = "api"
46
+ label = "API"
47
+ root = "." # 包根就是本目录:测试在 ./test
48
+ source_pkg = "app" # 想让覆盖率页签工作就写;不写 = 不统计覆盖率
49
+ ```
50
+
51
+ 起面板:
52
+
53
+ ```bash
54
+ testlens dev # 读当前目录的配置,自动开浏览器
55
+ # 或者
56
+ testlens serve
57
+ ```
58
+
59
+ 浏览器打开 `http://127.0.0.1:8787/`:左边是脚本清单,右边是运行面板。
60
+ 终端会打印「这次读的是哪份配置、几个包、多少个脚本」,包数量对不上时先看这一行。
61
+
62
+ monorepo 里一套配置看多个包:
63
+
64
+ ```toml
65
+ [[package]]
66
+ name = "api"
67
+ label = "API"
68
+ root = "services/api"
69
+ source_pkg = "app"
70
+
71
+ [[package]]
72
+ name = "shared"
73
+ label = "Shared"
74
+ root = "packages/shared"
75
+ source_pkg = "shared"
76
+ ```
77
+
78
+ > 多个包的 `root` 都相对同一个 `repo_root`(默认是配置文件所在目录),不能越界;
79
+ > 只有一个包时可以用 `root = "."`。
80
+
81
+ ## 覆盖率从哪来
82
+
83
+ 只要写了 `source_pkg`,其余四项会按项目现状推导,不用重复配置:
84
+
85
+ | 字段 | 推导规则 |
86
+ | --- | --- |
87
+ | `coverage_config` | 显式写的(或 `coverage_args` 里 `--cov-config=`)优先;否则取 Makefile 里跑 `--cov=<source_pkg>` 的命令用的 `--cov-config=`;再没有就按包名挑 `.coveragerc.<后缀>` |
88
+ | `coverage_data_file` | 该配置的 `[run] data_file`(相对配置文件所在目录)→ `pyproject.toml` 的 `[tool.coverage.run].data_file` → `.coverage.<包目录名>` |
89
+ | `coverage_args` | `--cov-config=<rc>` + `--cov=<source_pkg>` + `--cov-report=` |
90
+ | `coverage_hint` | Makefile 命中就是 `make <目标>`,否则给一条等价的 pytest 命令 |
91
+
92
+ 四个字段都可以显式覆盖;显式写了就以你写的为准。
93
+
94
+ 开启 `[coverage] warmup = true` 后,启动面板时会先配对:某个包的数据文件不存在就先跑
95
+ 一遍带 `--cov` 的测试生成它,已存在就跳过。`--warm-coverage` / `--no-warm-coverage`
96
+ 可临时覆盖。
97
+
98
+ ## 命令行
99
+
100
+ ```bash
101
+ testlens list [--package NAME] # 脚本清单与测试函数数
102
+ testlens changed [--package NAME] # 与缓存基线不一致的脚本
103
+ testlens plan [--package NAME] [--run] # 该跑哪些;--run 直接跑
104
+ testlens page [--output PATH] [--single] # 生成可双击的静态页
105
+ testlens serve [--port 8787] [--no-browser] [--warm-coverage]
106
+ testlens dev [TARGET] [--port 8787] [--no-browser] [--warm-coverage]
107
+ ```
108
+
109
+ - 默认读**当前目录**的 `testlens.toml` 或 `testlens_config.py`(toml 优先);
110
+ `--config PATH` 可以指向任意一份,写在子命令前后都行。
111
+ - `dev TARGET`:给目录就先读目录里的配置、没有才按目录推断(一个包、`root="."`、
112
+ 测试在 `<TARGET>/test`);给 `.py` 就直接读模块里的 `config`。
113
+ - 没有配置文件、推断出的 `test/` 又是空的时候,`dev` 会直接报错并列出附近找到的配置,
114
+ 不会起一个空面板。
115
+
116
+ ## 嵌入到代码里
117
+
118
+ ```python
119
+ from pathlib import Path
120
+
121
+ from testlens import PackageSpec, Toolkit, build_config
122
+
123
+ config = build_config(
124
+ packages=(
125
+ PackageSpec(
126
+ name="api",
127
+ label="API",
128
+ root="services/api",
129
+ source_pkg="app", # 覆盖率四项自动推导
130
+ ),
131
+ ),
132
+ repo_root=Path(__file__).parent,
133
+ port=9000,
134
+ )
135
+
136
+ toolkit = Toolkit(config)
137
+ toolkit.scan() # 清单 + 变更检测
138
+ toolkit.build_payload() # 页面数据
139
+ toolkit.serve() # 起面板,阻塞到 Ctrl+C
140
+ ```
141
+
142
+ `Toolkit` 一个实例持有一份配置与全部服务(扫描、历史、覆盖率、运行器),同一进程可以
143
+ 建多个实例,互不共享状态。
144
+
145
+ ## 配置参考
146
+
147
+ `testlens.toml` 支持这些段(全部可选,除 `[[package]]`):
148
+
149
+ ```toml
150
+ [page]
151
+ dir = "reports" # 静态页目录;省略 = testlens.data/page
152
+ name = "testlens.html"
153
+ single = false # true = 合成单个自包含 HTML
154
+
155
+ [paths]
156
+ data_dir = "testlens.data" # 运行数据目录
157
+ # template_path = "ui/mine.html" # 换模板(同目录放同名 .css / .js)
158
+
159
+ [server]
160
+ host = "127.0.0.1" # 只允许回环
161
+ port = 8787
162
+ open_browser = true
163
+ run_timeout = 900
164
+ max_output_lines = 20000
165
+ tail_lines = 800
166
+
167
+ [history]
168
+ max_entries = 50
169
+
170
+ [coverage]
171
+ warmup = false # 启动时补生成缺失的覆盖率数据
172
+
173
+ [commands]
174
+ pytest_cmd = "uv run pytest" # 不用 uv 就改成 python -m pytest
175
+ coverage_json_cmd = "uv run coverage json"
176
+
177
+ [[package]]
178
+ name = "api" # 唯一 id:URL 段、缓存文件名;只允许小写字母/数字/连字符
179
+ label = "API" # 界面 tab 显示名
180
+ root = "services/api" # 相对 repo_root
181
+ tests_dir = "test" # 测试目录
182
+ test_glob = "test_*.py" # 测试文件名
183
+ func_prefix = "test_" # 统计测试函数用的前缀
184
+ source_pkg = "app" # 覆盖率顶层模块;不写 = 不统计覆盖率
185
+ coverage_data_file = ".coverage.api"
186
+ coverage_config = ".coveragerc.api"
187
+ coverage_args = ["--cov-config=.coveragerc.api", "--cov=app", "--cov-report="]
188
+ coverage_hint = "make test-api"
189
+ ```
190
+
191
+ 覆盖率定位顺序:显式 `coverage_data_file` > 显式 `coverage_config` / args 里
192
+ `--cov-config=` 指向的配置 > Makefile 里 `--cov=<pkg>` 那行的 `--cov-config` >
193
+ 按包名挑的 `.coveragerc*` > `.coverage.<包目录名>`。生成数据时统一用 `COVERAGE_FILE`
194
+ 锁定到最终路径,保证「写哪儿就读哪儿」。
195
+
196
+ 运行数据默认写在 `<repo>/testlens.data/`(脚本基线、运行历史、覆盖率缓存、临时目录、
197
+ 静态页),整个目录可随时删掉重建。
198
+
199
+ ## 开发
200
+
201
+ ```bash
202
+ git clone https://github.com/yanjike/testlens
203
+ cd testlens
204
+ python -m pip install -e . pytest
205
+ python -m pytest -q
206
+ ```
207
+
208
+ 插件自身没有运行时依赖;前端是三个静态文件(`testlens/web/dashboard.{html,css,js}`),
209
+ 没有构建步骤,改完重启面板即生效。
210
+
211
+ ## License
212
+
213
+ [MIT](LICENSE)
@@ -0,0 +1,52 @@
1
+ """最小接入示例:在宿主项目里构造 testlens,而不是走 CLI。
2
+
3
+ 运行(从仓库根):
4
+
5
+ uv run python packages/testlens/examples/plugin_embed.py
6
+
7
+ 只演示装配,不会起 HTTP 服务——真起面板就是 `toolkit.serve()`。
8
+ """
9
+
10
+ from __future__ import annotations
11
+
12
+ import sys
13
+ from pathlib import Path
14
+
15
+ # 从源码目录直接跑示例时,让 `import testlens` 找到同级的包
16
+ sys.path.insert(0, str(Path(__file__).resolve().parents[1]))
17
+
18
+ from testlens import PackageSpec, Toolkit, build_config
19
+
20
+
21
+ def main() -> int:
22
+ # 仅示例:换成你自己的仓库根。真实插件里通常由调用方传入。
23
+ repo_root = Path(__file__).resolve().parents[3]
24
+
25
+ config = build_config(
26
+ packages=(
27
+ PackageSpec(
28
+ name="my-api", # 唯一 id:URL 段、缓存文件名
29
+ label="My API", # 界面 tab 显示名
30
+ root="apps/web-service", # 换成宿主真实的包目录
31
+ source_pkg="app", # 不填 = 该包不统计覆盖率
32
+ # 覆盖率其余四项按项目现状推导(Makefile / .coveragerc / pyproject);
33
+ # 想固定下来再显式写 coverage_data_file / coverage_config / coverage_args。
34
+ ),
35
+ ),
36
+ repo_root=repo_root,
37
+ port=9000, # 其余参数都有默认值
38
+ open_browser=False,
39
+ )
40
+
41
+ toolkit = Toolkit(config)
42
+ result = toolkit.scan()
43
+
44
+ print(f"包数量:{len(config.packages)}")
45
+ print(f"数据目录:{config.data_dir}")
46
+ print(f"扫描到 {result['total']} 个测试脚本,{result['changed']} 个已更新")
47
+ print("\n起面板只需一行:toolkit.serve()")
48
+ return 0
49
+
50
+
51
+ if __name__ == "__main__":
52
+ raise SystemExit(main())
@@ -0,0 +1,65 @@
1
+ [build-system]
2
+ requires = ["hatchling"]
3
+ build-backend = "hatchling.build"
4
+
5
+ [project]
6
+ name = "testlens"
7
+ dynamic = ["version"]
8
+ description = "本地测试面板:脚本清单、变更检测、运行历史、代码覆盖率,零运行时依赖"
9
+ readme = "README.md"
10
+ requires-python = ">=3.11"
11
+ license = "MIT"
12
+ license-files = ["LICENSE"]
13
+ authors = [{ name = "yanjike" }]
14
+ dependencies = []
15
+ keywords = ["testing", "pytest", "coverage", "developer-tools", "test-dashboard"]
16
+ classifiers = [
17
+ "Development Status :: 4 - Beta",
18
+ "Environment :: Console",
19
+ "Intended Audience :: Developers",
20
+ "Operating System :: OS Independent",
21
+ "Programming Language :: Python :: 3",
22
+ "Programming Language :: Python :: 3 :: Only",
23
+ "Programming Language :: Python :: 3.11",
24
+ "Programming Language :: Python :: 3.12",
25
+ "Programming Language :: Python :: 3.13",
26
+ "Programming Language :: Python :: 3.14",
27
+ "Topic :: Software Development :: Testing",
28
+ "Topic :: Software Development :: Quality Assurance",
29
+ "Typing :: Typed",
30
+ ]
31
+
32
+ [project.urls]
33
+ Homepage = "https://github.com/yanjike/testlens"
34
+ Repository = "https://github.com/yanjike/testlens"
35
+ Issues = "https://github.com/yanjike/testlens/issues"
36
+ Changelog = "https://github.com/yanjike/testlens/blob/main/CHANGELOG.md"
37
+
38
+ [project.scripts]
39
+ testlens = "testlens.cli:main"
40
+
41
+ # 版本只在 testlens/__init__.py 维护一处,打包时读出来
42
+ [tool.hatch.version]
43
+ path = "testlens/__init__.py"
44
+
45
+ # 只打包包目录;tests/、examples/、pyproject 不进 wheel。
46
+ # hatchling 默认会把包目录里的非源码文件(web/dashboard.{html,css,js}、py.typed)
47
+ # 一起带上,所以前端资源不需要额外 package-data 配置。
48
+ [tool.hatch.build.targets.wheel]
49
+ packages = ["testlens"]
50
+
51
+ [tool.hatch.build.targets.sdist]
52
+ include = [
53
+ "testlens/",
54
+ "tests/",
55
+ "examples/",
56
+ "README.md",
57
+ "CHANGELOG.md",
58
+ "LICENSE",
59
+ "pyproject.toml",
60
+ "run.py",
61
+ ]
62
+
63
+ [tool.pytest.ini_options]
64
+ testpaths = ["tests"]
65
+ addopts = "-q"
testlens-0.3.0/run.py ADDED
@@ -0,0 +1,22 @@
1
+ #!/usr/bin/env python3
2
+ """从仓库根运行本插件的兼容入口。
3
+
4
+ uv run python packages/testlens/run.py <command>
5
+
6
+ 等价于 `cd packages/testlens && python -m testlens <command>`。保留这一层是因为
7
+ Windows 的 make 用 cmd.exe 执行 recipe,`PYTHONPATH=... <cmd>` 那种 POSIX 前置
8
+ 赋值不生效;由脚本自己把发行根(本文件所在目录)放进 sys.path,跨平台行为一致。
9
+ """
10
+
11
+ from __future__ import annotations
12
+
13
+ import sys
14
+ from pathlib import Path
15
+
16
+ # 本文件在发行根 packages/testlens/,包目录是同一级的 testlens/
17
+ sys.path.insert(0, str(Path(__file__).resolve().parent))
18
+
19
+ from testlens.cli import main # noqa: E402
20
+
21
+ if __name__ == "__main__":
22
+ sys.exit(main())