"""viz 包测试。 绘图测试不比对像素——只验证"能画出来、能存成文件、尺寸符合美赛要求"。 未实现的函数按决策 26 **逐条** skip,不用模块级 `pytestmark`。 """ from __future__ import annotations import matplotlib matplotlib.use("Agg") import pytest _NOT_IMPLEMENTED = "未实现:见 docs/03-roadmap.md L0/L1" @pytest.fixture def fig(): """最小 Figure;测试结束统一关闭,避免 matplotlib 的"图开太多"告警。""" import matplotlib.pyplot as plt figure, _ = plt.subplots(figsize=(2, 2)) yield figure plt.close(figure) @pytest.mark.skip(reason=_NOT_IMPLEMENTED) @pytest.mark.parametrize("kind", ["line", "scatter", "hist", "box", "bar"]) def test_plot_kinds_produce_figure(kind, rng): from mcm_toolkit.viz import plot x = rng.normal(size=100) figure, ax = plot(x, x, kind=kind) assert figure is not None and ax is not None @pytest.mark.skip(reason=_NOT_IMPLEMENTED) def test_mcm_style_sets_300dpi(): from mcm_toolkit.viz import mcm_style mcm_style() assert matplotlib.rcParams["figure.dpi"] == 300.0 assert matplotlib.rcParams["font.size"] >= 8.0 def test_save_figure_writes_png_and_pdf(fig, tmp_path): from mcm_toolkit.viz import save_figure paths = save_figure(fig, tmp_path / "fig1", formats=("png", "pdf")) assert [p.suffix for p in paths] == [".png", ".pdf"] assert all(p.exists() and p.stat().st_size > 0 for p in paths) def test_save_figure_strips_an_image_suffix_from_path(fig, tmp_path): """`fig1.png` 当主干用:不能写出 `fig1.png.png`。""" from mcm_toolkit.viz import save_figure paths = save_figure(fig, tmp_path / "fig1.png", formats=("pdf",)) assert paths == [tmp_path / "fig1.pdf"] assert paths[0].exists() def test_save_figure_creates_parent_directories(fig, tmp_path): from mcm_toolkit.viz import save_figure target = tmp_path / "nested" / "deeper" / "fig1" paths = save_figure(fig, target, formats=("png",)) assert paths[0] == tmp_path / "nested" / "deeper" / "fig1.png" assert paths[0].exists() def test_save_figure_normalizes_format_case_and_keeps_order(fig, tmp_path): from mcm_toolkit.viz import save_figure paths = save_figure(fig, tmp_path / "fig1", formats=("PDF", "png")) assert [p.suffix for p in paths] == [".pdf", ".png"] assert all(p.exists() for p in paths) def test_save_figure_rejects_a_single_string_as_formats(fig, tmp_path): from mcm_toolkit.viz import save_figure with pytest.raises(TypeError, match="不要传单个字符串"): save_figure(fig, tmp_path / "fig1", formats="png") # type: ignore[arg-type] def test_save_figure_rejects_bad_formats(fig, tmp_path): from mcm_toolkit.viz import save_figure with pytest.raises(ValueError, match="不能为空"): save_figure(fig, tmp_path / "fig1", formats=()) with pytest.raises(ValueError, match="不支持"): save_figure(fig, tmp_path / "fig1", formats=("gif",)) with pytest.raises(ValueError, match="重复"): save_figure(fig, tmp_path / "fig1", formats=("png", "PNG")) def test_save_figure_validates_before_creating_directories(fig, tmp_path): """格式错时不能先建目录留下副作用。""" from mcm_toolkit.viz import save_figure nested = tmp_path / "should_not_exist" / "fig1" with pytest.raises(ValueError, match="不支持"): save_figure(fig, nested, formats=("gif",)) assert not nested.parent.exists() def test_save_figure_rejects_bad_render_options(fig, tmp_path): from mcm_toolkit.viz import save_figure with pytest.raises(TypeError, match="dpi"): save_figure(fig, tmp_path / "fig1", dpi=300.0) # type: ignore[arg-type] with pytest.raises(TypeError, match="dpi"): save_figure(fig, tmp_path / "fig1", dpi=True) with pytest.raises(ValueError, match="dpi"): save_figure(fig, tmp_path / "fig1", dpi=0) with pytest.raises(TypeError, match="bbox_inches"): save_figure(fig, tmp_path / "fig1", bbox_inches=3) # type: ignore[arg-type] with pytest.raises(ValueError, match="bbox_inches"): save_figure(fig, tmp_path / "fig1", bbox_inches="") with pytest.raises(TypeError, match="transparent"): save_figure(fig, tmp_path / "fig1", transparent="yes") # type: ignore[arg-type] def test_save_figure_rejects_non_figure(tmp_path): from mcm_toolkit.viz import save_figure with pytest.raises(TypeError, match="Figure"): save_figure(object(), tmp_path / "fig1") # type: ignore[arg-type] @pytest.mark.skip(reason=_NOT_IMPLEMENTED) def test_save_table_writes_latex_with_caption(tmp_path): import pandas as pd from mcm_toolkit.viz import save_table df = pd.DataFrame({"rmse": [0.31, 0.28]}, index=["linear", "rf"]) paths = save_table(df, tmp_path / "tab1", caption="模型对比", label="tab:compare") tex = next(p for p in paths if p.suffix == ".tex") content = tex.read_text(encoding="utf-8") assert "模型对比" in content and "tab:compare" in content