def METHOD_NAME(
    forecasts: Iterable[Forecast],
    *,
    test_data: TestData,
    metrics,
    axis: Optional[Union[int, tuple]] = None,
    mask_invalid_label: bool = True,
    allow_nan_forecast: bool = False,
    seasonality: Optional[int] = None
) -> dict:
    """
    Evaluate ``forecasts`` by comparing them with ``test_data``, according
    to ``metrics``.
    .. note:: This feature is experimental and may be subject to changes.
    The optional ``axis`` arguments controls aggregation of the metrics:
    - ``None`` (default) aggregates across all dimensions
    - ``0`` aggregates across the dataset
    - ``1`` aggregates across the first data dimension (time, in the univariate setting)
    - ``2`` aggregates across the second data dimension (time, in the multivariate setting)
    Return results as a dictionary.
    """
    label_ndim = first(test_data.label)["target"].ndim
    assert label_ndim in [1, 2]
    if axis is None:
        axis = tuple(range(label_ndim + 1))
    if isinstance(axis, int):
        axis = (axis,)
    assert all(ax in range(3) for ax in axis)
    evaluators = {}
    for metric in metrics:
        evaluator = metric(axis=axis)
        evaluators[evaluator.name] = evaluator
    index_data = []
    for input_, label, forecast in tqdm(
        zip(test_data.input, test_data.label, forecasts)
    ):
        if 0 not in axis:
            index_data.append((forecast.item_id, forecast.start_date))
        data_batch = _get_data_batch(
            input_,
            label,
            forecast,
            seasonality=seasonality,
            mask_invalid_label=mask_invalid_label,
            allow_nan_forecast=allow_nan_forecast,
        )
        for evaluator in evaluators.values():
            evaluator.update(data_batch)
    metrics_values = {
        metric_name: evaluator.get()
        for metric_name, evaluator in evaluators.items()
    }
    if index_data:
        metrics_values["__index_0"] = index_data
    return metrics_values