Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
42 changes: 42 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -26,6 +26,9 @@ Macros that generate dbt code, and log it to the command line.
- [generate_model_import_ctes (source)](#generate_model_import_ctes-source)
- [Arguments:](#arguments-5)
- [Usage:](#usage-5)
- [generate_unit_test_template (source)](#generate_unit_test_template-source)
- [Arguments:](#arguments-6)
- [Usage:](#usage-6)
- [Contributing](#contributing)

# Installation instructions
Expand Down Expand Up @@ -394,6 +397,45 @@ select * from final

4. Replace the contents of the model's current SQL file with the compiled or logged code

## generate_unit_test_template ([source](macros/generate_unit_test_template.sql))

This macro generates the unit testing YAML for a given model with all references included as `given` inputs (along with their columns), plus the columns within the expected output.

### Arguments:

- `model_name` (required): The model you wish to generate unit testing YAML for.
- `inline_columns` (optional, default=False): Whether you want all columns on the same line.

### Usage:

1. Create a model with your original SQL query
2. Call the macro as an [operation](https://docs.getdbt.com/docs/using-operations):

```
$ dbt run-operation generate_unit_test_template --args '{"model_name": "order_items", "inline_columns": true}'
```

3. The new YAML - with all given inputs included - will be logged to the command line

```yaml
unit_tests:
- name: unit_test_order_items
model: order_items

given:
- input: ref("stg_order_items")
rows:
- col_a:
col_b:

expect:
rows:
- id:
```

4. Create a new YAML file with the compiled or logged code.
5. Add column values for the given inputs and expected output.

## Contributing

To contirbute code to this package, please follow the steps outlined in the `integration_tests` directory's [README](https://github.com/dbt-labs/dbt-codegen/blob/main/integration_tests/README.md) file.
5 changes: 5 additions & 0 deletions integration_tests/models/model_incremental.sql
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
{{ config(
materialized='incremental'
) }}

select 1 as id
28 changes: 28 additions & 0 deletions integration_tests/tests/test_generate_unit_test_template.sql
Original file line number Diff line number Diff line change
@@ -0,0 +1,28 @@
{% set actual_model_yaml = codegen.generate_unit_test_template(
model_name='child_model',
inline_columns=False
)
%}

-- depends_on: {{ ref('model_data_a') }}
-- depends_on: {{ ref('child_model') }}

{% set expected_model_yaml %}
unit_tests:
- name: unit_test_child_model
model: child_model

given:
- input: ref("model_data_a")
rows:
- col_a:
col_b:

expect:
rows:
- col_a:
col_b:

{% endset %}

{{ assert_equal (actual_model_yaml | trim, expected_model_yaml | trim) }}
Original file line number Diff line number Diff line change
@@ -0,0 +1,28 @@
{% set actual_model_yaml = codegen.generate_unit_test_template(
model_name='model_incremental',
)
%}

-- depends_on: {{ ref('model_incremental') }}

{% set expected_model_yaml %}
unit_tests:
- name: unit_test_model_incremental
model: model_incremental

overrides:
macros:
is_incremental: true

given:
- input: this
rows:
- id:

expect:
rows:
- id:

{% endset %}

{{ assert_equal (actual_model_yaml | trim, expected_model_yaml | trim) }}
Original file line number Diff line number Diff line change
@@ -0,0 +1,26 @@
{% set actual_model_yaml = codegen.generate_unit_test_template(
model_name='child_model',
inline_columns=True
)
%}

-- depends_on: {{ ref('model_data_a') }}
-- depends_on: {{ ref('child_model') }}

{% set expected_model_yaml %}
unit_tests:
- name: unit_test_child_model
model: child_model

given:
- input: ref("model_data_a")
rows:
- {col_a: , col_b: }

expect:
rows:
- {col_a: , col_b: }

{% endset %}

{{ assert_equal (actual_model_yaml | trim, expected_model_yaml | trim) }}
Original file line number Diff line number Diff line change
@@ -0,0 +1,26 @@
{% set actual_model_yaml = codegen.generate_unit_test_template(
model_name='model_from_source',
)
%}

-- depends_on: {{ ref('model_from_source') }}

{% set expected_model_yaml %}
unit_tests:
- name: unit_test_model_from_source
model: model_from_source

given:
- input: source("codegen_integration_tests__data_source_schema", "codegen_integration_tests__data_source_table")
rows:
- my_integer_col:
my_bool_col:

expect:
rows:
- my_integer_col:
my_bool_col:

{% endset %}

{{ assert_equal (actual_model_yaml | trim, expected_model_yaml | trim) }}
Original file line number Diff line number Diff line change
@@ -0,0 +1,23 @@
{% set actual_model_yaml = codegen.generate_unit_test_template(
model_name='data__a_relation',
inline_columns=False
)
%}

-- depends_on: {{ ref('data__a_relation') }}

{% set expected_model_yaml %}
unit_tests:
- name: unit_test_data__a_relation
model: data__a_relation

given: []

expect:
rows:
- col_a:
col_b:

{% endset %}

{{ assert_equal (actual_model_yaml | trim, expected_model_yaml | trim) }}
3 changes: 2 additions & 1 deletion integration_tests/tests/test_helper_get_models.sql
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
-- depends_on: {{ ref('model_data_a') }}
-- depends_on: {{ ref('model_incremental') }}
-- depends_on: {{ ref('model_struct') }}
-- depends_on: {{ ref('model_without_import_ctes') }}
-- depends_on: {{ ref('model_without_any_ctes') }}
Expand All @@ -7,6 +8,6 @@
{% set actual_list = codegen.get_models(prefix='model_')|sort %}
{% endif %}

{% set expected_list = ['model_data_a', 'model_from_source', 'model_repeated', 'model_struct', 'model_without_any_ctes', 'model_without_import_ctes'] %}
{% set expected_list = ['model_data_a', 'model_from_source', 'model_incremental', 'model_repeated', 'model_struct', 'model_without_any_ctes', 'model_without_import_ctes'] %}

{{ assert_equal (actual_list, expected_list) }}
151 changes: 151 additions & 0 deletions macros/generate_unit_test_template.sql
Original file line number Diff line number Diff line change
@@ -0,0 +1,151 @@
{% macro generate_unit_test_template(model_name, inline_columns=false) %}
{{ return(adapter.dispatch('generate_unit_test_template', 'codegen')(model_name, inline_columns)) }}
{% endmacro %}

{% macro default__generate_unit_test_template(model_name, inline_columns=false) %}

{%- set ns = namespace(depends_on_list = []) -%}

{%- if execute -%}

-- getting inputs and materialization info
{%- for node in graph.nodes.values()
| selectattr("resource_type", "equalto", "model")
| selectattr("name", "equalto", model_name) -%}
{%- set ns.depends_on_list = ns.depends_on_list + node.depends_on.nodes -%}
{%- set ns.this_materialization = node.config['materialized'] -%}
{%- endfor -%}

{%- endif -%}

-- getting the input columns
{%- set ns.input_columns_list = [] -%}
{%- for item in ns.depends_on_list -%}
{%- set input_columns_list = [] -%}
{%- set item_dict = codegen.get_resource_from_unique_id(item) -%}
{%- if item_dict.resource_type == 'source' %}
{%- set columns = adapter.get_columns_in_relation(source(item_dict.source_name, item_dict.identifier)) -%}
{%- else -%}
{%- set columns = adapter.get_columns_in_relation(ref(item_dict.alias)) -%}
{%- endif -%}
{%- for column in columns -%}
{{ input_columns_list.append(column.name) }}
{%- endfor -%}
{{ ns.input_columns_list.append(input_columns_list) }}
{%- endfor -%}

-- getting 'this' columns
{% set relation_exists = load_relation(ref(model_name)) is not none %}
{% if relation_exists %}
{%- set ns.expected_columns_list = [] -%}
{%- set columns = adapter.get_columns_in_relation(ref(model_name)) -%}
{%- for column in columns -%}
{{ ns.expected_columns_list.append(column.name) }}
{%- endfor -%}
{% endif %}

{%- set unit_test_yaml_template -%}
unit_tests:
- name: unit_test_{{ model_name }}
model: {{ model_name }}
{% if ns.this_materialization == 'incremental' %}
overrides:
macros:
is_incremental: true
{% else -%}

{%- endif %}
given: {%- if ns.depends_on_list|length == 0 and ns.this_materialization != 'incremental' %} []{% endif %}
{%- for i in range(ns.depends_on_list|length) -%}
{%- set item_dict = codegen.get_resource_from_unique_id(ns.depends_on_list[i]) -%}
{% if item_dict.resource_type == 'source' %}
- input: source("{{item_dict.source_name}}", "{{item_dict.identifier}}")
rows:
{%- else %}
- input: ref("{{item_dict.alias}}")
rows:
{%- endif -%}
{%- if inline_columns -%}
{%- set ns.column_string = '- {' -%}
{%- for column_name in ns.input_columns_list[i] -%}
{%- if not loop.last -%}
{%- set ns.column_string = ns.column_string ~ column_name ~ ': , ' -%}
{%- else -%}
{%- set ns.column_string = ns.column_string ~ column_name ~ ': }' -%}
{%- endif -%}
{% endfor %}
{%- else -%}
{%- set ns.column_string = '' -%}
{%- for column_name in ns.input_columns_list[i] -%}
{%- if loop.first -%}
{%- set ns.column_string = ns.column_string ~ '- ' ~ column_name ~ ': ' -%}
{%- else -%}
{%- set ns.column_string = ns.column_string ~ '\n ' ~ column_name ~ ': ' -%}
{%- endif -%}
{% endfor %}
{%- endif %}
{{ns.column_string}}
{%- endfor %}

{%- if ns.this_materialization == 'incremental' %}
- input: this
rows:
{%- if relation_exists -%}
{%- if inline_columns -%}
{%- set ns.column_string = '- {' -%}
{%- for column_name in ns.expected_columns_list -%}
{%- if not loop.last -%}
{%- set ns.column_string = ns.column_string ~ column_name ~ ': , ' -%}
{%- else -%}
{%- set ns.column_string = ns.column_string ~ column_name ~ ': }' -%}
{%- endif -%}
{% endfor %}
{%- else -%}
{%- set ns.column_string = '' -%}
{%- for column_name in ns.expected_columns_list -%}
{%- if loop.first -%}
{%- set ns.column_string = ns.column_string ~ '- ' ~ column_name ~ ': ' -%}
{%- else -%}
{%- set ns.column_string = ns.column_string ~ '\n ' ~ column_name ~ ': ' -%}
{%- endif -%}
{% endfor %}
{%- endif %}
{{ns.column_string}}
{%- endif %}
{%- endif %}

expect:
rows:
{%- if relation_exists -%}
{%- if inline_columns -%}
{%- set ns.column_string = '- {' -%}
{%- for column_name in ns.expected_columns_list -%}
{%- if not loop.last -%}
{%- set ns.column_string = ns.column_string ~ column_name ~ ': , ' -%}
{%- else -%}
{%- set ns.column_string = ns.column_string ~ column_name ~ ': }' -%}
{%- endif -%}
{% endfor %}
{%- else -%}
{%- set ns.column_string = '' -%}
{%- for column_name in ns.expected_columns_list -%}
{%- if loop.first -%}
{%- set ns.column_string = ns.column_string ~ '- ' ~ column_name ~ ': ' -%}
{%- else -%}
{%- set ns.column_string = ns.column_string ~ '\n ' ~ column_name ~ ': ' -%}
{%- endif -%}
{% endfor %}
{%- endif %}
{{ns.column_string}}
{%- endif -%}

{%- endset -%}

{% if execute %}

{{ print(unit_test_yaml_template) }}
{% do return(unit_test_yaml_template) %}

{% endif %}

{% endmacro %}
17 changes: 16 additions & 1 deletion macros/helpers/helpers.sql
Original file line number Diff line number Diff line change
Expand Up @@ -48,7 +48,7 @@
{% set model_path = "/".join(model.path.split("/")[:-1]) %}
{% if model_path == directory and model.name.startswith(prefix) %}
{% do model_names.append(model.name) %}
{% endif %}
{% endif %}
{% endfor %}
{% elif directory %}
{% for model in models %}
Expand Down Expand Up @@ -88,3 +88,18 @@
{% set formatted = codegen.format_column(column) %}
{{ return(formatted['data_type'] | lower) }}
{% endmacro %}

{# retrieve entire resource dictionary based on unique id #}
{% macro get_resource_from_unique_id(resource_unique_id) %}
{% set resource_type = resource_unique_id.split('.')[0] %}
{% if resource_type == 'source' %}
{% set resource = graph.sources[resource_unique_id] %}
{% elif resource_type == 'exposure' %}
{% set resource = graph.exposure[resource_unique_id] %}
{% elif resource_type == 'metric' %}
{% set resource = graph.metrics[resource_unique_id] %}
{% else %}
{% set resource = graph.nodes[resource_unique_id] %}
{% endif %}
{{ return(resource) }}
{% endmacro %}
Loading