"""Tests para build_boxplots_figure (boxplots horizontales de Tukey, grupo eda). Usa el backend Agg sin display; no muestra ni guarda figuras. Cada test cierra explícitamente la Figure construida (matplotlib.pyplot.close) para no acumular estado entre tests. """ import matplotlib matplotlib.use("Agg") import matplotlib.pyplot as plt # noqa: E402 from matplotlib.figure import Figure # noqa: E402 from build_boxplots_figure import build_boxplots_figure def _box(name, q1, median, q3, mn, mx, low=False, high=False, fliers=None): """Construye una entrada {name, box, fliers} con un box estilo build_boxplot_stats.""" iqr = q3 - q1 return { "name": name, "box": { "q1": q1, "median": median, "q3": q3, "iqr": iqr, "lower_fence": q1 - 1.5 * iqr, "upper_fence": q3 + 1.5 * iqr, "whisker_lo": max(mn, q1 - 1.5 * iqr), "whisker_hi": min(mx, q3 + 1.5 * iqr), "min": mn, "max": mx, "has_low_outliers": low, "has_high_outliers": high, "n_outliers": 0, }, "fliers": fliers, } def test_returns_figure_with_axes(): boxes = [ _box("edad", 10.0, 25.0, 40.0, 1.0, 100.0, high=True), _box("ingresos", 100.0, 200.0, 300.0, 50.0, 400.0), _box("score", -1.0, 0.0, 1.0, -5.0, 5.0, low=True, high=True), ] fig = build_boxplots_figure(boxes, title="Boxplots", max_boxes=12) assert isinstance(fig, Figure) assert len(fig.axes) >= 1 # Tres cajas -> tres etiquetas en el eje Y. ax = fig.axes[0] assert len(ax.get_yticks()) == 3 plt.close(fig) def test_empty_list_returns_placeholder_figure(): fig = build_boxplots_figure([], title="vacío") assert isinstance(fig, Figure) assert len(fig.axes) >= 1 plt.close(fig) def test_invalid_box_is_skipped_not_raised(): boxes = [ {"name": "rota", "box": {"q1": None, "median": None, "q3": None}}, {"name": "sin_box"}, # falta la clave box. "no_es_dict", # entrada no-dict. _box("buena", 1.0, 2.0, 3.0, 0.0, 10.0, high=True), ] fig = build_boxplots_figure(boxes) assert isinstance(fig, Figure) ax = fig.axes[0] # Solo la caja válida sobrevive al filtrado. assert len(ax.get_yticks()) == 1 plt.close(fig) def test_all_invalid_returns_placeholder(): boxes = [ {"name": "a", "box": {"q1": None, "median": 1.0, "q3": 2.0}}, {"name": "b"}, ] fig = build_boxplots_figure(boxes) assert isinstance(fig, Figure) assert len(fig.axes) >= 1 plt.close(fig) def test_raw_fliers_are_drawn(): boxes = [ _box("con_fliers", 10.0, 20.0, 30.0, 5.0, 200.0, high=True, fliers=[150.0, 180.0, 200.0]), ] fig = build_boxplots_figure(boxes) assert isinstance(fig, Figure) assert len(fig.axes) >= 1 plt.close(fig) def test_max_boxes_truncates_and_does_not_raise(): boxes = [_box(f"c{i}", float(i), float(i + 1), float(i + 2), float(i - 5), float(i + 10)) for i in range(20)] fig = build_boxplots_figure(boxes, title="muchos", max_boxes=5) assert isinstance(fig, Figure) ax = fig.axes[0] # Solo se dibujan las primeras 5 cajas. assert len(ax.get_yticks()) == 5 plt.close(fig)