"""公开 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)