From 1e4bed0f1bda2cb8b4ccf0a5a080435ca8c77584 Mon Sep 17 00:00:00 2001 From: Quigley Malcolm Date: Wed, 24 Apr 2024 16:37:14 -0700 Subject: [PATCH] Reorganize a number of our unit tests (#9972) * Move `tests/unit/test_yaml_renderer.py` to `tests/unit/parser/test_schema_renderer.py` * Move `tests/unit/test_unit_test_parser.py` to `tests/unit/parser/test_unit_tests.py` * Convert `tests/unit/test_tracking.py` to use pytest fixtures * Delete `tests/unit/test_sql_result.py` as it was moved to `dbt-adapters` * Move `tests/unit/test_semantic_models.py` to `tests/unit/graph/test_nodes.py * Group tests of `SemanticModel` in `test_nodes.py` into a `TestSemanticModel` class * Move `tests/unit/test_selector_errors.py` to `tests/unit/config/test_selectors.py` --- .../test_selectors.py} | 0 tests/unit/graph/test_nodes.py | 106 ++++++++++++++++++ .../test_schema_renderer.py} | 0 .../test_unit_tests.py} | 4 +- tests/unit/test_semantic_models.py | 106 ------------------ tests/unit/test_sql_result.py | 19 ---- tests/unit/test_tracking.py | 39 ++++--- 7 files changed, 127 insertions(+), 147 deletions(-) rename tests/unit/{test_selector_errors.py => config/test_selectors.py} (100%) create mode 100644 tests/unit/graph/test_nodes.py rename tests/unit/{test_yaml_renderer.py => parser/test_schema_renderer.py} (100%) rename tests/unit/{test_unit_test_parser.py => parser/test_unit_tests.py} (98%) delete mode 100644 tests/unit/test_semantic_models.py delete mode 100644 tests/unit/test_sql_result.py diff --git a/tests/unit/test_selector_errors.py b/tests/unit/config/test_selectors.py similarity index 100% rename from tests/unit/test_selector_errors.py rename to tests/unit/config/test_selectors.py diff --git a/tests/unit/graph/test_nodes.py b/tests/unit/graph/test_nodes.py new file mode 100644 index 00000000000..9512e8983ad --- /dev/null +++ b/tests/unit/graph/test_nodes.py @@ -0,0 +1,106 @@ +from copy import deepcopy +import pytest +from typing import List + +from dbt.artifacts.resources import Dimension, Entity, Measure, Defaults +from dbt.contracts.graph.nodes import SemanticModel +from dbt.artifacts.resources.v1.semantic_model import NodeRelation +from dbt.node_types import NodeType +from dbt_semantic_interfaces.references import MeasureReference +from dbt_semantic_interfaces.type_enums import AggregationType, DimensionType, EntityType + + +class TestSemanticModel: + @pytest.fixture(scope="function") + def dimensions(self) -> List[Dimension]: + return [Dimension(name="ds", type=DimensionType)] + + @pytest.fixture(scope="function") + def entities(self) -> List[Entity]: + return [Entity(name="test_entity", type=EntityType.PRIMARY, expr="id")] + + @pytest.fixture(scope="function") + def measures(self) -> List[Measure]: + return [Measure(name="test_measure", agg=AggregationType.COUNT, expr="id")] + + @pytest.fixture(scope="function") + def default_semantic_model( + self, dimensions: List[Dimension], entities: List[Entity], measures: List[Measure] + ) -> SemanticModel: + return SemanticModel( + name="test_semantic_model", + resource_type=NodeType.SemanticModel, + model="ref('test_model')", + package_name="test", + path="test_path", + original_file_path="test_fixture", + unique_id=f"{NodeType.SemanticModel}.test.test_semantic_model", + fqn=[], + defaults=Defaults(agg_time_dimension="ds"), + dimensions=dimensions, + entities=entities, + measures=measures, + node_relation=NodeRelation( + alias="test_alias", schema_name="test_schema", database="test_database" + ), + ) + + def test_checked_agg_time_dimension_for_measure_via_defaults( + self, + default_semantic_model: SemanticModel, + ): + assert default_semantic_model.defaults.agg_time_dimension is not None + measure = default_semantic_model.measures[0] + measure.agg_time_dimension = None + default_semantic_model.checked_agg_time_dimension_for_measure( + MeasureReference(element_name=measure.name) + ) + + def test_checked_agg_time_dimension_for_measure_via_measure( + self, default_semantic_model: SemanticModel + ): + default_semantic_model.defaults = None + measure = default_semantic_model.measures[0] + measure.agg_time_dimension = default_semantic_model.dimensions[0].name + default_semantic_model.checked_agg_time_dimension_for_measure( + MeasureReference(element_name=measure.name) + ) + + def test_checked_agg_time_dimension_for_measure_exception( + self, default_semantic_model: SemanticModel + ): + default_semantic_model.defaults = None + measure = default_semantic_model.measures[0] + measure.agg_time_dimension = None + + with pytest.raises(AssertionError) as execinfo: + default_semantic_model.checked_agg_time_dimension_for_measure( + MeasureReference(measure.name) + ) + + assert ( + f"Aggregation time dimension for measure {measure.name} on semantic model {default_semantic_model.name}" + in str(execinfo.value) + ) + + def test_semantic_model_same_contents(self, default_semantic_model: SemanticModel): + default_semantic_model_copy = deepcopy(default_semantic_model) + + assert default_semantic_model.same_contents(default_semantic_model_copy) + + def test_semantic_model_same_contents_update_model( + self, default_semantic_model: SemanticModel + ): + default_semantic_model_copy = deepcopy(default_semantic_model) + default_semantic_model_copy.model = "ref('test_another_model')" + + assert not default_semantic_model.same_contents(default_semantic_model_copy) + + def test_semantic_model_same_contents_different_node_relation( + self, + default_semantic_model: SemanticModel, + ): + default_semantic_model_copy = deepcopy(default_semantic_model) + default_semantic_model_copy.node_relation.alias = "test_another_alias" + # Relation should not be consided in same_contents + assert default_semantic_model.same_contents(default_semantic_model_copy) diff --git a/tests/unit/test_yaml_renderer.py b/tests/unit/parser/test_schema_renderer.py similarity index 100% rename from tests/unit/test_yaml_renderer.py rename to tests/unit/parser/test_schema_renderer.py diff --git a/tests/unit/test_unit_test_parser.py b/tests/unit/parser/test_unit_tests.py similarity index 98% rename from tests/unit/test_unit_test_parser.py rename to tests/unit/parser/test_unit_tests.py index 9f6fb0ced55..dfcf059dcf2 100644 --- a/tests/unit/test_unit_test_parser.py +++ b/tests/unit/parser/test_unit_tests.py @@ -3,8 +3,8 @@ from dbt.parser import SchemaParser from dbt.parser.unit_tests import UnitTestParser -from .utils import MockNode -from .test_parser import SchemaParserTest, assertEqualNodes +from tests.unit.utils import MockNode +from tests.unit.test_parser import SchemaParserTest, assertEqualNodes from unittest import mock from dbt.contracts.graph.unparsed import UnitTestOutputFixture diff --git a/tests/unit/test_semantic_models.py b/tests/unit/test_semantic_models.py deleted file mode 100644 index 3bd79a679e9..00000000000 --- a/tests/unit/test_semantic_models.py +++ /dev/null @@ -1,106 +0,0 @@ -from copy import deepcopy -import pytest -from typing import List - -from dbt.artifacts.resources import Dimension, Entity, Measure, Defaults -from dbt.contracts.graph.nodes import SemanticModel -from dbt.artifacts.resources.v1.semantic_model import NodeRelation -from dbt.node_types import NodeType -from dbt_semantic_interfaces.references import MeasureReference -from dbt_semantic_interfaces.type_enums import AggregationType, DimensionType, EntityType - - -@pytest.fixture(scope="function") -def dimensions() -> List[Dimension]: - return [Dimension(name="ds", type=DimensionType)] - - -@pytest.fixture(scope="function") -def entities() -> List[Entity]: - return [Entity(name="test_entity", type=EntityType.PRIMARY, expr="id")] - - -@pytest.fixture(scope="function") -def measures() -> List[Measure]: - return [Measure(name="test_measure", agg=AggregationType.COUNT, expr="id")] - - -@pytest.fixture(scope="function") -def default_semantic_model( - dimensions: List[Dimension], entities: List[Entity], measures: List[Measure] -) -> SemanticModel: - return SemanticModel( - name="test_semantic_model", - resource_type=NodeType.SemanticModel, - model="ref('test_model')", - package_name="test", - path="test_path", - original_file_path="test_fixture", - unique_id=f"{NodeType.SemanticModel}.test.test_semantic_model", - fqn=[], - defaults=Defaults(agg_time_dimension="ds"), - dimensions=dimensions, - entities=entities, - measures=measures, - node_relation=NodeRelation( - alias="test_alias", schema_name="test_schema", database="test_database" - ), - ) - - -def test_checked_agg_time_dimension_for_measure_via_defaults( - default_semantic_model: SemanticModel, -): - assert default_semantic_model.defaults.agg_time_dimension is not None - measure = default_semantic_model.measures[0] - measure.agg_time_dimension = None - default_semantic_model.checked_agg_time_dimension_for_measure( - MeasureReference(element_name=measure.name) - ) - - -def test_checked_agg_time_dimension_for_measure_via_measure(default_semantic_model: SemanticModel): - default_semantic_model.defaults = None - measure = default_semantic_model.measures[0] - measure.agg_time_dimension = default_semantic_model.dimensions[0].name - default_semantic_model.checked_agg_time_dimension_for_measure( - MeasureReference(element_name=measure.name) - ) - - -def test_checked_agg_time_dimension_for_measure_exception(default_semantic_model: SemanticModel): - default_semantic_model.defaults = None - measure = default_semantic_model.measures[0] - measure.agg_time_dimension = None - - with pytest.raises(AssertionError) as execinfo: - default_semantic_model.checked_agg_time_dimension_for_measure( - MeasureReference(measure.name) - ) - - assert ( - f"Aggregation time dimension for measure {measure.name} on semantic model {default_semantic_model.name}" - in str(execinfo.value) - ) - - -def test_semantic_model_same_contents(default_semantic_model: SemanticModel): - default_semantic_model_copy = deepcopy(default_semantic_model) - - assert default_semantic_model.same_contents(default_semantic_model_copy) - - -def test_semantic_model_same_contents_update_model(default_semantic_model: SemanticModel): - default_semantic_model_copy = deepcopy(default_semantic_model) - default_semantic_model_copy.model = "ref('test_another_model')" - - assert not default_semantic_model.same_contents(default_semantic_model_copy) - - -def test_semantic_model_same_contents_different_node_relation( - default_semantic_model: SemanticModel, -): - default_semantic_model_copy = deepcopy(default_semantic_model) - default_semantic_model_copy.node_relation.alias = "test_another_alias" - # Relation should not be consided in same_contents - assert default_semantic_model.same_contents(default_semantic_model_copy) diff --git a/tests/unit/test_sql_result.py b/tests/unit/test_sql_result.py deleted file mode 100644 index f7273acac2e..00000000000 --- a/tests/unit/test_sql_result.py +++ /dev/null @@ -1,19 +0,0 @@ -import unittest -from dbt.adapters.sql.connections import SQLConnectionManager - - -class TestProcessSQLResult(unittest.TestCase): - def test_duplicated_columns(self): - cols_with_one_dupe = ["a", "b", "a", "d"] - rows = [(1, 2, 3, 4)] - self.assertEqual( - SQLConnectionManager.process_results(cols_with_one_dupe, rows), - [{"a": 1, "b": 2, "a_2": 3, "d": 4}], - ) - - cols_with_more_dupes = ["a", "a", "a", "b"] - rows = [(1, 2, 3, 4)] - self.assertEqual( - SQLConnectionManager.process_results(cols_with_more_dupes, rows), - [{"a": 1, "a_2": 2, "a_3": 3, "b": 4}], - ) diff --git a/tests/unit/test_tracking.py b/tests/unit/test_tracking.py index accfa99bc3f..d2bea6b152e 100644 --- a/tests/unit/test_tracking.py +++ b/tests/unit/test_tracking.py @@ -1,22 +1,24 @@ +import pytest + import dbt.tracking import datetime -import shutil import tempfile -import unittest -class TestTracking(unittest.TestCase): - def setUp(self): - dbt.tracking.active_user = None - self.tempdir = tempfile.mkdtemp() +@pytest.fixture(scope="function") +def active_user_none() -> None: + dbt.tracking.active_user = None + + +@pytest.fixture(scope="function") +def tempdir(active_user_none) -> str: + return tempfile.mkdtemp() - def tearDown(self): - dbt.tracking.active_user = None - shutil.rmtree(self.tempdir) - def test_tracking_initial(self): +class TestTracking: + def test_tracking_initial(self, tempdir): assert dbt.tracking.active_user is None - dbt.tracking.initialize_from_flags(True, self.tempdir) + dbt.tracking.initialize_from_flags(True, tempdir) assert isinstance(dbt.tracking.active_user, dbt.tracking.User) invocation_id = dbt.tracking.active_user.invocation_id @@ -48,7 +50,7 @@ def test_tracking_initial(self): # if you use `!=`, you might hit a race condition (especially on windows) assert dbt.tracking.active_user.run_started_at is not run_started_at - def test_tracking_never_ok(self): + def test_tracking_never_ok(self, active_user_none): assert dbt.tracking.active_user is None # this should generate a whole new user object -> new invocation_id/run_started_at @@ -60,7 +62,7 @@ def test_tracking_never_ok(self): assert isinstance(dbt.tracking.active_user.invocation_id, str) assert isinstance(dbt.tracking.active_user.run_started_at, datetime.datetime) - def test_disable_never_enabled(self): + def test_disable_never_enabled(self, active_user_none): assert dbt.tracking.active_user is None # this should generate a whole new user object -> new invocation_id/run_started_at @@ -72,10 +74,7 @@ def test_disable_never_enabled(self): assert isinstance(dbt.tracking.active_user.invocation_id, str) assert isinstance(dbt.tracking.active_user.run_started_at, datetime.datetime) - def test_initialize_from_flags(self): - for send_anonymous_usage_stats in [True, False]: - with self.subTest(send_anonymous_usage_stats=send_anonymous_usage_stats): - - dbt.tracking.initialize_from_flags(send_anonymous_usage_stats, self.tempdir) - - assert dbt.tracking.active_user.do_not_track != send_anonymous_usage_stats + @pytest.mark.parametrize("send_anonymous_usage_stats", [True, False]) + def test_initialize_from_flags(self, tempdir, send_anonymous_usage_stats): + dbt.tracking.initialize_from_flags(send_anonymous_usage_stats, tempdir) + assert dbt.tracking.active_user.do_not_track != send_anonymous_usage_stats