测试Pipeline说明.md 6.44 KB

搜索引擎测试流水线指南

本文档是测试套件的权威入口,涵盖目录约定、运行方式、回归锚点矩阵、以及手动 联调脚本的分工。任何与这里不一致的历史文档(例如提到 tests/unit/scripts/start_test_environment.sh)都是过期信息,以本文为准。

1. 测试目录与分层

tests/
├── conftest.py              # 只做 sys.path 注入;不再维护全局 fixture
├── ci/                      # API/服务契约(FastAPI TestClient + 全 fake 依赖)
│   └── test_service_api_contracts.py
├── manual/                  # 需真实服务才能跑的联调脚本,pytest 默认不 collect
│   ├── test_build_docs_api.py
│   ├── test_cnclip_service.py
│   └── test_facet_api.py
└── test_*.py                # 子系统单测(全部自带 fake,无外部依赖)

关键约束(写在 pytest.ini 里,不要另起分支):

  • testpaths = testsnorecursedirs = tests/manual
  • --strict-markers:所有 marker 必须先在 pytest.ini::markers 登记;
  • 测试不得依赖真实 ES / DeepL / LLM 服务。需要外部依赖的脚本请放 tests/manual/

2. 运行方式

场景 命令 覆盖范围
CI 门禁(每次提交) ./scripts/run_ci_tests.sh tests/ci + contract marker + search ∧ regression
发版 / 大合并前 ./scripts/run_regression_tests.sh 所有 @pytest.mark.regression
子系统子集 SUBSYSTEM=search ./scripts/run_regression_tests.sh 指定子系统的 regression 锚点
全量(含非回归) python -m pytest tests/ -q 全部自动化用例
手动联调 python tests/manual/<script>.py 需提前起对应服务

3. Marker 体系与回归锚点矩阵

marker 定义见 pytest.ini。每个测试文件通过模块级 pytestmark 贴标,同时 属于 regression 的用例构成“回归锚点集合”。

子系统 marker 关键文件(锚点) 保护的行为
contract tests/ci/test_service_api_contracts.py Search / Indexer / Embedding / Reranker / Translation 的 HTTP 契约
search test_search_rerank_window.py, test_es_query_builder.py, test_es_query_builder_text_recall_languages.py Searcher 主路径、排序 / 召回、keywords 副 combined_fields、多语种
query test_query_parser_mixed_language.py, test_tokenization.py 中英混合解析、HanLP 分词、language detect
intent test_style_intent.py, test_product_title_exclusion.py, test_sku_intent_selector.py 风格意图、商品标题排除、SKU 选型
rerank test_rerank_client.py, test_rerank_query_text.py, test_rerank_provider_topn.py, test_reranker_server_topn.py, test_reranker_dashscope_backend.py, test_reranker_qwen3_gguf_backend.py 粗排 / 精排 / topN / 后端切换
embedding test_embedding_pipeline.py, test_embedding_service_limits.py, test_embedding_service_priority.py, test_cache_keys.py 文本/图像向量客户端、inflight limiter、优先级队列、缓存 key
translation test_translation_deepl_backend.py, test_translation_llm_backend.py, test_translation_local_backends.py, test_translator_failure_semantics.py DeepL / LLM / 本地回退、失败语义
indexer test_product_enrich_partial_mode.py, test_process_products_batching.py, test_llm_enrichment_batch_fill.py LLM Partial Mode、batch 拆分、空结果补位
suggestion test_suggestions.py 建议索引构建
eval test_eval_metrics.py(regression) + test_search_evaluation_datasets.py / test_eval_framework_clients.py(非 regression) NDCG / ERR 指标、数据集加载、评估客户端

任何新写的子系统单测,都应该在顶部加 pytestmark = [pytest.mark.<子系统>, pytest.mark.regression]。 不贴 regression 的测试默认不会run_regression_tests.sh 选中,请谨慎决定。

4. 当前覆盖缺口(跟踪中)

以下场景目前没有被 regression 锚点覆盖,优先级从高到低:

  1. api/routes/search.py 的请求参数映射QueryParser.parse(...) 透传是否完整(目前只有 tests/ci 间接覆盖)。
  2. indexer/document_transformer.py 的端到端转换:从 MySQL 行到 ES doc 的 snapshot 对比。
  3. config/loader.py 加载多租户配置:含继承 / override 的合并规则。
  4. search/searcher.py::_build_function_score:function_score 装配。
  5. Facet 聚合 / disjunctive 过滤
  6. 图像搜索主路径search/image_searcher.py)。

补齐时记得同步贴 regression + 对应子系统 marker,并在本表删除条目。

5. 手动联调:索引文档构建流水线

除自动化测试外,联调/问题排查时建议走一遍“MySQL → ES doc”链路,确保字段与 mapping 与查询逻辑对齐。

cd /home/tw/saas-search
./scripts/stop.sh                  # 停掉已有进程(可选)
./scripts/start_indexer.sh         # 启动 indexer 服务,默认端口 6004

curl -X POST "http://127.0.0.1:6004/indexer/build-docs-from-db" \
  -H "Content-Type: application/json" \
  -d '{ "tenant_id": "170", "spu_ids": ["223167"] }'

返回中 docs[0] 即当前代码构造的 ES doc(与 mappings/search_products.json 对齐)。 与真实 ES 数据对比的查询参考 docs/常用查询 - ES.md;若字段不一致,按以下路径定位:

  • indexer/document_transformer.py — 文档构造逻辑
  • indexer/incremental_service.py — 增量查库逻辑
  • logs/indexer.log — 索引日志

6. 编写测试的约束(与 开发原则 对齐)

  • fail fast:测试输入不合法时应直接抛错,不用 if ... return;不要用 try/except 吃掉异常再 assert not exception
  • 不做兼容双轨:用例对准当前实现,不为历史行为保留“旧 assert”。若确有外部兼容性(例如 API 上标注 Deprecated 的字段),在 tests/ci 里单独写契约用例并注明 Deprecated。
  • 外部依赖全 fake:凡是依赖 HTTP / Redis / ES / LLM 的测试必须注入 fake stub,否则归入 tests/manual/
  • 一处真相:共享 fixture 如果超过 2 个文件使用,放 tests/conftest.py;只给 1 个文件用就放在该文件内。避免再次出现全库无人引用的 dead fixture。