diff --git a/README.md b/README.md index 6284551..5987689 100644 --- a/README.md +++ b/README.md @@ -213,6 +213,92 @@ python3 scripts/selfcheck_m5a.py > `SDLC_WS_ROOT`(配合可选 `SDLC_PROJECT` 覆盖项目名,默认 `pbls`)时生效;定位不到工作空间 > 根时这三项返回 `SKIPPED`(不计 FAIL),避免别的宿主挂载本模块时把「路径不存在」误判成质量回归。 +## Running tests(测试前置依赖与执行方式,发现项 DISC.2) + +> 本节是 `scripts/setup_test_env.sh` 报错文案所指向的排障入口,**必须存在**(QC #12)。 +> 同时回答两个问题:① pytest 依赖声明在哪里;② 不手工注入 `PYTHONPATH` 怎么把测试跑绿。 + +### 为什么需要这一节(DISC.2 根因) + +测试机宿主 `python3` **没有** pytest,`python3 -m pytest` 直接 `No module named pytest`; +而宿主 user site(`/d/pipeline/.local/lib/python3.10/site-packages`)在本机是**只读文件系统**, +`pip install --user pytest` 会以 `[Errno 30] Read-only file system` 失败。此前靠临时注入 +`PYTHONPATH=/.pylibs`(pytest 9.1.1)跑通,属一次性手段——换机器/换角色即失效, +test 角色据此会误判「用例失败/环境不可用」。**正确落点是显式声明 + 一条命令引导到 venv。** + +### 依赖声明在哪(两份,版本区间同源 `>=7.0,<10`) + +| 文件 | 作用域 | 谁装它 | +|---|---|---| +| `requirements-dev.txt`(本模块) | 模块级:不安装本包也能跑离线单测的最短路径(CI / 测试机宿主) | `scripts/setup_test_env.sh` | +| `pyproject.toml` → `[project.optional-dependencies].test` | 包级:`pip install -e .[test]` | 已装本包的场景 | +| `apps/pbls/requirements-test.txt` | 应用级:随 `build.sh` 第 7b 步进 venv | `apps/pbls/build.sh` | + +三者版本区间一致(下限 7.0 要 `pytest.mark.skipif` 与 `monkeypatch(raising=)` 稳定语义; +上限 <10 不自动跟随主版本跳变)。pytest 属**测试**依赖,绝不写进 `[project].dependencies`。 + +### 三条真实可执行路径 + +```bash +# A) 一条命令引导 + 跑(推荐,无 pytest 时自动建 .venv-test 并装 requirements-dev.txt) +cd modules/pbl_evidence && bash scripts/run_tests.sh +# 等价显式写法:bash scripts/run_tests.sh tests/test_m5a_idempotency.py -v +# 同时落盘日志:bash scripts/run_tests.sh --log ../../projects/pbls/docs/02-develop/m5a-disc2-pytest-run.log + +# B) 只引导环境,之后自己用 venv 解释器 +bash scripts/setup_test_env.sh +bash scripts/activate_test_env.sh # 打印 source 命令;或直接: +.venv-test/bin/python -m pytest tests/test_m5a_idempotency.py -v + +# C) 走应用构建链(venv 内即有 pytest,由 build.sh 第 7b 步安装) +bash apps/pbls/build.sh +source apps/pbls/venv/bin/activate && python3 -m pytest modules/pbl_evidence/tests/test_m5a_idempotency.py -v +``` + +`run_tests.sh` 的解释器解析顺序:`PBL_EVIDENCE_TEST_PYTHON` → 宿主 `python3`(能 import pytest 才用) +→ `${PBL_EVIDENCE_TEST_VENV}/bin/python`(默认 `.venv-test`);三者都没有 pytest 时自动调用 +`setup_test_env.sh` 引导,引导失败 exit 3(不会伪装成测试失败)。 + +### 环境变量 + +| 变量 | 含义 | 缺省 | +|---|---|---| +| `PBL_EVIDENCE_TEST_VENV` | 测试 venv 落点 | `/.venv-test`(已在 `.gitignore`) | +| `PBL_EVIDENCE_TEST_PYTHON` | 强制指定解释器 | 空(按上面顺序解析) | +| `PBL_EVIDENCE_INSTALL_PKG` | 置 `1` 时额外 `pip install -e .` | `0` | +| `PBL_EVIDENCE_TEST_DB` | 真实库开关,见下节 | 空(DB 用例 SKIPPED) | +| `PBL_EVIDENCE_TEST_TENANT` | DB 用例使用的租户 | `m5a-idem-test` | +| `PIP_INDEX_URL` | 内网 PyPI 源 | 空(用 pip 自身配置) | + +### `test_double_collect_is_idempotent_with_db` 的开关用法 + +该用例需要**真实 MariaDB**(双次采集 → 断言第二次不产生新行),未设开关时按设计 +`SKIPPED`——这是预期结果,不是失败,也不是环境不可用。可执行路径: + +```bash +# 1) 库与表就位(pbl_artifact / pbl_evidence 由 apps/pbls/build.sh 第 10 步建表) +# 2) 带开关运行:DSN 形如 mysql://user:pwd@host:3306/pbls +PBL_EVIDENCE_TEST_DB='mysql://:@:3306/' \ +PBL_EVIDENCE_TEST_TENANT=m5a-idem-test \ + bash scripts/run_tests.sh tests/test_m5a_idempotency.py -v +# 期望:9 passed, 0 skipped +``` + +不带开关时的期望结果(本机实测,日志见 +`projects/pbls/docs/02-develop/m5a-disc2-pytest-run.log`):`8 passed, 1 skipped`,RC=0。 + +### 排障 + +| 现象 | 原因 | 处理 | +|---|---|---| +| `安装 pytest 失败:检查内网 PyPI 源可达性` | 内网源不可达 / 无代理 | 先 `curl -I ${PIP_INDEX_URL:-https://pypi.tuna.tsinghua.edu.cn/simple}`;离线环境用 `PIP_NO_INDEX=1 PIP_FIND_LINKS=/path/to/wheels bash scripts/setup_test_env.sh`(需预置 pytest+pluggy+iniconfig+packaging+tomli+exceptiongroup+pygments 的 wheel) | +| `pip install --user` 报 `Read-only file system` | 宿主 user site 只读(本机即如此) | **不要**再试 `--user`,走 venv(路径 A/B) | +| 报 venv 落点未被 `.gitignore` 忽略 | 仓库卫生门禁(QC #1 曾误入库 1171 个文件) | 在 `.gitignore` 补 `.venv-test/` 并 `git rm -r --cached .venv-test`,再重跑 | +| `No module named pytest` 但已跑过 build.sh | 构建时设了 `PBL_SKIP_TEST_DEPS=1` | 去掉该开关重跑 `build.sh`,或走路径 A | +| 用例里 DB 相关断言被跳过 | 未设 `PBL_EVIDENCE_TEST_DB` | 见上节开关用法 | + +**禁止**为凑通过修改测试断言逻辑(DISC.2 的验收前提)。 + ## Integration(宿主挂载方式) 模块只依赖:基础包(sqlor、ahserver ServerEnv、appPublic 工具)、`pbl_common`(租户上下文/