67 lines
2.2 KiB
Python
67 lines
2.2 KiB
Python
"""公开 API 契约测试。
|
|||
|
|
|
||
|
|
这组测试**不测算法本身**,只守两件事:
|
||
|
|
1. `__all__` 里承诺的名字,必须真的存在且可调用;
|
||
|
|
2. 子包的导出必须与顶层一致。
|
||
|
|
|
||
|
|
它现在就能通过,并且在后续实现过程中会立刻抓到"改了导出忘了改 `__all__`"这类问题。
|
||
|
|
"""
|
||
|
|
|
||
|
|
from __future__ import annotations
|
||
|
|
|
||
|
|
import importlib
|
||
|
|
|
||
|
|
import pytest
|
||
|
|
|
||
|
|
import mcm_toolkit as mcm
|
||
|
|
|
||
|
|
SUBPACKAGES = ["core", "data", "stats", "models", "solve", "decision", "viz"]
|
||
|
|
|
||
|
|
|
||
|
|
def test_version_is_string():
|
||
|
|
assert isinstance(mcm.__version__, str)
|
||
|
|
assert mcm.__version__.count(".") == 2
|
||
|
|
|
||
|
|
|
||
|
|
@pytest.mark.parametrize("name", SUBPACKAGES)
|
||
|
|
def test_subpackage_importable(name):
|
||
|
|
mod = importlib.import_module(f"mcm_toolkit.{name}")
|
||
|
|
assert hasattr(mod, "__all__"), f"mcm_toolkit.{name} 缺 __all__"
|
||
|
|
assert hasattr(mcm, name), f"顶层未导出子包 {name}"
|
||
|
|
|
||
|
|
|
||
|
|
@pytest.mark.parametrize("name", SUBPACKAGES)
|
||
|
|
def test_subpackage_all_resolves(name):
|
||
|
|
mod = importlib.import_module(f"mcm_toolkit.{name}")
|
||
|
|
missing = [n for n in mod.__all__ if not hasattr(mod, n)]
|
||
|
|
assert not missing, f"mcm_toolkit.{name}.__all__ 里有不存在的名字:{missing}"
|
||
|
|
|
||
|
|
|
||
|
|
def test_toplevel_all_resolves():
|
||
|
|
missing = [n for n in mcm.__all__ if not hasattr(mcm, n)]
|
||
|
|
assert not missing, f"顶层 __all__ 里有不存在的名字:{missing}"
|
||
|
|
|
||
|
|
|
||
|
|
def test_toplevel_shortcuts_are_callable():
|
||
|
|
for name in ["monte_carlo", "bootstrap_ci", "parameter_sweep", "topsis", "compare_models"]:
|
||
|
|
assert callable(getattr(mcm, name)), f"{name} 应当是可调用的快捷函数"
|
||
|
|
|
||
|
|
|
||
|
|
def test_no_star_import_pollution():
|
||
|
|
"""顶层不应把子包内部的函数全部摊平——只允许 __all__ 里那批。"""
|
||
|
|
allowed = set(mcm.__all__) | {"annotations"} # annotations 来自 __future__ 导入
|
||
|
|
leaked = [n for n in dir(mcm) if not n.startswith("_") and n not in allowed]
|
||
|
|
assert not leaked, f"顶层出现了 __all__ 之外的公开名字:{leaked}"
|
||
|
|
|
||
|
|
|
||
|
|
def test_result_dataclass_defaults():
|
||
|
|
r = mcm.Result("demo")
|
||
|
|
assert r.success is True
|
||
|
|
assert r.params is None
|
||
|
|
assert r.metrics is None
|
||
|
|
assert r.message == ""
|
||
|
|
|
||
|
|
|
||
|
|
def test_random_helpers_exist():
|
||
|
|
assert callable(mcm.set_seed)
|
||
|
|
assert callable(mcm.get_rng)
|