测试数据质量

本文档将介绍如何使用 Dataform 表断言和单元测试来测试工作流代码。

准备工作

  1. 在 Google Cloud 控制台中,前往 Dataform 页面。

    前往 Dataform 页面

  2. 选择或创建代码库

  3. 选择或创建开发工作区

  4. 创建表

所需的角色

如需获得创建断言和单元测试所需的权限,请让管理员向您授予以下 IAM 角色:

如需详细了解如何授予角色,请参阅管理对项目、文件夹和组织的访问权限

您也可以通过自定义角色或其他预定义角色来获取所需的权限。

使用断言测试数据

断言是一种数据质量测试查询,用于查找违反查询中指定的一个或多个条件的行。如果查询返回任何行,则断言失败。Dataform 每次更新工作流时都会运行断言,并在任何断言失败时提醒您。

Dataform 会在 BigQuery 中自动创建包含已编译断言查询结果的视图。如工作流设置文件中所配置的那样,Dataform 会在断言架构中创建这些视图,您可以在其中检查断言结果。

例如,对于默认的 dataform_assertions 架构,Dataform 会在 BigQuery 中创建以下格式的视图:dataform_assertions.assertion_name

您可以为所有 Dataform 表类型(包括表、增量表、视图和物化视图)创建断言。

您可以通过以下方式创建断言:

创建内置断言

您可以向表的 config 块添加内置的 Dataform 断言。Dataform 会在创建表后运行这些断言。Dataform 创建表后,您可以在工作区的工作流执行日志标签页中查看断言是否通过。

您可以在表的 config 块中创建以下断言:

  • nonNull

    此条件断言指定列在所有表行中均不为 null。此条件适用于永远不能为 null 的列。

    以下代码示例展示了表格的 config 块中的 nonNull 断言:

config {
  type: "table",
  assertions: {
    nonNull: ["user_id", "customer_id", "email"]
  }
}
SELECT ...
  • rowConditions

    此条件用于断言所有表行都遵循您定义的自定义逻辑。每个行条件都是一个自定义 SQL 表达式,并且每个表行都会根据每个行条件进行评估。如果任何表行导致 false,则断言失败。

    以下代码示例展示了增量表的 config 块中的自定义 rowConditions 断言:

config {
  type: "incremental",
  assertions: {
    rowConditions: [
      'signup_date is null or signup_date > "2022-08-01"',
      'email like "%@%.%"'
    ]
  }
}
SELECT ...
  • uniqueKey

    此条件断言,在指定列中,没有表格行的值相同。

    以下代码示例展示了视图的 config 块中的 uniqueKey 断言:

config {
  type: "view",
  assertions: {
    uniqueKey: ["user_id"]
  }
}
SELECT ...
  • uniqueKeys

    此条件断言,在指定的列中,没有表格行的值相同。如果表中有多行的所有指定列的值都相同,则断言失败。

    以下代码示例展示了表格的 config 块中的 uniqueKeys 断言:

config {
  type: "table",
  assertions: {
    uniqueKeys: [["user_id"], ["signup_date", "customer_id"]]
  }
}
SELECT ...

config 代码块添加断言

如需向表的配置块添加断言,请按以下步骤操作:

  1. 在开发工作区的文件窗格中,选择一个表定义 SQLX 文件。
  2. 在表格文件的 config 块中,输入 assertions: {}
  3. assertions: {} 内,添加断言。
  4. 可选:点击格式

以下代码示例展示了在 config 块中添加的条件:

config {
  type: "table",
  assertions: {
    uniqueKey: ["user_id"],
    nonNull: ["user_id", "customer_id"],
    rowConditions: [
      'signup_date is null or signup_date > "2019-01-01"',
      'email like "%@%.%"'
    ]
  }
}
SELECT ...

使用 SQLX 创建手动断言

手动断言是指您在专用 SQLX 文件中编写的 SQL 查询。手动断言 SQL 查询必须返回零行。如果查询在运行时返回行,则断言失败。

如需在新的 SQLX 文件中添加手动断言,请按照以下步骤操作:

  1. 文件窗格中,点击 definitions/ 旁边的 更多菜单。
  2. 点击创建文件
  3. 添加文件路径字段中,输入文件名称,后跟 .sqlx。例如 definitions/custom_assertion.sqlx

    文件名只能包含数字、字母、连字符和下划线。

  4. 点击创建文件

  5. 文件窗格中,点击新文件。

  6. 在该文件中,输入:

    config {
      type: "assertion"
    }
    
  7. config 块下方,编写 SQL 查询或多个查询。

  8. 可选:点击格式

以下代码示例展示了 SQLX 文件中的手动断言,该断言用于断言 sometable 中的字段 ABc 永远不会为 NULL

config { type: "assertion" }

SELECT
  *
FROM
  ${ref("sometable")}
WHERE
  a IS NULL
  OR b IS NULL
  OR c IS NULL

使用单元测试测试数据质量

