测试框架

aeon 使用 pytest 来测试估计器的接口合规性和代码的正确性。本页概述了测试框架。

单元测试应尽可能覆盖更多的代码。这包括函数和估计器的不同参数、错误处理以及边缘情况。仅仅运行代码是不够的,还需要通过输出和状态检查来测试其行为是否符合预期。

编写aeon测试

aeon中有两种主要的方式来测试代码。通过测试文件和通用的估计器测试。

测试文件

所有文件、函数和类都可以在包的相应tests目录中有相应的测试文件。如果要测试的文件是module.py,则其测试文件应放置在与原始文件相同的包中的tests/test_module.py

aeon/
   └── package/
      ├── __init__.py
      ├── module.py
      └── tests/
         ├── __init__.py
         └── test_module.py

所有单元测试应放置在tests目录中,文件名应以test_开头。所有测试函数应以test_开头,以便被pytest发现,例如。

def test_function():
    assert function() == expected_output

对于估计器,应避免测试基类功能,以防止与一般测试重复。避免测试:

  • 使用简单/测试参数运行fitpredict方法的基本运行

  • 基础类功能,例如get_paramsset_params的输出,或将输入数据转换为正确格式。

  • 处理基本错误的错误处理,例如错误的输入类型或输出形状和类型

进行测试:

  • 估计器的特定功能,例如在一般测试中未见的参数

  • 拟合后内部属性是否正确设置或输出是否符合预期

  • 参数值的边缘情况和错误处理以及更复杂的错误

如果未安装所需的软依赖项,则应跳过需要这些依赖项的测试函数。这可以使用pytest.mark.skipif装饰器来完成。请参阅依赖项页面。

通用估计器测试

testing 模块包含对任何扩展BaseAeonEstimator的类的通用测试。这将根据一组通用检查来测试估计器,以确保其符合aeon API。这包括检查继承的方法是否按预期执行,例如估计器可以在虚拟数据上无问题地拟合。我们还将对估计器执行各种检查,例如选择、是否是非确定性的以及方法调用后参数、输入和属性的状态。

从其他基类继承的估计器,例如BaseClassifierBaseAnomalyDetector,将运行额外的测试以确保它们 符合学习任务的API。估计器的标签也会影响 测试的内容,例如X_input_type影响使用的测试数据,而cant_pickle 跳过需要序列化估计器的测试。

没有列出所有在估计器上运行的测试列表,但所有检查的代码可以在estimator_checks子包中找到。一般测试可以使用测试API页面中的函数运行,主要函数是check_estimator。此函数将从各种_yield_*_checks.py文件中收集所有适用的测试并在估计器上运行它们。

需要软依赖的估计器将跳过除少数检查软依赖周围行为的测试之外的所有测试,如果未安装该依赖项。

向常规测试中添加新检查

要向通用测试中添加新的检查,您必须将其放置在正确的文件中。 例如,如果您希望您的检查在所有估计器上运行,它应该位于 _yield_estimator_checks.py 文件中。与软依赖检查相关的测试位于 _yield_soft_depencency_checks.py 文件中。所有分类估计器的测试位于 _yield_classification_checks.py 文件中,依此类推。

有多种类型的检查可以添加到常规测试中,任何新检查应该是以下之一:

  • 检查估计器类

  • 检查需要数据类型的估计器类

  • 检查估计器实例

  • 检查需要数据类型的估计器实例

  • 检查估计器实例,其中应测试所有可接受的数据类型

在大多数情况下(包括aeon GitHub CI),一般测试将在估计器的_create_test_instance方法返回的所有参数集上运行。这些参数集可能包含具有不同参数集的多个对象。每个估计器将具有多种数据类型用于测试数据,这包括诸如3D numpy数组和列表等类型,以及多变量和不等长数据等功能。为每种数据类型多次运行检查可能会很昂贵,因此请决定是否有必要或是否有任何数据类型足够。

编写新检查后,必须将其添加到文件顶部的_yield_*_checks函数中。根据检查的类型,将检查放置在函数的正确部分。使用partial生成检查,确保包含检查的任何参数,以便输出检查不需要任何输入。即。

yield partial(
    check_non_state_changing_method,  # check function
    estimator=estimator,  # estimator i from estimator_instances
    datatype=datatypes[i][0],  # estimator i, test datatype 0
)

