diff --git a/singer_sdk/testing/factory.py b/singer_sdk/testing/factory.py
index f31930017..30740b6ef 100644
--- a/singer_sdk/testing/factory.py
+++ b/singer_sdk/testing/factory.py
@@ -8,6 +8,7 @@
 from .config import SuiteConfig
 from .runners import TapTestRunner, TargetTestRunner
 from .suites import (
+    TestSuite,
     tap_stream_attribute_tests,
     tap_stream_tests,
     tap_tests,
@@ -15,7 +16,12 @@
 )
 
 if t.TYPE_CHECKING:
-    from singer_sdk import Tap, Target
+    from singer_sdk import Stream, Tap, Target
+    from singer_sdk.testing.templates import (
+        AttributeTestTemplate,
+        StreamTestTemplate,
+        TapTestTemplate,
+    )
 
 
 class BaseTestClass:
@@ -144,8 +150,7 @@ def runner(self) -> TapTestRunner | TargetTestRunner:
 
         return TapTestClass
 
-    # TODO: Refactor this. It's too long and nested.
-    def _annotate_test_class(  # noqa: C901
+    def _annotate_test_class(
         self,
         empty_test_class: type[BaseTestClass],
         test_suites: list,
@@ -163,80 +168,101 @@ def _annotate_test_class(  # noqa: C901
         """
         for suite in test_suites:
             if suite.kind == "tap":
-                for test_class in suite.tests:
-                    test = test_class()
-                    test_name = f"test_{suite.kind}_{test.name}"
-                    setattr(empty_test_class, test_name, test.run)
+                self._with_tap_tests(empty_test_class, suite)
 
             if suite.kind in {"tap_stream", "tap_stream_attribute"}:
                 streams = list(test_runner.new_tap().streams.values())
 
                 if suite.kind == "tap_stream":
-                    params = [
+                    self._with_stream_tests(empty_test_class, suite, streams)
+
+                if suite.kind == "tap_stream_attribute":
+                    self._with_stream_attribute_tests(empty_test_class, suite, streams)
+
+        return empty_test_class
+
+    def _with_tap_tests(
+        self,
+        empty_test_class: type[BaseTestClass],
+        suite: TestSuite[TapTestTemplate],
+    ) -> None:
+        for test_class in suite.tests:
+            test = test_class()
+            test_name = f"test_{suite.kind}_{test.name}"
+            setattr(empty_test_class, test_name, test.run)
+
+    def _with_stream_tests(
+        self,
+        empty_test_class: type[BaseTestClass],
+        suite: TestSuite[StreamTestTemplate],
+        streams: list[Stream],
+    ) -> None:
+        params = [
+            {
+                "stream": stream,
+            }
+            for stream in streams
+        ]
+        param_ids = [stream.name for stream in streams]
+
+        for test_class in suite.tests:
+            test = test_class()
+            test_name = f"test_{suite.kind}_{test.name}"
+            setattr(
+                empty_test_class,
+                test_name,
+                test.run,
+            )
+            empty_test_class.params[test_name] = params
+            empty_test_class.param_ids[test_name] = param_ids
+
+    def _with_stream_attribute_tests(
+        self,
+        empty_test_class: type[BaseTestClass],
+        suite: TestSuite[AttributeTestTemplate],
+        streams: list[Stream],
+    ) -> None:
+        for test_class in suite.tests:
+            test = test_class()
+            test_name = f"test_{suite.kind}_{test.name}"
+            test_params = []
+            test_ids: list[str] = []
+            for stream in streams:
+                final_schema = stream.stream_maps[-1].transformed_schema["properties"]
+                test_params.extend(
+                    [
                         {
                             "stream": stream,
+                            "attribute_name": prop_name,
                         }
-                        for stream in streams
-                    ]
-                    param_ids = [stream.name for stream in streams]
-
-                    for test_class in suite.tests:
-                        test = test_class()
-                        test_name = f"test_{suite.kind}_{test.name}"
-                        setattr(
-                            empty_test_class,
-                            test_name,
-                            test.run,
+                        for prop_name, prop_schema in final_schema.items()
+                        if test_class.evaluate(
+                            stream=stream,
+                            property_name=prop_name,
+                            property_schema=prop_schema,
                         )
-                        empty_test_class.params[test_name] = params
-                        empty_test_class.param_ids[test_name] = param_ids
-
-                if suite.kind == "tap_stream_attribute":
-                    for test_class in suite.tests:
-                        test = test_class()
-                        test_name = f"test_{suite.kind}_{test.name}"
-                        test_params = []
-                        test_ids: list[str] = []
-                        for stream in streams:
-                            final_schema = stream.stream_maps[-1].transformed_schema[
-                                "properties"
-                            ]
-                            test_params.extend(
-                                [
-                                    {
-                                        "stream": stream,
-                                        "attribute_name": prop_name,
-                                    }
-                                    for prop_name, prop_schema in final_schema.items()
-                                    if test_class.evaluate(
-                                        stream=stream,
-                                        property_name=prop_name,
-                                        property_schema=prop_schema,
-                                    )
-                                ],
-                            )
-                            test_ids.extend(
-                                [
-                                    f"{stream.name}.{prop_name}"
-                                    for prop_name, prop_schema in final_schema.items()
-                                    if test_class.evaluate(
-                                        stream=stream,
-                                        property_name=prop_name,
-                                        property_schema=prop_schema,
-                                    )
-                                ],
-                            )
-
-                        if test_params:
-                            setattr(
-                                empty_test_class,
-                                test_name,
-                                test.run,
-                            )
-                            empty_test_class.params[test_name] = test_params
-                            empty_test_class.param_ids[test_name] = test_ids
+                    ],
+                )
+                test_ids.extend(
+                    [
+                        f"{stream.name}.{prop_name}"
+                        for prop_name, prop_schema in final_schema.items()
+                        if test_class.evaluate(
+                            stream=stream,
+                            property_name=prop_name,
+                            property_schema=prop_schema,
+                        )
+                    ],
+                )
 
-        return empty_test_class
+            if test_params:
+                setattr(
+                    empty_test_class,
+                    test_name,
+                    test.run,
+                )
+                empty_test_class.params[test_name] = test_params
+                empty_test_class.param_ids[test_name] = test_ids
 
 
 class TargetTestClassFactory:
diff --git a/singer_sdk/testing/suites.py b/singer_sdk/testing/suites.py
index d795cf153..df93c86d2 100644
--- a/singer_sdk/testing/suites.py
+++ b/singer_sdk/testing/suites.py
@@ -42,17 +42,17 @@
     TargetSchemaUpdates,
     TargetSpecialCharsInAttributes,
 )
+from .templates import TestTemplate
 
-if t.TYPE_CHECKING:
-    from .templates import TapTestTemplate, TargetTestTemplate, TestTemplate
+T = t.TypeVar("T", bound=TestTemplate)
 
 
 @dataclass
-class TestSuite:
+class TestSuite(t.Generic[T]):
     """Test Suite container class."""
 
     kind: str
-    tests: list[type[TestTemplate] | type[TapTestTemplate] | type[TargetTestTemplate]]
+    tests: list[type[T]]
 
 
 # Tap Test Suites