Compare commits
3 Commits
cbe48c8ee7
...
b1f64c4bac
| Author | SHA1 | Date | |
|---|---|---|---|
| b1f64c4bac | |||
| ed47754b46 | |||
| fbee8a751e |
@@ -191,8 +191,6 @@ pytest --cov=. --cov-report=html
|
||||
- `tests/unit/test_parsers.py` – 解析器单元测试
|
||||
- `tests/integration/test_database.py` – 数据库集成测试
|
||||
|
||||
<<<<<<< HEAD
|
||||
=======
|
||||
#### 3.3.1 测试模式(ONLINE / OFFLINE)
|
||||
|
||||
- `TEST_MODE=ONLINE`(默认)时,测试会模拟实时 API,完整执行 E/T/L。
|
||||
@@ -221,7 +219,6 @@ python scripts/run_tests.py --preset offline_realdb
|
||||
python scripts/run_tests.py --list-presets # 查看或自定义 scripts/test_presets.py
|
||||
```
|
||||
|
||||
>>>>>>> main
|
||||
---
|
||||
|
||||
## 4. 项目结构与文件说明
|
||||
@@ -308,11 +305,8 @@ etl_billiards/
|
||||
│ │ ├── __init__.py
|
||||
│ │ ├── test_config.py
|
||||
│ │ └── test_parsers.py
|
||||
<<<<<<< HEAD
|
||||
=======
|
||||
│ ├── testdata_json/ # 清洗入库用的测试Json文件
|
||||
│ │ └── XX.json
|
||||
>>>>>>> main
|
||||
│ └── integration/ # 集成测试
|
||||
│ ├── __init__.py
|
||||
│ └── test_database.py
|
||||
|
||||
@@ -1,12 +1,12 @@
|
||||
# -*- coding: utf-8 -*-
|
||||
"""配置默认值"""
|
||||
"""配置默认值定义"""
|
||||
|
||||
DEFAULTS = {
|
||||
"app": {
|
||||
"timezone": "Asia/Taipei",
|
||||
"store_id": "",
|
||||
"schema_oltp": "billiards",
|
||||
"schema_etl": "etl_admin",
|
||||
"schema_etl": "etl_admin",
|
||||
},
|
||||
"db": {
|
||||
"dsn": "",
|
||||
@@ -21,7 +21,7 @@ DEFAULTS = {
|
||||
"timezone": "Asia/Taipei",
|
||||
"statement_timeout_ms": 30000,
|
||||
"lock_timeout_ms": 5000,
|
||||
"idle_in_tx_timeout_ms": 600000
|
||||
"idle_in_tx_timeout_ms": 600000,
|
||||
},
|
||||
},
|
||||
"api": {
|
||||
@@ -37,9 +37,19 @@ DEFAULTS = {
|
||||
},
|
||||
"run": {
|
||||
"tasks": [
|
||||
"PRODUCTS", "TABLES", "MEMBERS", "ASSISTANTS", "PACKAGES_DEF",
|
||||
"ORDERS", "PAYMENTS", "REFUNDS", "COUPON_USAGE", "INVENTORY_CHANGE",
|
||||
"TOPUPS", "TABLE_DISCOUNT", "ASSISTANT_ABOLISH",
|
||||
"PRODUCTS",
|
||||
"TABLES",
|
||||
"MEMBERS",
|
||||
"ASSISTANTS",
|
||||
"PACKAGES_DEF",
|
||||
"ORDERS",
|
||||
"PAYMENTS",
|
||||
"REFUNDS",
|
||||
"COUPON_USAGE",
|
||||
"INVENTORY_CHANGE",
|
||||
"TOPUPS",
|
||||
"TABLE_DISCOUNT",
|
||||
"ASSISTANT_ABOLISH",
|
||||
"LEDGER",
|
||||
],
|
||||
"window_minutes": {
|
||||
@@ -49,13 +59,13 @@ DEFAULTS = {
|
||||
"overlap_seconds": 120,
|
||||
"idle_window": {
|
||||
"start": "04:00",
|
||||
"end": "16:00",
|
||||
"end": "16:00",
|
||||
},
|
||||
"allow_empty_result_advance": True,
|
||||
},
|
||||
"io": {
|
||||
"export_root": r"D:\LLZQ\DB\export",
|
||||
"log_root": r"D:\LLZQ\DB\logs",
|
||||
"log_root": r"D:\LLZQ\DB\logs",
|
||||
"manifest_name": "manifest.json",
|
||||
"ingest_report_name": "ingest_report.json",
|
||||
"write_pretty_json": False,
|
||||
@@ -76,31 +86,28 @@ DEFAULTS = {
|
||||
"redact_keys": ["token", "password", "Authorization"],
|
||||
"echo_token_in_logs": False,
|
||||
},
|
||||
<<<<<<< HEAD
|
||||
=======
|
||||
"testing": {
|
||||
# ONLINE: 正常实时ETL;OFFLINE: 读取归档JSON做T/L
|
||||
"mode": "OFFLINE",
|
||||
# 离线归档JSON所在目录
|
||||
# ONLINE: 正常实时 ETL;OFFLINE: 读取归档 JSON 做 T/L
|
||||
"mode": "ONLINE",
|
||||
# 离线归档 JSON 所在目录(测试/离线回放使用)
|
||||
"json_archive_dir": "",
|
||||
# 测试运行时用于生成/复制临时JSON的目录
|
||||
# 测试运行时用于生成/复制临时 JSON 的目录
|
||||
"temp_json_dir": "",
|
||||
},
|
||||
>>>>>>> main
|
||||
}
|
||||
|
||||
# 任务代码常量
|
||||
TASK_ORDERS = "ORDERS"
|
||||
TASK_PAYMENTS = "PAYMENTS"
|
||||
TASK_REFUNDS = "REFUNDS"
|
||||
TASK_INVENTORY_CHANGE = "INVENTORY_CHANGE"
|
||||
TASK_COUPON_USAGE = "COUPON_USAGE"
|
||||
TASK_MEMBERS = "MEMBERS"
|
||||
TASK_ASSISTANTS = "ASSISTANTS"
|
||||
TASK_PRODUCTS = "PRODUCTS"
|
||||
TASK_TABLES = "TABLES"
|
||||
TASK_PACKAGES_DEF = "PACKAGES_DEF"
|
||||
TASK_TOPUPS = "TOPUPS"
|
||||
TASK_TABLE_DISCOUNT = "TABLE_DISCOUNT"
|
||||
TASK_ORDERS = "ORDERS"
|
||||
TASK_PAYMENTS = "PAYMENTS"
|
||||
TASK_REFUNDS = "REFUNDS"
|
||||
TASK_INVENTORY_CHANGE = "INVENTORY_CHANGE"
|
||||
TASK_COUPON_USAGE = "COUPON_USAGE"
|
||||
TASK_MEMBERS = "MEMBERS"
|
||||
TASK_ASSISTANTS = "ASSISTANTS"
|
||||
TASK_PRODUCTS = "PRODUCTS"
|
||||
TASK_TABLES = "TABLES"
|
||||
TASK_PACKAGES_DEF = "PACKAGES_DEF"
|
||||
TASK_TOPUPS = "TOPUPS"
|
||||
TASK_TABLE_DISCOUNT = "TABLE_DISCOUNT"
|
||||
TASK_ASSISTANT_ABOLISH = "ASSISTANT_ABOLISH"
|
||||
TASK_LEDGER = "LEDGER"
|
||||
TASK_LEDGER = "LEDGER"
|
||||
|
||||
@@ -24,12 +24,9 @@ ENV_MAP = {
|
||||
"OVERLAP_SECONDS": ("run.overlap_seconds",),
|
||||
"WINDOW_BUSY_MIN": ("run.window_minutes.default_busy",),
|
||||
"WINDOW_IDLE_MIN": ("run.window_minutes.default_idle",),
|
||||
<<<<<<< HEAD
|
||||
=======
|
||||
"TEST_MODE": ("testing.mode",),
|
||||
"TEST_JSON_ARCHIVE_DIR": ("testing.json_archive_dir",),
|
||||
"TEST_JSON_TEMP_DIR": ("testing.temp_json_dir",),
|
||||
>>>>>>> main
|
||||
}
|
||||
|
||||
def _deep_set(d, dotted_keys, value):
|
||||
|
||||
@@ -3,8 +3,6 @@
|
||||
from tasks.orders_task import OrdersTask
|
||||
from tasks.payments_task import PaymentsTask
|
||||
from tasks.members_task import MembersTask
|
||||
<<<<<<< HEAD
|
||||
=======
|
||||
from tasks.products_task import ProductsTask
|
||||
from tasks.tables_task import TablesTask
|
||||
from tasks.assistants_task import AssistantsTask
|
||||
@@ -16,7 +14,6 @@ from tasks.topups_task import TopupsTask
|
||||
from tasks.table_discount_task import TableDiscountTask
|
||||
from tasks.assistant_abolish_task import AssistantAbolishTask
|
||||
from tasks.ledger_task import LedgerTask
|
||||
>>>>>>> main
|
||||
|
||||
class TaskRegistry:
|
||||
"""任务注册和工厂"""
|
||||
@@ -44,12 +41,6 @@ class TaskRegistry:
|
||||
|
||||
# 默认注册表
|
||||
default_registry = TaskRegistry()
|
||||
<<<<<<< HEAD
|
||||
default_registry.register("ORDERS", OrdersTask)
|
||||
default_registry.register("PAYMENTS", PaymentsTask)
|
||||
default_registry.register("MEMBERS", MembersTask)
|
||||
# 可以继续注册其他任务...
|
||||
=======
|
||||
default_registry.register("PRODUCTS", ProductsTask)
|
||||
default_registry.register("TABLES", TablesTask)
|
||||
default_registry.register("MEMBERS", MembersTask)
|
||||
@@ -64,4 +55,3 @@ default_registry.register("TOPUPS", TopupsTask)
|
||||
default_registry.register("TABLE_DISCOUNT", TableDiscountTask)
|
||||
default_registry.register("ASSISTANT_ABOLISH", AssistantAbolishTask)
|
||||
default_registry.register("LEDGER", LedgerTask)
|
||||
>>>>>>> main
|
||||
|
||||
@@ -21,6 +21,10 @@ import pytest
|
||||
|
||||
PROJECT_ROOT = os.path.abspath(os.path.join(os.path.dirname(__file__), ".."))
|
||||
|
||||
# 确保项目根目录在 sys.path,便于 tests 内部 import config / tasks 等模块
|
||||
if PROJECT_ROOT not in sys.path:
|
||||
sys.path.insert(0, PROJECT_ROOT)
|
||||
|
||||
SUITE_MAP: Dict[str, str] = {
|
||||
"online": "tests/unit/test_etl_tasks_online.py",
|
||||
"offline": "tests/unit/test_etl_tasks_offline.py",
|
||||
|
||||
@@ -1,53 +1,48 @@
|
||||
# -*- coding: utf-8 -*-
|
||||
"""测试命令“仓库”,集中维护 run_tests.py 的预置组合。
|
||||
"""测试命令仓库:集中维护 run_tests.py 的常用组合,并支持一键执行。
|
||||
|
||||
支持的参数键(可在 PRESETS 中自由组合):
|
||||
参数键说明(可在 PRESETS 中任意叠加):
|
||||
|
||||
1. suite
|
||||
- 类型:列表
|
||||
- 作用:引用 run_tests 中的预置套件,值可为 online / offline / integration 等。
|
||||
- 用法:["online"] 仅跑在线模式;["online","offline"] 同时跑两套;["integration"] 跑数据库集成测试。
|
||||
类型:列表;值:["online"], ["offline"], ["integration"] 等。
|
||||
含义:引用 run_tests 内置测试套件。online=在线模式;offline=离线模式;integration=数据库集成测试。
|
||||
用法:["online","offline"] 表示一次执行两套;["integration"] 仅跑数据库相关用例。
|
||||
|
||||
2. tests
|
||||
- 类型:列表
|
||||
- 作用:传入任意 pytest 目标路径,适合补充临时/自定义测试文件。
|
||||
- 用法:["tests/unit/test_config.py","tests/unit/test_parsers.py"]。
|
||||
类型:列表;示例:["tests/unit/test_config.py"]。
|
||||
含义:自定义的 pytest 目标路径,适合补充临时/个别测试。
|
||||
|
||||
3. mode
|
||||
- 类型:字符串
|
||||
- 取值:ONLINE 或 OFFLINE。
|
||||
- 作用:覆盖 TEST_MODE;ONLINE 走 API 全流程,OFFLINE 读取 JSON 归档执行 T+L。
|
||||
类型:字符串;取值:"ONLINE" 或 "OFFLINE"。
|
||||
含义:覆盖 TEST_MODE;ONLINE 走 API 全流程,OFFLINE 读取 JSON 归档执行 Transform + Load。
|
||||
|
||||
4. db_dsn
|
||||
- 类型:字符串
|
||||
- 作用:设置 TEST_DB_DSN,指定真实 PostgreSQL 连接;缺省时测试引擎使用伪 DB,仅记录写入不触库。
|
||||
- 示例:postgresql://user:pwd@localhost:5432/testdb。
|
||||
类型:字符串;示例:postgresql://user:pwd@host:5432/testdb。
|
||||
含义:设置 TEST_DB_DSN,使用真实 PostgreSQL 连接;不设置则使用伪 DB(仅记录操作,不落库)。
|
||||
|
||||
5. json_archive / json_temp
|
||||
- 类型:字符串
|
||||
- 作用:离线模式的 JSON 归档目录 / 临时输出目录。
|
||||
- 说明:不设置时沿用 .env 或默认值;仅在 OFFLINE 模式需要关注。
|
||||
类型:字符串;示例:"tests/testdata_json"、"C:/tmp/json"。
|
||||
含义:离线模式所需的归档输入目录 / 临时输出目录。未设置时沿用 .env 或默认配置。
|
||||
|
||||
6. keyword
|
||||
- 类型:字符串
|
||||
- 作用:等价 pytest -k,用于筛选测试名/节点。
|
||||
- 示例:"ORDERS" 可只运行包含该关键字的测试函数。
|
||||
类型:字符串;示例:"ORDERS"。
|
||||
含义:等价 pytest -k,可筛选测试名/节点,只运行包含该关键字的用例。
|
||||
|
||||
7. pytest_args
|
||||
- 类型:字符串
|
||||
- 作用:附加 pytest 命令行参数。
|
||||
- 示例:"-vv --maxfail=1 --disable-warnings"。
|
||||
类型:字符串;示例:"-vv --maxfail=1"。
|
||||
含义:追加 pytest 命令行参数,用于控制日志、失败策略等。
|
||||
|
||||
8. env
|
||||
- 类型:列表
|
||||
- 作用:追加环境变量,形如 ["STORE_ID=123","API_TOKEN=xxx"],会在 run_tests 内透传给 os.environ。
|
||||
类型:列表;示例:["STORE_ID=123","API_TOKEN=xxx"]。
|
||||
含义:额外的环境变量,在调用 run_tests 前注入到 os.environ。
|
||||
|
||||
9. preset_meta
|
||||
- 类型:字符串
|
||||
- 作用:纯注释信息,便于描述该预置组合的用途,不会传递给 run_tests。
|
||||
类型:字符串;仅用于描述场景,不会传给 run_tests(纯注释)。
|
||||
|
||||
运行方式建议直接 F5(或 `python scripts/test_presets.py`),脚本将读取 AUTO_RUN_PRESETS 中的配置依次执行。
|
||||
如需临时指定其它预置,可传入 `--preset xxx`;`--list` 用于查看所有参数说明和预置详情。
|
||||
使用方式:
|
||||
- 直接 F5 或 `python scripts/test_presets.py`:读取 AUTO_RUN_PRESETS 的预置并顺序执行。
|
||||
- `python scripts/test_presets.py --preset offline_realdb`:临时指定要运行的组合。
|
||||
- `python scripts/test_presets.py --list`:查看参数说明及所有预置详情。
|
||||
"""
|
||||
|
||||
from __future__ import annotations
|
||||
@@ -60,60 +55,46 @@ from typing import List
|
||||
|
||||
RUN_TESTS_SCRIPT = os.path.join(os.path.dirname(__file__), "run_tests.py")
|
||||
|
||||
AUTO_RUN_PRESETS = ["online_orders"]
|
||||
|
||||
# PRESETS = {
|
||||
# "online_orders": {
|
||||
# "suite": ["online"],
|
||||
# "mode": "ONLINE",
|
||||
# "keyword": "ORDERS",
|
||||
# "pytest_args": "-vv",
|
||||
# "preset_meta": "在线模式,仅跑订单任务,输出更详细日志",
|
||||
# },
|
||||
# "offline_realdb": {
|
||||
# "suite": ["offline"],
|
||||
# "mode": "OFFLINE",
|
||||
# "db_dsn": "postgresql://user:pwd@localhost:5432/testdb",
|
||||
# "json_archive": "tests/testdata_json",
|
||||
# "preset_meta": "离线模式 + 真实测试库,用预置 JSON 回放全量任务",
|
||||
# },
|
||||
# "integration_db": {
|
||||
# "suite": ["integration"],
|
||||
# "db_dsn": "postgresql://user:pwd@localhost:5432/testdb",
|
||||
# "preset_meta": "仅跑数据库连接/操作相关的集成测试",
|
||||
# },
|
||||
# }
|
||||
# 默认自动运行的预置(可自定义顺序)
|
||||
AUTO_RUN_PRESETS = ["offline_realdb"]
|
||||
|
||||
PRESETS = {
|
||||
"online_orders": {
|
||||
"suite": ["online"],
|
||||
"mode": "ONLINE",
|
||||
"keyword": "ORDERS",
|
||||
"pytest_args": "-vv",
|
||||
"preset_meta": "在线模式,仅跑订单任务并输出详细日志",
|
||||
},
|
||||
"offline_realdb": {
|
||||
"suite": ["offline"],
|
||||
"mode": "OFFLINE",
|
||||
"db_dsn": "postgresql://local-Python:Neo-local-1991125@100.64.0.4:5432/LLZQ-test",
|
||||
"json_archive": "tests/testdata_json",
|
||||
"preset_meta": "离线模式 + 真实测试库,用预置 JSON 回放全量任务",
|
||||
"keyword": "ORDERS",
|
||||
"preset_meta": "离线模式 + 真实测试库,用预置 JSON 回放并写入测试库",
|
||||
},
|
||||
}
|
||||
|
||||
|
||||
|
||||
def print_parameter_help():
|
||||
print("可用参数键说明:")
|
||||
print(" suite -> 预置测试套件列表,如 ['online','offline']")
|
||||
print(" tests -> 自定义测试文件路径列表")
|
||||
print(" mode -> TEST_MODE,ONLINE / OFFLINE")
|
||||
print(" db_dsn -> TEST_DB_DSN,连接真实 PostgreSQL")
|
||||
print(" json_archive -> TEST_JSON_ARCHIVE_DIR,离线 JSON 目录")
|
||||
print(" json_temp -> TEST_JSON_TEMP_DIR,离线临时目录")
|
||||
print(" keyword -> pytest -k 过滤关键字")
|
||||
print(" pytest_args -> 额外 pytest 参数(单个字符串)")
|
||||
print(" env -> 附加环境变量,形如 ['KEY=VALUE']")
|
||||
print(" preset_meta -> 注释说明,不会传给 run_tests")
|
||||
def print_parameter_help() -> None:
|
||||
print("=== 参数键说明 ===")
|
||||
print("suite : 预置套件列表,如 ['online','offline']")
|
||||
print("tests : 自定义 pytest 路径列表")
|
||||
print("mode : TEST_MODE(ONLINE/ OFFLINE)")
|
||||
print("db_dsn : TEST_DB_DSN,连接真实 PostgreSQL")
|
||||
print("json_archive : TEST_JSON_ARCHIVE_DIR,离线模式输入目录")
|
||||
print("json_temp : TEST_JSON_TEMP_DIR,离线模式临时目录")
|
||||
print("keyword : pytest -k 过滤关键字")
|
||||
print("pytest_args : 额外 pytest 参数(字符串)")
|
||||
print("env : 附加环境变量,例如 ['KEY=VALUE']")
|
||||
print("preset_meta : 仅用于注释说明")
|
||||
print()
|
||||
|
||||
|
||||
def print_presets():
|
||||
def print_presets() -> None:
|
||||
if not PRESETS:
|
||||
print("当前没有定义任何预置命令,可自行在 PRESETS 中添加。")
|
||||
print("当前未定义任何预置,请在 PRESETS 中添加。")
|
||||
return
|
||||
for idx, (name, payload) in enumerate(PRESETS.items(), start=1):
|
||||
comment = payload.get("preset_meta", "")
|
||||
@@ -127,13 +108,32 @@ def print_presets():
|
||||
print()
|
||||
|
||||
|
||||
def run_presets(preset_names: List[str], dry_run: bool):
|
||||
cmds = []
|
||||
def resolve_targets(requested: List[str] | None) -> List[str]:
|
||||
if not PRESETS:
|
||||
raise SystemExit("Pre-sets 为空,请先在 PRESETS 中定义测试组合。")
|
||||
|
||||
def valid(names: List[str]) -> List[str]:
|
||||
return [name for name in names if name in PRESETS]
|
||||
|
||||
if requested:
|
||||
candidates = valid(requested)
|
||||
missing = [name for name in requested if name not in PRESETS]
|
||||
if missing:
|
||||
print(f"警告:忽略未定义的预置 {missing}")
|
||||
if candidates:
|
||||
return candidates
|
||||
|
||||
auto = valid(AUTO_RUN_PRESETS)
|
||||
if auto:
|
||||
return auto
|
||||
|
||||
# 兜底:全部预置
|
||||
return list(PRESETS.keys())
|
||||
|
||||
|
||||
def run_presets(preset_names: List[str], dry_run: bool) -> None:
|
||||
for name in preset_names:
|
||||
cmd = [sys.executable, RUN_TESTS_SCRIPT, "--preset", name]
|
||||
cmds.append(cmd)
|
||||
|
||||
for cmd in cmds:
|
||||
printable = " ".join(cmd)
|
||||
if dry_run:
|
||||
print(f"[Dry-Run] {printable}")
|
||||
@@ -142,11 +142,11 @@ def run_presets(preset_names: List[str], dry_run: bool):
|
||||
subprocess.run(cmd, check=False)
|
||||
|
||||
|
||||
def main():
|
||||
parser = argparse.ArgumentParser(description="测试预置仓库(在此集中配置并运行测试组合)")
|
||||
parser.add_argument("--preset", choices=sorted(PRESETS.keys()), nargs="+", help="直接指定要运行的预置命令")
|
||||
parser.add_argument("--list", action="store_true", help="仅列出参数键和所有预置命令")
|
||||
parser.add_argument("--dry-run", action="store_true", help="仅打印将要执行的命令,而不真正运行")
|
||||
def main() -> None:
|
||||
parser = argparse.ArgumentParser(description="测试预置仓库(集中配置即可批量触发 run_tests)")
|
||||
parser.add_argument("--preset", choices=sorted(PRESETS.keys()), nargs="+", help="指定要运行的预置命令")
|
||||
parser.add_argument("--list", action="store_true", help="仅列出参数说明与所有预置")
|
||||
parser.add_argument("--dry-run", action="store_true", help="仅打印命令,不执行 pytest")
|
||||
args = parser.parse_args()
|
||||
|
||||
if args.list:
|
||||
@@ -154,12 +154,8 @@ def main():
|
||||
print_presets()
|
||||
return
|
||||
|
||||
if args.preset:
|
||||
target = args.preset
|
||||
else:
|
||||
target = AUTO_RUN_PRESETS or list(PRESETS.keys())
|
||||
|
||||
run_presets(target, dry_run=args.dry_run)
|
||||
targets = resolve_targets(args.preset)
|
||||
run_presets(targets, dry_run=args.dry_run)
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
|
||||
@@ -1,9 +1,3 @@
|
||||
<<<<<<< HEAD
|
||||
class TablesTask(BaseTask):
|
||||
def get_task_code(self) -> str: # 返回 "TABLES"
|
||||
def execute(self) -> dict: # 拉取 /Table/GetSiteTables
|
||||
def _parse_table(self, raw: dict) -> dict | None:
|
||||
=======
|
||||
# -*- coding: utf-8 -*-
|
||||
"""台桌档案任务"""
|
||||
|
||||
@@ -89,4 +83,3 @@ class TablesTask(BaseTask):
|
||||
),
|
||||
"raw_data": json.dumps(raw, ensure_ascii=False),
|
||||
}
|
||||
>>>>>>> main
|
||||
|
||||
Reference in New Issue
Block a user