请参阅_yield_classification_checks函数以查看此示例。

向通用测试添加新模块

如果你有一个需要通用测试的新模块,你必须在estimator_checking目录中添加一个新文件。这个文件应该命名为_yield_*_checks.py,其中*是模块的名称。这个文件应该遵循与目录中其他文件相同的结构。

确保新模块的基类在register中,并且它被包含在任何相关的tags中作为有效选项。

您需要确保在testing/testing_data.py中为您的估计器类型提供有效的测试数据和标签。 将数据添加到FULL_TEST_DATA_DICT字典中,并根据需要编辑底部的函数以适应您的模块。

一些测试工具可能也需要根据模块结构进行编辑, 即 _run_estimator_method 可能需要编辑以适应不同的方法参数名称。

排除测试和估计器

测试和估计器可以通过将它们添加到testing/testing_config.py文件中的EXCLUDED_ESTIMATORSEXCLUDED_TESTS列表中,完全排除在常规测试之外。EXCLUDED_ESTIMATORS只需要估计器类名即可跳过所有测试,而EXCLUDED_TESTS则需要类名和测试名称的列表。

这些跳过仅作为临时措施,导致跳过的问题最终应被修复,并且该项目应被移除。如果问题无法在估计器本身中解决(即predict必须更新任务的状态,这通常是不允许的),则应更新测试本身,并将项目从排除列表中移除。

aeon CI中进行测试

aeon 使用 GitHub Actions 进行持续集成测试。

aeon 定期测试工作流每天运行一次(手动运行除外),并将运行所有可能的测试。这包括运行所有测试文件和对所有估计器进行一般估计器测试。CI 还将检查代码格式和 linting,以及测试覆盖率。

aeon PR测试工作流在每个PR到主分支时运行。默认情况下,这将运行一组受限的测试,排除一些测试,例如那些明显昂贵或容易失败的测试(即来自外部源的I/O)。运行的估计器也将被分成较小的子集,以在不同的Python版本和操作系统组合上分布。这是由testing/testing_config.py中的PR_TESTING标志控制的。

大部分测试时间都花在编译numba函数上。默认情况下,拉取请求工作流将使用从定期测试生成的一组缓存函数。任何更改文件的缓存函数都将失效。

有许多标签可以添加到PR中以控制测试。这些标签包括:

  • codecov actions - 运行codecov操作以更新测试覆盖率

  • full examples run - 运行文档中的完整示例

  • full pre-commit - 在所有文件上运行预提交检查

  • full pytest actions - 在CI中运行所有测试,禁用PR_TESTING

  • no numba cache - 禁用GitHub numba 缓存以进行测试

  • run typecheck test - 运行 mypy 类型检查工作流

定期测试将运行上述所有内容。

使用pytest在本地运行单元测试

要检查您的代码是否在本地通过了所有测试,您需要安装开发版本的aeon以及所有额外的依赖项。有关更多信息,请参阅开发者安装指南。

要运行所有单元测试,请运行:

pytest aeon/

所有常规的 pytest 配置在这里都适用。更多信息请参阅他们的 文档

-k 选项可用于运行具有特定关键字的测试,即运行所有包含 DummyClassifier 的测试:

pytest aeon/ -k DummyClassifier

所有常规测试都会在测试名称中包含估计器名称,因此这可以用于运行特定估计器的测试。这也适用于测试/检查名称,或组合以在特定估计器上运行特定检查。

pytest-xdist 依赖项允许进行并行测试。要在所有可用核心上并行运行测试,请运行:

pytest aeon/ -n auto

或者,输入一个数字以在那么多核心上运行,即 -n 4 以在4个核心上运行。

aeon 在其 conftest.py` 文件 中也有一些自定义配置选项。它们是:

  • --nonumba - 如果为真,则禁用numba编译

  • --enablethreading - 如果为真,则在测试前跳过将各种线程选项设置为1

  • --prtesting - 设置PR_TESTING标志

跟踪测试覆盖率

我们使用coveragepytest-cov插件和codecov进行测试覆盖率。测试覆盖率可以在aeon codecov页面上找到。

生成覆盖率报告的工作流将禁用numba njit函数。 这主要是因为无法准确测量这些函数的覆盖率。 numba函数也容易出现意外错误,例如数组越界访问,这不会引发错误。因此,我们使用这些工作流作为代码库中错误的额外检查。