Compare commits

...

3 Commits

Author SHA1 Message Date
Neo
b1f64c4bac 版本更改 2025-11-19 05:35:22 +08:00
Neo
ed47754b46 版本更改 2025-11-19 05:35:10 +08:00
Neo
fbee8a751e 同步 2025-11-19 05:32:03 +08:00
7 changed files with 119 additions and 138 deletions

View File

@@ -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

View File

@@ -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: 正常实时ETLOFFLINE: 读取归档JSONT/L
"mode": "OFFLINE",
# 离线归档JSON所在目录
# ONLINE: 正常实时 ETLOFFLINE: 读取归档 JSONT/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"

View File

@@ -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):

View File

@@ -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

View File

@@ -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",

View File

@@ -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_MODEONLINE 走 API 全流程OFFLINE 读取 JSON 归档执行 T+L。
类型:字符串;取值:"ONLINE""OFFLINE"
含义:覆盖 TEST_MODEONLINE 走 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_MODEONLINE / 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_MODEONLINE/ 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__":

View File

@@ -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