Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Unit testing feature branch pull request #8411

Merged
merged 41 commits into from
Jan 16, 2024
Merged
Show file tree
Hide file tree
Changes from 1 commit
Commits
Show all changes
41 commits
Select commit Hold shift + click to select a range
181f520
Initial implementation of unit testing (from pr #2911)
gshank Aug 14, 2023
24abc37
Merge branch 'main' into unit_testing_feature_branch
gshank Aug 23, 2023
7ea7069
Merge branch 'main' into unit_testing_feature_branch
gshank Aug 28, 2023
42e66fd
8295 unit testing artifacts (#8477)
gshank Aug 29, 2023
b3bcbd5
Merge branch 'main' into unit_testing_feature_branch
gshank Aug 30, 2023
1e64f94
Merge branch 'main' into unit_testing_feature_branch
gshank Aug 31, 2023
120b36e
Merge branch 'main' into unit_testing_feature_branch
gshank Sep 7, 2023
2b376d9
Merge branch 'main' into unit_testing_feature_branch
gshank Sep 11, 2023
12342ca
unit test config: tags & meta (#8565)
MichelleArk Sep 12, 2023
c48e34c
Add additional functional test for unit testing selection, artifacts,…
gshank Sep 13, 2023
3dbf095
Merge branch 'main' into unit_testing_feature_branch
gshank Sep 13, 2023
08ef90a
Merge branch 'main' into unit_testing_feature_branch
gshank Sep 22, 2023
ac719e4
Merge branch 'main' into unit_testing_feature_branch
gshank Sep 26, 2023
bb6fd30
Merge branch 'main' into unit_testing_feature_branch
gshank Oct 2, 2023
5cafb96
Merge branch 'main' into unit_testing_feature_branch
gshank Oct 5, 2023
3b6f9bd
Enable inline csv format in unit testing (#8743)
gshank Oct 5, 2023
df4e4ed
Merge branch 'main' into unit_testing_feature_branch
gshank Oct 12, 2023
f77c226
Merge branch 'main' into unit_testing_feature_branch
gshank Nov 1, 2023
aa91ea4
Support unit testing incremental models (#8891)
MichelleArk Nov 2, 2023
02a3dc5
update unit test key: unit -> unit-tests (#8988)
emmyoop Nov 3, 2023
f629baa
convert to use unit test name at top level key (#8966)
emmyoop Nov 3, 2023
2792e0c
Merge branch 'main' into unit_testing_feature_branch
gshank Nov 8, 2023
3b033ac
csv file fixtures (#9044)
emmyoop Nov 9, 2023
ebf48d2
Unit test support for `state:modified` and `--defer` (#9032)
jtcohen6 Nov 14, 2023
436dae6
Merge branch 'main' into unit_testing_feature_branch
gshank Nov 14, 2023
c6be2d2
Allow use of sources as unit testing inputs (#9059)
gshank Nov 15, 2023
35f579e
Use daff for diff formatting in unit testing (#8984)
MichelleArk Nov 15, 2023
3432436
Fix #8652: Use seed file from disk for unit testing if rows not speci…
aranke Nov 16, 2023
964a728
Merge branch 'main' into unit_testing_feature_branch
gshank Nov 16, 2023
a559259
Merge branch 'unit_testing_feature_branch' of github.com:dbt-labs/dbt…
gshank Nov 16, 2023
e001991
Merge branch 'main' into unit_testing_feature_branch
gshank Nov 21, 2023
3f1ed23
Move unit testing to test and build commands (#9108)
gshank Nov 27, 2023
bf6bffa
Merge branch 'main' into unit_testing_feature_branch
gshank Nov 30, 2023
ca82f54
Enable unit testing in non-root packages (#9184)
gshank Nov 30, 2023
a570a2c
convert test to data_test (#9201)
emmyoop Dec 7, 2023
9a79fba
Make fixtures files full-fledged members of manifest and enable parti…
gshank Dec 7, 2023
4e87f46
Merge branch 'main' into unit_testing_feature_branch
gshank Dec 7, 2023
a0177e3
In build command run unit tests before models (#9273)
gshank Dec 20, 2023
56dfb34
Merge branch 'main' into unit_testing_feature_branch
gshank Jan 2, 2024
9160738
Merge branch 'main' into unit_testing_feature_branch
gshank Jan 12, 2024
42b0b85
Merge branch 'main' into unit_testing_feature_branch
gshank Jan 16, 2024
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
Prev Previous commit
Next Next commit
unit test config: tags & meta (#8565)
  • Loading branch information
MichelleArk authored Sep 12, 2023
commit 12342ca92b2b1ebb6a140010b69bf739b9b474d2
6 changes: 6 additions & 0 deletions .changes/unreleased/Features-20230906-234741.yaml
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
kind: Features
body: Support config with tags & meta for unit tests
time: 2023-09-06T23:47:41.059915-04:00
custom:
Author: michelleark
Issue: "8294"
5 changes: 5 additions & 0 deletions core/dbt/config/project.py
Original file line number Diff line number Diff line change
Expand Up @@ -425,6 +425,7 @@ def create_project(self, rendered: RenderComponents) -> "Project":
snapshots: Dict[str, Any]
sources: Dict[str, Any]
tests: Dict[str, Any]
unit_tests: Dict[str, Any]
metrics: Dict[str, Any]
semantic_models: Dict[str, Any]
exposures: Dict[str, Any]
Expand All @@ -437,6 +438,7 @@ def create_project(self, rendered: RenderComponents) -> "Project":
snapshots = cfg.snapshots
sources = cfg.sources
tests = cfg.tests
unit_tests = cfg.unit_tests
metrics = cfg.metrics
semantic_models = cfg.semantic_models
exposures = cfg.exposures
Expand Down Expand Up @@ -496,6 +498,7 @@ def create_project(self, rendered: RenderComponents) -> "Project":
query_comment=query_comment,
sources=sources,
tests=tests,
unit_tests=unit_tests,
metrics=metrics,
semantic_models=semantic_models,
exposures=exposures,
Expand Down Expand Up @@ -604,6 +607,7 @@ class Project:
snapshots: Dict[str, Any]
sources: Dict[str, Any]
tests: Dict[str, Any]
unit_tests: Dict[str, Any]
metrics: Dict[str, Any]
semantic_models: Dict[str, Any]
exposures: Dict[str, Any]
Expand Down Expand Up @@ -681,6 +685,7 @@ def to_project_config(self, with_packages=False):
"snapshots": self.snapshots,
"sources": self.sources,
"tests": self.tests,
"unit_tests": self.unit_tests,
"metrics": self.metrics,
"semantic-models": self.semantic_models,
"exposures": self.exposures,
Expand Down
2 changes: 2 additions & 0 deletions core/dbt/config/runtime.py
Original file line number Diff line number Diff line change
Expand Up @@ -166,6 +166,7 @@ def from_parts(
query_comment=project.query_comment,
sources=project.sources,
tests=project.tests,
unit_tests=project.unit_tests,
metrics=project.metrics,
semantic_models=project.semantic_models,
exposures=project.exposures,
Expand Down Expand Up @@ -323,6 +324,7 @@ def get_resource_config_paths(self) -> Dict[str, PathSet]:
"snapshots": self._get_config_paths(self.snapshots),
"sources": self._get_config_paths(self.sources),
"tests": self._get_config_paths(self.tests),
"unit_tests": self._get_config_paths(self.unit_tests),
"metrics": self._get_config_paths(self.metrics),
"semantic_models": self._get_config_paths(self.semantic_models),
"exposures": self._get_config_paths(self.exposures),
Expand Down
4 changes: 4 additions & 0 deletions core/dbt/context/context_config.py
Original file line number Diff line number Diff line change
Expand Up @@ -49,6 +49,8 @@ def get_config_dict(self, resource_type: NodeType) -> Dict[str, Any]:
model_configs = unrendered.get("semantic_models")
elif resource_type == NodeType.Exposure:
model_configs = unrendered.get("exposures")
elif resource_type == NodeType.Unit:
model_configs = unrendered.get("unit_tests")
else:
model_configs = unrendered.get("models")
if model_configs is None:
Expand Down Expand Up @@ -76,6 +78,8 @@ def get_config_dict(self, resource_type: NodeType) -> Dict[str, Any]:
model_configs = self.project.semantic_models
elif resource_type == NodeType.Exposure:
model_configs = self.project.exposures
elif resource_type == NodeType.Unit:
model_configs = self.project.unit_tests
else:
model_configs = self.project.models
return model_configs
Expand Down
2 changes: 1 addition & 1 deletion core/dbt/context/providers.py
Original file line number Diff line number Diff line change
Expand Up @@ -1504,7 +1504,7 @@ def defer_relation(self) -> Optional[RelationProxy]:
class UnitTestContext(ModelContext):
model: UnitTestNode

@contextmember
@contextmember()
def env_var(self, var: str, default: Optional[str] = None) -> str:
"""The env_var() function. Return the overriden unit test environment variable named 'var'.

Expand Down
13 changes: 13 additions & 0 deletions core/dbt/contracts/graph/model_config.py
Original file line number Diff line number Diff line change
Expand Up @@ -640,6 +640,18 @@ def finalize_and_validate(self):
return self.from_dict(data)


@dataclass
class UnitTestConfig(BaseConfig):
tags: Union[str, List[str]] = field(
default_factory=list_str,
metadata=metas(ShowBehavior.Hide, MergeBehavior.Append, CompareBehavior.Exclude),
)
meta: Dict[str, Any] = field(
default_factory=dict,
metadata=MergeBehavior.Update.meta(),
)


RESOURCE_TYPES: Dict[NodeType, Type[BaseConfig]] = {
NodeType.Metric: MetricConfig,
NodeType.SemanticModel: SemanticModelConfig,
Expand All @@ -650,6 +662,7 @@ def finalize_and_validate(self):
NodeType.Unit: TestConfig,
NodeType.Model: NodeConfig,
NodeType.Snapshot: SnapshotConfig,
NodeType.Unit: UnitTestConfig,
}


Expand Down
17 changes: 11 additions & 6 deletions core/dbt/contracts/graph/nodes.py
Original file line number Diff line number Diff line change
Expand Up @@ -40,7 +40,10 @@
from dbt.contracts.graph.node_args import ModelNodeArgs
from dbt.contracts.util import Replaceable, AdditionalPropertiesMixin
from dbt.events.functions import warn_or_error
from dbt.exceptions import ParsingError, ContractBreakingChangeError
from dbt.exceptions import (
ParsingError,
ContractBreakingChangeError,
)
from dbt.events.types import (
SeedIncreased,
SeedExceedsLimitSamePath,
Expand Down Expand Up @@ -73,6 +76,7 @@
EmptySnapshotConfig,
SnapshotConfig,
SemanticModelConfig,
UnitTestConfig,
)


Expand Down Expand Up @@ -1062,17 +1066,22 @@ class UnitTestNode(CompiledNode):
@dataclass
class UnitTestDefinition(GraphNode):
model: str
attached_node: str
given: Sequence[InputFixture]
expect: List[Dict[str, Any]]
description: str = ""
overrides: Optional[UnitTestOverrides] = None
depends_on: DependsOn = field(default_factory=DependsOn)
config: UnitTestConfig = field(default_factory=UnitTestConfig)

@property
def depends_on_nodes(self):
return self.depends_on.nodes

@property
def tags(self) -> List[str]:
tags = self.config.tags
return [tags] if isinstance(tags, str) else tags


# ====================================
# Snapshot node
Expand Down Expand Up @@ -1699,10 +1708,6 @@ def primary_entity_reference(self) -> Optional[EntityReference]:
else None
)

@property
def group(self):
return None


# ====================================
# Patches
Expand Down
1 change: 1 addition & 0 deletions core/dbt/contracts/graph/unparsed.py
Original file line number Diff line number Diff line change
Expand Up @@ -750,6 +750,7 @@ class UnparsedUnitTestDefinition(dbtClassMixin):
expect: List[Dict[str, Any]]
description: str = ""
overrides: Optional[UnitTestOverrides] = None
config: Dict[str, Any] = field(default_factory=dict)


@dataclass
Expand Down
1 change: 1 addition & 0 deletions core/dbt/contracts/project.py
Original file line number Diff line number Diff line change
Expand Up @@ -213,6 +213,7 @@ class Project(dbtClassMixin, Replaceable):
analyses: Dict[str, Any] = field(default_factory=dict)
sources: Dict[str, Any] = field(default_factory=dict)
tests: Dict[str, Any] = field(default_factory=dict)
unit_tests: Dict[str, Any] = field(default_factory=dict)
metrics: Dict[str, Any] = field(default_factory=dict)
semantic_models: Dict[str, Any] = field(default_factory=dict)
exposures: Dict[str, Any] = field(default_factory=dict)
Expand Down
115 changes: 72 additions & 43 deletions core/dbt/parser/unit_tests.py
Original file line number Diff line number Diff line change
@@ -1,39 +1,34 @@
from typing import List, Set, Dict, Any

from dbt.config import RuntimeConfig
from dbt.context.context_config import ContextConfig
from dbt.context.providers import generate_parse_exposure, get_rendered
from dbt.contracts.files import FileHash
from dbt.contracts.graph.manifest import Manifest
from dbt.contracts.graph.model_config import NodeConfig
from dbt_extractor import py_extract_from_source # type: ignore
from dbt.contracts.graph.unparsed import UnparsedUnitTestSuite
from dbt.contracts.graph.nodes import (
ModelNode,
UnitTestNode,
RefArgs,
UnitTestDefinition,
DependsOn,
UnitTestConfig,
)
from dbt.config import RuntimeConfig
from dbt.contracts.graph.manifest import Manifest
from dbt.contracts.graph.unparsed import UnparsedUnitTestSuite
from dbt.exceptions import ParsingError
from dbt.graph import UniqueId
from dbt.node_types import NodeType
from dbt.parser.schemas import (
SchemaParser,
YamlBlock,
ValidationError,
JSONValidationError,
YamlParseDictError,
YamlReader,
ParseResult,
)
from dbt.node_types import NodeType

from dbt.exceptions import (
ParsingError,
)

from dbt.contracts.files import FileHash
from dbt.graph import UniqueId

from dbt.context.providers import generate_parse_exposure, get_rendered
from typing import List, Set
from dbt.utils import get_pseudo_test_path


def _is_model_node(node_id, manifest):
return manifest.nodes[node_id].resource_type == NodeType.Model
from dbt_extractor import py_extract_from_source # type: ignore


class UnitTestManifestLoader:
Expand Down Expand Up @@ -176,43 +171,77 @@ def _get_original_input_node(self, input: str):


class UnitTestParser(YamlReader):
def __init__(self, schema_parser: SchemaParser, yaml: YamlBlock):
def __init__(self, schema_parser: SchemaParser, yaml: YamlBlock) -> None:
super().__init__(schema_parser, yaml, "unit")
self.schema_parser = schema_parser
self.yaml = yaml

def parse(self):
def parse(self) -> ParseResult:
for data in self.get_key_dicts():
try:
UnparsedUnitTestSuite.validate(data)
unparsed = UnparsedUnitTestSuite.from_dict(data)
except (ValidationError, JSONValidationError) as exc:
raise YamlParseDictError(self.yaml.path, self.key, data, exc)
package_name = self.project.project_name

actual_node = self.manifest.ref_lookup.perform_lookup(
f"model.{package_name}.{unparsed.model}", self.manifest
)
if not actual_node:
raise ParsingError(
"Unable to find model {unparsed.model} for unit tests in {self.yaml.path.original_file_path}"
unit_test_suite = self._get_unit_test_suite(data)
model_name_split = unit_test_suite.model.split()
tested_model_node = self._find_tested_model_node(unit_test_suite)

for test in unit_test_suite.tests:
unit_test_case_unique_id = (
f"unit.{self.project.project_name}.{unit_test_suite.model}.{test.name}"
)
for test in unparsed.tests:
unit_test_case_unique_id = f"unit.{package_name}.{test.name}.{unparsed.model}"
unit_test_case = UnitTestDefinition(
unit_test_fqn = [self.project.project_name] + model_name_split + [test.name]
unit_test_config = self._build_unit_test_config(unit_test_fqn, test.config)

unit_test_definition = UnitTestDefinition(
name=test.name,
model=unparsed.model,
model=unit_test_suite.model,
resource_type=NodeType.Unit,
package_name=package_name,
package_name=self.project.project_name,
path=self.yaml.path.relative_path,
original_file_path=self.yaml.path.original_file_path,
unique_id=unit_test_case_unique_id,
attached_node=actual_node.unique_id,
given=test.given,
expect=test.expect,
description=test.description,
overrides=test.overrides,
depends_on=DependsOn(nodes=[actual_node.unique_id]),
fqn=[package_name, test.name],
depends_on=DependsOn(nodes=[tested_model_node.unique_id]),
fqn=unit_test_fqn,
config=unit_test_config,
)
self.manifest.add_unit_test(self.yaml.file, unit_test_case)
self.manifest.add_unit_test(self.yaml.file, unit_test_definition)

return ParseResult()

def _get_unit_test_suite(self, data: Dict[str, Any]) -> UnparsedUnitTestSuite:
try:
UnparsedUnitTestSuite.validate(data)
return UnparsedUnitTestSuite.from_dict(data)
except (ValidationError, JSONValidationError) as exc:
raise YamlParseDictError(self.yaml.path, self.key, data, exc)

def _find_tested_model_node(self, unit_test_suite: UnparsedUnitTestSuite) -> ModelNode:
package_name = self.project.project_name
model_name_split = unit_test_suite.model.split()
model_name = model_name_split[0]
model_version = model_name_split[1] if len(model_name_split) == 2 else None

tested_node = self.manifest.ref_lookup.find(
model_name, package_name, model_version, self.manifest
)
if not tested_node:
raise ParsingError(
f"Unable to find model '{package_name}.{unit_test_suite.model}' for unit tests in {self.yaml.path.original_file_path}"
)

return tested_node

def _build_unit_test_config(
self, unit_test_fqn: List[str], config_dict: Dict[str, Any]
) -> UnitTestConfig:
config = ContextConfig(
self.schema_parser.root_project,
unit_test_fqn,
NodeType.Unit,
self.schema_parser.project.project_name,
)
unit_test_config_dict = config.build_config_dict(patch_config_dict=config_dict)
unit_test_config_dict = self.render_entry(unit_test_config_dict)

return UnitTestConfig.from_dict(unit_test_config_dict)
Loading