跳过主要内容

测试哲学

观看这个演讲以获取动力:Autonomous Testing and the Future of Software Development by Will Wilson

Metaflow 测试套件

核心 Metaflow 的集成测试工具位于 test/core,生成并执行合成的 Metaflow 流,测试 Metaflow 的所有方面。测试套件使用 tox 按照 tox.ini 中的配置执行。您可以按照下面的描述手动使用 pytestrun_tests.py 运行测试。

当你执行 python helloworld.py run 时,会发生什么?执行涉及 Metaflow 栈的多个层次。该栈如下所示,从最基本的层开始,一直到用户界面:

  1. Python 解释器 (python2, python3)
  2. Metaflow 核心 (task.py, runtime.py, datastore, 等等)
  3. Metaflow 插件 (@timeout, @catch, metadata.py)
  4. 用户定义的图
  5. 用户定义的步骤函数
  6. 用户界面 (cli.py, metaflow.client)

我们可以为第2、3和6层中的函数编写单元测试,这将捕捉到一些错误。然而,更大范围的错误是由于层之间意外的相互作用引起的。例如,在深度嵌套的foreach图中捕捉到的异常可能无法在客户端API中正确返回,当使用Python 3时。

core目录中包含的集成测试工具通过使用开发者提供的规范自动生成测试用例,试图揭示诸如此类的错误。

规格

测试工具允许您以四种方式自定义行为,这些方式对应于上述层次:

  1. 您定义执行环境,包括环境变量、Python解释器的版本和用作上下文的数据库类型在contexts.json (层 1 和 2)
  2. 您定义步骤函数、使用的装饰器以及期望的结果作为 MetaflowTest 模板,存储在 tests 目录中 (层 3 和 5)
  3. 您定义与步骤函数匹配的各种图形,作为图形结构的简单 JSON 描述,存储在 graphs 目录中 (层 4)
  4. 您可以将检查结果的不同方式定义为对应于Metaflow不同用户界面的 MetaflowCheck 类,这些类存储在 metaflow_test 目录 (第6层)。您可以在 context.json 中自定义在不同上下文中使用哪些检查器。

测试工具将所有 contextsgraphstestscheckers 结合起来,为它们的每个组合生成一个测试流程,除非您明确设置了允许的组合约束。然后执行这些测试流程,选择性地进行并行执行,并收集和总结结果。

上下文

上下文定义在 contexts.json 中。这个文件应该很好理解。 大多数情况下,您不需要编辑该文件,除非您为新的命令行参数添加测试。

请注意,某些上下文具有 disabled: true。在 CI 系统运行测试时,这些上下文默认不会被执行。您可以在命令行中启用它们以进行本地测试,如下所示。

测试

查看 tests/basic_artifact.py。此测试验证在第一步骤中定义的工件在所有下游步骤中可用。您可以将此简单测试用作新测试的模板。

您的测试类应该派生自 MetaflowTest。类变量 PRIORITY 表示所测试的功能对 Metaflow 的基础性。测试按优先级升序执行,以确保基础稳固后再进行更复杂的案例。

步骤函数使用@steps装饰器进行修饰。请注意,与正常的Metaflow流程不同,这些函数可以应用于图中的多个步骤。这个测试工具的核心思想是将图与步骤函数解耦,以便可以自动测试各种组合。因此,您需要提供可以应用于各种步骤类型的步骤函数。

@steps 装饰器接受两个参数。第一个参数是一个整数,用于定义多个 steps 函数之间的优先顺序,以防多个步骤函数模板匹配。典型模式是为特定步骤类型提供特定函数,例如连接,并给它一个优先级 0。然后可以定义另一个通用函数 @steps(2, ['all'])。因此,特殊函数应用于连接,而通用函数应用于所有其他步骤。

第二个参数提供了一个资格的列表,指定此函数可以应用于哪种类型的步骤。有一组内置资格:all, start, end, join, linear,它们与相应的步骤类型匹配。除了这些内置资格,图形还可以指定任何自定义资格。

通过将 required=True 作为关键字参数指定给 @steps,您可以要求某个步骤函数需要与图结合使用,以生成有效的测试用例。通过创建自定义限定符并设置 required=True,您可以控制测试如何与图进行匹配。

一般来说,编写不指定过于严格的限定条件和 required=True 的测试用例是有益的。这样可以广泛捕捉到许多生成测试用例中的错误。然而,如果测试执行缓慢和/或没有从大量匹配图形中受益,那么使测试更具体是一个好主意。

断言

测试用例没有验证其结果时不是非常有用。验证测试是否按预期行为的方式有两种。

您可以在步骤函数中使用一个函数 assert_equals(expected, got) 来确认步骤函数内部的数据是有效的。其次,您可以在测试类中定义一个方法 check_results(self, flow, checker),该方法在流成功执行后验证存储的结果。

使用

checker.assert_artifact(step_name, artifact_name, expected_value)

以确认步骤包含预期的数据工件。

查看tests目录中的现有测试用例,以了解这在实践中是如何工作的。

图表

图是有向图的简单JSON表示。它们列出了图中的每一步和它们之间的转换。每一步可以有一个可选的自定义限定符列表,如上所述。

您可以查看graphs目录中的现有图形,以了解语法。

跳棋

目前,测试工具使用了两种类型的用户界面:命令行接口,定义在 cli_check.py 中,以及 Python API,定义在 mli_check.py 中。

目前,您可以使用这些检查器来断言数据工件或日志输出的值。如果您想为CLI和/或Python API中的新功能类型添加测试,您应该在MetaflowCheck基类中添加一个新方法,并在mli_check.pycli_check.py中实现相应的实现。如果某些功能仅在其中一个接口中可用,您可以在另一个检查器类中提供一个返回True的存根实现。

使用

测试工具通过运行 run_tests.py 来执行。默认情况下,它执行所有有效的上下文、测试、图表和检查器的组合。此模式适合由 CI 系统执行的自动化测试。

在本地测试时,建议按照以下方式运行测试套件:

cd metaflow/test/core
PYTHONPATH=`pwd`/../../ python run_tests.py --debug --contexts dev-local

这仅使用 dev_local 上下文,这个上下文不依赖于任何网络通信,比如 --metadata=service--datastore=s3--debug 标志使得在第一个测试用例失败时,框架迅速失败。默认模式是运行所有测试用例,并在最后总结所有失败情况。

您可以如下运行单个测试用例:

cd metaflow/test/core
PYTHONPATH=`pwd`/../../ python run_tests.py --debug --contexts dev-local --graphs single-linear-step --tests BasicArtifactTest

这选择了一个单一的上下文,一个单一的图形,和一个单一的测试。如果您正在开发一个新测试,这是测试该测试的最快方式。

覆盖率报告

测试工具使用Python中的 coverage 包来生成测试覆盖率报告。默认情况下,您可以在测试工具完成后在 coverage 目录中找到全面的测试覆盖率报告。

在您在 Metaflow 中开发新功能后,请使用逐行覆盖报告确认与新功能相关的所有行都被测试覆盖。