Files
Neo-ZQYY/tests/test_property_3_table_map_coverage.py

215 lines
9.8 KiB
Python
Raw Permalink Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
# -*- coding: utf-8 -*-
"""
Feature: dataflow-field-completion, Property 3: TABLE_MAP 覆盖完整性
**Validates: Requirements 7.2, 8.2**
对于任意在 TABLE_MAP 中注册的 DWD 表,该表的所有非 SCD2 列要么在 FACT_MAPPINGS
中有显式映射,要么在对应 ODS 表中存在同名列(自动映射)。
本测试聚焦以下可静态验证的属性:
1. TABLE_MAP 所有条目的 ODS 源表非空
2. C 类表在 TABLE_MAP 中注册
3. C 类表在 FACT_MAPPINGS 中有条目
4. C 类表映射字段数量与期望一致
5. TABLE_MAP 与 FACT_MAPPINGS 交叉一致性
6. hypothesis 属性测试:随机 TABLE_MAP 条目结构验证
"""
from __future__ import annotations
import sys
from pathlib import Path
from hypothesis import given, settings, HealthCheck
import hypothesis.strategies as st
# ── 将 ETL 模块加入 sys.path ──
_ETL_ROOT = Path(__file__).resolve().parent.parent / "apps" / "etl" / "connectors" / "feiqiu"
if str(_ETL_ROOT) not in sys.path:
sys.path.insert(0, str(_ETL_ROOT))
from tasks.dwd.dwd_load_task import DwdLoadTask
# ── C 类表定义 ──
C_CLASS_TABLES = [
"dwd.dwd_goods_stock_summary",
"dwd.dwd_goods_stock_movement",
]
# ── C 类表期望映射字段 ──
# goods_stock_summary → dwd_goods_stock_summary14 个字段)
_GOODS_STOCK_SUMMARY_EXPECTED_COLS = {
"site_goods_id", "goods_name", "goods_unit", "goods_category_id",
"goods_category_second_id", "category_name", "range_start_stock",
"range_end_stock", "range_in", "range_out", "range_sale",
"range_sale_money", "range_inventory", "current_stock",
}
# goods_stock_movements → dwd_goods_stock_movement19 个字段)
_GOODS_STOCK_MOVEMENT_EXPECTED_COLS = {
"site_goods_stock_id", "tenant_id", "site_id", "site_goods_id",
"goods_name", "goods_category_id", "goods_second_category_id",
"unit", "price", "stock_type", "change_num", "start_num", "end_num",
"change_num_a", "start_num_a", "end_num_a", "remark", "operator_name",
"create_time",
}
# ── 收集所有 TABLE_MAP 条目 ──
_ALL_TABLE_MAP_ENTRIES = list(DwdLoadTask.TABLE_MAP.items())
# ══════════════════════════════════════════════════════════════════
# Property 3.1: TABLE_MAP 所有条目的 ODS 源表非空
# ══════════════════════════════════════════════════════════════════
def test_all_table_map_entries_have_ods_source():
"""TABLE_MAP 中每个 DWD 表都有非空的 ODS 源表名。"""
for dwd_table, ods_table in DwdLoadTask.TABLE_MAP.items():
assert ods_table and isinstance(ods_table, str) and ods_table.strip(), \
f"{dwd_table}: TABLE_MAP 中的 ODS 源表为空或无效"
# ══════════════════════════════════════════════════════════════════
# Property 3.2: C 类表在 TABLE_MAP 中注册
# ══════════════════════════════════════════════════════════════════
def test_c_class_tables_registered_in_table_map():
"""
**Validates: Requirements 7.2, 8.2**
C 类表dwd_goods_stock_summary、dwd_goods_stock_movement
必须在 TABLE_MAP 中注册。
"""
for table in C_CLASS_TABLES:
assert table in DwdLoadTask.TABLE_MAP, \
f"C 类表 {table} 未在 TABLE_MAP 中注册"
ods_table = DwdLoadTask.TABLE_MAP[table]
assert ods_table and isinstance(ods_table, str), \
f"C 类表 {table}: TABLE_MAP 中的 ODS 源表无效"
# ══════════════════════════════════════════════════════════════════
# Property 3.3: C 类表在 FACT_MAPPINGS 中有条目
# ══════════════════════════════════════════════════════════════════
def test_c_class_tables_have_fact_mappings():
"""
**Validates: Requirements 7.2, 8.2**
C 类表必须在 FACT_MAPPINGS 中有至少一个映射条目。
"""
for table in C_CLASS_TABLES:
entries = DwdLoadTask.FACT_MAPPINGS.get(table, [])
assert len(entries) > 0, \
f"C 类表 {table} 在 FACT_MAPPINGS 中无条目"
# ══════════════════════════════════════════════════════════════════
# Property 3.4: goods_stock_summary 14 个字段全覆盖
# ══════════════════════════════════════════════════════════════════
def test_goods_stock_summary_mapping_coverage():
"""
**Validates: Requirements 7.2**
dwd.dwd_goods_stock_summary 的 FACT_MAPPINGS 应覆盖全部 14 个期望字段。
"""
table = "dwd.dwd_goods_stock_summary"
entries = DwdLoadTask.FACT_MAPPINGS.get(table, [])
actual_cols = {e[0].lower() for e in entries}
# 验证数量
assert len(entries) == 14, \
f"{table}: 期望 14 个映射条目,实际 {len(entries)}"
# 验证字段覆盖
missing = _GOODS_STOCK_SUMMARY_EXPECTED_COLS - actual_cols
assert not missing, \
f"{table}: 缺少映射字段 {missing}"
extra = actual_cols - _GOODS_STOCK_SUMMARY_EXPECTED_COLS
assert not extra, \
f"{table}: 存在多余映射字段 {extra}"
# ══════════════════════════════════════════════════════════════════
# Property 3.5: goods_stock_movement 19 个字段全覆盖
# ══════════════════════════════════════════════════════════════════
def test_goods_stock_movement_mapping_coverage():
"""
**Validates: Requirements 8.2**
dwd.dwd_goods_stock_movement 的 FACT_MAPPINGS 应覆盖全部 19 个期望字段。
"""
table = "dwd.dwd_goods_stock_movement"
entries = DwdLoadTask.FACT_MAPPINGS.get(table, [])
actual_cols = {e[0].lower() for e in entries}
# 验证数量
assert len(entries) == 19, \
f"{table}: 期望 19 个映射条目,实际 {len(entries)}"
# 验证字段覆盖
missing = _GOODS_STOCK_MOVEMENT_EXPECTED_COLS - actual_cols
assert not missing, \
f"{table}: 缺少映射字段 {missing}"
extra = actual_cols - _GOODS_STOCK_MOVEMENT_EXPECTED_COLS
assert not extra, \
f"{table}: 存在多余映射字段 {extra}"
# ══════════════════════════════════════════════════════════════════
# Property 3.6: FACT_MAPPINGS 是 TABLE_MAP 的子集
# ══════════════════════════════════════════════════════════════════
def test_fact_mappings_subset_of_table_map():
"""FACT_MAPPINGS 中的所有 DWD 表都必须在 TABLE_MAP 中注册。"""
for table in DwdLoadTask.FACT_MAPPINGS:
assert table in DwdLoadTask.TABLE_MAP, \
f"FACT_MAPPINGS 中的 {table} 未在 TABLE_MAP 中注册"
# ══════════════════════════════════════════════════════════════════
# Hypothesis 属性测试:随机 TABLE_MAP 条目结构验证
# ══════════════════════════════════════════════════════════════════
_table_map_entry_strategy = st.sampled_from(_ALL_TABLE_MAP_ENTRIES)
@given(entry=_table_map_entry_strategy)
@settings(max_examples=200, suppress_health_check=[HealthCheck.function_scoped_fixture])
def test_random_table_map_entry_valid(entry):
"""
**Validates: Requirements 7.2, 8.2**
对于任意随机选取的 TABLE_MAP 条目,验证:
- DWD 表名为 "dwd." 前缀的非空字符串
- ODS 源表名为 "ods." 前缀的非空字符串
- 如果该表在 FACT_MAPPINGS 中有条目,每个条目都是合法的三元组
"""
dwd_table, ods_table = entry
# DWD 表名格式验证
assert isinstance(dwd_table, str) and dwd_table.startswith("dwd."), \
f"TABLE_MAP key {dwd_table!r} 不以 'dwd.' 开头"
# ODS 源表名格式验证
assert isinstance(ods_table, str) and ods_table.startswith("ods."), \
f"TABLE_MAP[{dwd_table}] = {ods_table!r} 不以 'ods.' 开头"
# 如果有 FACT_MAPPINGS 条目,验证结构
entries = DwdLoadTask.FACT_MAPPINGS.get(dwd_table, [])
for i, e in enumerate(entries):
assert isinstance(e, (tuple, list)) and len(e) == 3, \
f"{dwd_table}[{i}]: FACT_MAPPINGS 条目应为三元组"
dwd_col, ods_expr, cast_type = e
assert isinstance(dwd_col, str) and dwd_col.strip(), \
f"{dwd_table}[{i}]: dwd_col 为空"
assert isinstance(ods_expr, str) and ods_expr.strip(), \
f"{dwd_table}[{i}]: ods_expr 为空"
assert cast_type is None or isinstance(cast_type, str), \
f"{dwd_table}[{i}].{dwd_col}: cast_type 类型无效"