测试
简介¶
测试是您对dbt项目中的模型和其他资源(例如源、种子和快照)所做的断言。当您运行dbt test时,dbt会告诉您项目中的每个测试是通过还是失败。
通过对生成的结果进行断言,可以使用测试来提高每个模型中SQL的完整性。开箱即用,您可以测试模型中的指定列是否只包含非null值、唯一值或在另一个模型中具有相应值的值(例如,订单的customer_id对应于客户模型中的id),以及指定列表中的值。您可以扩展测试以适应特定于组织的业务逻辑,您可以以一个select查询的形式对模型做出的任何断言都可以转化为测试。
与dbt中的几乎所有内容一样,测试都是SQL查询。特别是,它们是select语句,试图获取失败的记录,来反驳你的断言。如果断言一个字段在模型中是唯一的,则测试查询会选择重复列;如果断言字段从不为null,那么测试将查找null。如果测试返回零个失败行,那么它就通过了,并且您的断言已经得到验证。
在dbt中定义测试有两种方法:
- 单例 测试是以最简单的形式进行测试的: 如果您可以编写返回失败行的SQL查询,则可以将该查询保存在test目录中的
.sql文件中。它将由dbt test命令执行。 - 通用 测试是一个接受参数的查询。测试查询是在一个特殊的
测试块(如宏)中定义的。定义后,您可以在整个.yml文件中按名称引用通用测试,这些文件在模型、列、源、快照和种子上定义通用测试。dbt附带了四个内置的通用测试,我们认为您应该使用它们!
定义测试是确认代码正常工作的好方法,有助于防止代码更改时出现倒退。因为您可以反复使用它们,通过微小的变化做出类似的断言,所以通用测试往往更常见——它们应该构成dbt测试套件的大部分。也就是说,定义测试的两种方法都有其合适的时候。
创建你的第一个测试
如果您是dbt新手,我们建议您查看我们的quickstart指南,以构建您的第一个带有模型和测试的dbt项目。
单例测试¶
定义测试的最简单方法是编写将返回失败记录的SQL。我们称这些测试为“单例”测试,因为它们是一次性的断言,可用于单一目的。
这些测试在.sql文件中定义,通常在“tests”目录中(由test-paths配置定义)。您可以在测试定义中使用Jinja(包括ref和source),就像创建模型时一样。每个.sql文件包含一个select语句,它定义了一个测试:
-- Refunds have a negative amount, so the total amount should always be >= 0.
-- Therefore return records where this isn't true to make the test fail
select
order_id,
sum(amount) as total_amount
from {{ ref('fct_payments' )}}
group by 1
having not(total_amount >= 0)
此测试的名称是文件的名称:assert_total_payment_amount_is_positive。很简单。
单例测试很容易编写,因此您可能会发现自己一遍又一遍地编写相同的基本结构,只需更改列或模型的名称。到那时,这个测试就不那么单一了!在这种情况下,我们建议。。。
通用测试¶
确定的测试是通用的:它们可以反复使用。通用测试是在“测试”块中定义的,该块包含参数化查询并接受参数。它可能看起来像:
{% test not_null(model, column_name) %}
select *
from {{ model }}
where {{ column_name }} is null
{% endtest %}
您会注意到有两个参数,model和column_name,然后将它们模板化到查询中。这就是为什么测试是“通用的”:它可以在任意多的列上定义,也可以跨任意多的模型定义,dbt将相应地传递model和column_name的值。一旦定义了通用测试,就可以将其添加为任何现有模型(或源、种子或快照)上的_属性_。这些属性被添加到位于同一目录中的.yml文件中。
注意
如果这是您第一次向资源添加属性,请查看声明属性上的文档。
开箱即用,dbt附带了四个已经定义的通用测试:unique(唯一), not_null(非空), accepted_values(可接受值) and relationships(关联关系)。以下是在订单模型上使用这些测试的完整示例
version: 2
models:
- name: orders
columns:
- name: order_id
tests:
- unique
- not_null
- name: status
tests:
- accepted_values:
values: ['placed', 'shipped', 'completed', 'returned']
- name: customer_id
tests:
- relationships:
to: ref('customers')
field: id
简单地说,这些测试翻译为:
unique唯一:order_id字段在orders模型中必须是唯一not_null非空:order_id字段在orders模型中必须是非空值accepted_values可接受值:status字段在orders模型中必须是'placed','shipped','completed', 或者'returned'relationships关联关系: 每个customer_id在orders模型中必须存在一个id在customers表里
在后台,dbt使用通用测试块中的参数化查询为每个测试构建一个select查询。这些查询返回断言为 非 真的行;如果测试返回零行,则断言通过。
您可以在参考章节中找到有关这些测试和其他配置的更多信息(包括严重性 和标记)。
了解更多关于通用测试¶
这四项测试足以让你开始使用测试。你很快就会发现你想使用更广泛的测试——这是一件好事!您也可以从包中安装通用测试,或者编写自己的测试,以便在dbt项目中使用(和重用)。查看自定义通用测试指南以了解更多信息。
注意
在一些开源软件包中定义了一些通用测试,例如dbt-utils和dbt-expectations-跳到packages上的文档以了解更多信息!
示例¶
要将通用测试添加到项目中,请执行以下操作:
- 将
.yml文件添加到models目录,例如models/schema.yml,其中包含以下内容(您可能需要调整现有模型的name:值)
- 运行
dbt test命令:
$ dbt test
Found 3 models, 2 tests, 0 snapshots, 0 analyses, 130 macros, 0 operations, 0 seed files, 0 sources
17:31:05 | Concurrency: 1 threads (target='learn')
17:31:05 |
17:31:05 | 1 of 2 START test not_null_order_order_id..................... [RUN]
17:31:06 | 1 of 2 PASS not_null_order_order_id........................... [PASS in 0.99s]
17:31:06 | 2 of 2 START test unique_order_order_id....................... [RUN]
17:31:07 | 2 of 2 PASS unique_order_order_id............................. [PASS in 0.79s]
17:31:07 |
17:31:07 | Finished running 2 tests in 7.17s.
Completed successfully
Done. PASS=2 WARN=0 ERROR=0 SKIP=0 TOTAL=2
- dbt Cloud: 检查详情选项卡.
- dbt CLI: 检查
target/compiled文件夹
Unique test
select *
from (
select
order_id
from analytics.orders
where order_id is not null
group by order_id
having count(*) > 1
) validation_errors
select *
from (
select
{{ column_name }}
from {{ model }}
where {{ column_name }} is not null
group by {{ column_name }}
having count(*) > 1
) validation_errors
Not null test
存储测试失败用例¶
通常,测试查询会在执行过程中统计失败次数。如果设置了可选的--store failures标记或store_failures配置,dbt将首先将测试查询的结果保存到数据库中的一个表中,然后查询该表以统计失败次数。
此工作流允许您在开发过程中更快地查询和检查失败记录:

请注意,如果您选择存储测试失败:
- 默认情况下,测试结果表是在以
dbt_test__audit为后缀或名称的schema中创建的。可以通过设置schema配置来更改此值。(有关schema命名的更多详细信息,请参阅使用自定义schema。) - 测试的结果将始终 替换 先前的失败。