单元测试是一种数据质量测试,在专用 .sqlx 文件中定义,用于模拟所测试工作流操作的所有依赖项并提供预期结果。您可以使用单元测试针对受控的模拟输入测试 Dataform 操作,以验证操作代码是否能正确处理极端情况、null 值、聚合、正则表达式和条件逻辑。

操作依赖项(例如 ${ref()} 函数中引用的前置表、视图或原始声明)的模拟对象在 input 块中定义。每个 input 块都通过名称引用依赖项,并包含一个用于定义模拟行的 SQL 查询。此查询通常是一系列与 UNION ALL 结合使用的 SELECT 语句。预期结果是 SQL 查询,表示在工作流操作 SQL 语句上运行指定输入的结果。

Dataform 会逐行运行单元测试,并将运行工作流操作的 SQL 逻辑(针对模拟数据)的实际结果与预期结果集进行比较。

单元测试会解析为以下状态:

  • SUCCESS:测试通过。实际结果与预期结果一致。
  • FAILURE:测试失败。实际结果与预期结果不符。

限制

Dataform 单元测试可用,但存在以下限制:

  • 单元测试适用于 Dataform 核心版本 3.0.56 及更高版本。
  • 单元测试中输入数据的最大大小为每个输入 100 行。

创建单元测试

将单元测试的 .sqlx 文件存储在 definitions/ 目录中。 如需在 definitions/ 目录中创建新的单元测试 .sqlx 文件,请按以下步骤操作:

  1. 在 Google Cloud 控制台中,前往 Dataform 页面。

    前往 Dataform 页面

  2. 选择一个代码库。

  3. 选择开发工作区。

  4. 文件窗格中,点击 definitions/ 旁边的 更多菜单。

  5. 点击创建文件

  6. 创建新文件窗格中,执行以下操作:

    1. 添加文件路径字段中的 definitions/ 后面输入文件的名称,然后输入 _test.sqlx。例如 definitions/customer_spend_test.sqlx

      文件名只能包含数字、字母、连字符和下划线。

    2. 点击创建文件

  7. 在测试文件中,添加以下 config 代码块:

    config {
      type: "test",
      dataset: "ACTION_NAME"
    }
    

    ACTION_NAME 替换为此测试验证的操作的名称。

  8. 如需模拟测试的操作,请为每个操作依赖项添加一个 input 块,并按以下格式编写用于测试该依赖项的 SQL 查询:

    input "DEPENDENCY_NAME" {
    SELECT ...
    SELECT ...
    }
    

    DEPENDENCY_NAME 替换为相应输入所模拟的已测试操作依赖项的名称。

  9. input 代码块下方,编写标准 SQL 查询,以表示预期输出行,格式如下:

    -- Expected Output
    SELECT ...
    SELECT ...
    

预期输出查询应仅返回测试操作在给定模拟输入的情况下应生成的行和列。

以下代码示例展示了 customer_spend.sqlx 工作流操作:

config {
type: "table",
name: "customer_spend"
}

SELECT
  c.customer_id,
  c.name,
  SUM(o.amount) AS total_completed_amount
FROM
  ${ref("source_customers")} c
  JOIN
  ${ref("source_orders")} o
  ON c.customer_id = o.customer_id
WHERE
  o.status = 'COMPLETED'
GROUP BY
  1, 2

以下代码示例展示了 customer_spend_test.sqlx 单元测试,该测试会模拟 customer_spend.sqlx 操作的依赖项,并为模拟对象定义预期结果:

config {
  type: "test",
  dataset: "customer_spend"
}

input "source_customers" {
  SELECT 101 AS customer_id, 'Alice' AS name UNION ALL
  SELECT 102 AS customer_id, 'Bob' AS name UNION ALL
  SELECT 103 AS customer_id, 'Charlie' AS name
}

input "source_orders" {
  -- Alice has one completed and one pending order
  SELECT 1 AS order_id, 101 AS customer_id, 'COMPLETED' AS status, 100.0 AS amount UNION ALL
  SELECT 2 AS order_id, 101 AS customer_id, 'PENDING' AS status, 50.0 AS amount UNION ALL
  -- Bob has one completed order
  SELECT 3 AS order_id, 102 AS customer_id, 'COMPLETED' AS status, 250.0 AS amount UNION ALL
  -- Charlie has no orders
  SELECT 4 AS order_id, 999 AS customer_id, 'COMPLETED' AS status, 10.0 AS amount
}

-- Expected Output
SELECT 101 AS customer_id, 'Alice' AS name, 100.0 AS total_completed_amount UNION ALL
SELECT 102 AS customer_id, 'Bob' AS name, 250.0 AS total_completed_amount

运行单元测试

如需运行单元测试,请按以下步骤操作:

