Files
MCM-ICM_Toolkit/tests/test_api_surface.py
T
guzhujushi 5319589dfd chore: 初始化 MCM/ICM Toolkit 仓库骨架
审查并精简 ChatGPT 的建库建议后落地的第一版骨架,只定接口不写实现。

- 建议审查:14 个包 → 7 个;约 250 个函数 → 约 40 个;5 个 Result 类 → 1 个
  (docs/01-suggestion-review.md)
- docs/:架构、路线图(含永久不做清单)、环境、Git 工作流、函数卡片模板
- src/mcm_toolkit/:core / data / stats / models / solve / decision / viz
  共 23 个模块,均为签名 + docstring + NotImplementedError
- tests/:9 个文件,其中 test_api_surface.py 已通过(20 passed)
- examples/ problems/ notebooks/ figures/ tables/ 骨架
- pyproject.toml(核心依赖 4 个 + 6 组 extras)、environment.yml
- scripts/:setup-venv.ps1(ASCII + LF)、setup-conda.ps1、doctor.ps1
- 环境:.venv(system-site-packages)与 conda 环境 mcm-toolkit 均已建好,
  数值栈版本与 base 完全对齐(numpy 2.4.6 / scipy 1.18.1 / pandas 3.0.6,MKL 后端)
2026-10-01 17:43:48 +08:00

68 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)