控制台

  1. 在 Google Cloud 控制台中,前往 Dataform 页面。

    前往 Dataform 页面

  2. 选择一个代码库。

  3. 选择开发工作区。

  4. 依次点击开始执行  > 执行操作

  5. 执行面板的执行模式部分中,选择单元测试

  6. 从下列选项中选择一项:

    • 选择单元测试:运行您手动选择的单元测试。
    • 选择已标记的单元测试:运行具有所选标记的单元测试。
    • 所有单元测试:运行工作区中的所有单元测试。
  7. 可选:在执行选项部分,选中作为高优先级的交互式作业执行复选框,以立即运行单元测试,优先考虑执行速度。

    如果您未选中作为高优先级的交互式作业执行复选框,Dataform 默认会使用批处理资源运行单元测试,优先考虑节省计算费用。

  8. 点击开始执行

API

如需以编程方式运行单元测试,请使用 WorkflowInvocations.create 方法创建工作流调用,并在 invocationConfig 对象中设置以下单元测试执行参数:

"executionMode": "UNIT_TESTS_ONLY"
此参数设置为 "UNIT_TESTS_ONLY" 时,会触发执行代码库中定义的单元测试。
可选:"queryPriority": "INTERACTIVE"
如果此参数设置为 "INTERACTIVE",Dataform 会立即运行查询。 如果未设置,Dataform 会以默认的批量查询优先级运行单元测试。
可选:"includedTargets": []
借助此参数,您可以指定单元测试,以便 Dataform 仅运行这些测试。
可选:"includedTags": []
借助此参数,您可以指定标记,以便 Dataform 仅运行带有这些标记的单元测试。

以下代码示例展示了工作流调用的正文,该调用使用默认的批处理查询优先级运行 my-repo 代码库中定义的所有单元测试:

{
  "compilationResult": "projects/my-project/locations/us/repositories/my-repo/compilationResults/my-compilation-id",
  "invocationConfig": {
    "executionMode": "UNIT_TESTS_ONLY"
  }
}

以下代码示例展示了工作流调用的正文,该调用仅运行具有交互式查询优先级的 my-test 单元测试:

{
  "compilationResult": "projects/my-project/locations/us/repositories/my-repo/compilationResults/my-compilation-id",
  "invocationConfig": {
    "executionMode": "UNIT_TESTS_ONLY",
    "queryPriority": "INTERACTIVE",
    "includedTargets": [
      {
        "database": "my-project",
        "schema": "my-dataset",
        "name": "my-test"
      }
    ]
  }
}

以下代码示例展示了工作流调用的正文,该调用用于运行 my-repo 代码库中带有 test-tag-1test-tag-2 标记的单元测试:

{
  "compilationResult": "projects/my-project/locations/us/repositories/my-repo/compilationResults/my-compilation-id",
  "invocationConfig": {
    "executionMode": "UNIT_TESTS_ONLY",
    "queryPriority": "INTERACTIVE",
    "includedTags": [
      "test-tag-1",
      "test-tag-2"
    ]
  }
}

检查单元测试结果

您可以在已编译的图执行中检查单元测试的预期脚本与实际脚本之间的差异。

已编译的图表

如需在工作流操作的已编译图中查看单元测试的实际脚本和预期脚本,请按以下步骤操作:

  1. 在 Google Cloud 控制台中,前往 Dataform 页面。

    前往 Dataform 页面

  2. 选择一个代码库。

  3. 选择开发工作区。

  4. 可选:如需查看与所测试操作相关联的单元测试,而不是将它们视为独立的图节点,请在 workflow_settings.yaml 文件中将 includeTestsInCompiledGraph 设置为 true

    1. 选择 workflow_settings.yaml 文件。
    2. 添加以下代码:
    includeTestsInCompiledGraph: true
    
  5. 点击已编译的图表

  6. 在编译后的图中,选择一个单元测试,然后点击查询

  7. 比较实际 SQL 脚本预期 SQL 脚本

执行

  1. 在 Google Cloud 控制台中,前往 Dataform 页面。

    前往 Dataform 页面

  2. 选择一个代码库。

  3. 选择开发工作区。

  4. 点击执行,然后点击所选单元测试旁边的查看详情

  5. 比较实际结果查询预期结果查询

单元测试最佳实践

保持模拟数据集较小
将模拟输入数据保持在 10 行以下,以便更快地进行编译并更轻松地进行调试。
指定明确的行顺序
请务必在操作查询和预期输出查询中都添加 ORDER BY 子句,以确保在评估期间确定行顺序。
在模拟语句中显式转换列
明确转换模拟语句中的列(例如,使用 CAST(100 AS INT64))可保持类型严格性并防止出现编译错误。
包含具有 NULL 或缺失值的测试用例
在输入模拟查询中包含具有 NULL 或缺失值的测试用例,可确保 COALESCE 语句、字符串操作和过滤条件能够安全地处理不完整或为 null 的生产数据。

以下代码示例展示了一个 NULL 测试用例:

input "source_customers" {
  SELECT 101 AS customer_id, 'Alice' AS name UNION ALL
  SELECT 102 AS customer_id, NULL AS name -- Test null handling
}

后续步骤