The Meltano SDK includes suites of standard tests for both Taps and Targets to help you get started. These suites cover most common cases out-of-the-box, and tests are added to the standard suites as new errors are encountered by users in their deployments.
The Meltano SDK test framework consists of 4 main components:
- A runner class (
TapTestRunner
andTargetTestRunner
), responsible for executing Taps/Targets and capturing their output. - A suite dataclass, containing a list of tests.
- A test template classes (
TapTestTemplate
,StreamTestTemplate
,AttributeTestTemplate
andTargetTestTemplate
), with methods to.setup()
,.test()
,.validate()
and.teardown()
(called in that order using.run()
). get_tap_test_class
andget_target_test_class
factory methods. These wrap aget_test_class
factory method, which takes a runner and a list of suits and return apytest
test class.
If you created your Tap/Target using the provided cookiecutter templates, you will find the following snippets in tests/test_core.py
.
You will also find a conftest.py
file containing configuration of the SDK as a pytest
plugin.
This is required for tests to collect correctly:
# register the singer_sdk pytest plugin
pytest_plugins = ("singer_sdk.testing.pytest_plugin",)
import datetime
from singer_sdk.testing import get_tap_test_class
from example.tap import TapExample
SAMPLE_CONFIG = {
"start_date": datetime.datetime.now(datetime.timezone.utc).strftime("%Y-%m-%d")
}
# Run standard built-in tap tests from the SDK:
TestTapExample = get_tap_test_class(
tap_class=TapExample,
config=SAMPLE_CONFIG
)
import pytest
from typing import Dict, Any
from singer_sdk.testing import get_target_test_class
from example.target import TargetExample
SAMPLE_CONFIG: Dict[str, Any] = {
# TODO: Initialize minimal target config
}
# Run standard built-in target tests from the SDK:
StandardTargetTests = get_target_test_class(
target_class=TargetExample,
config=SAMPLE_CONFIG
)
class TestTargetExample(StandardTargetTests):
"""Standard Target Tests."""
@pytest.fixture(scope="class")
def resource(self):
"""Generic external resource.
This fixture is useful for setup and teardown of external resources,
such output folders, tables, buckets etc. for use during testing.
Example usage can be found in the SDK samples test suite:
https://github.com/meltano/sdk/tree/main/tests/samples
"""
yield "resource"
Test suite behaviors can be configured by passing a SuiteConfig
instance to the get_test_class
functions:
from singer_sdk.testing import SuiteConfig, get_tap_test_class
from tap_stackexchange.tap import TapStackExchange
SAMPLE_CONFIG = {
"site": "stackoverflow",
"tags": [
"meltano",
"singer-io",
],
"metrics_log_level": "debug",
}
TEST_SUITE_CONFIG = SuiteConfig(
ignore_no_records_for_streams=["tag_synonyms"]
)
TestTapStackExchange = get_tap_test_class(
tap_class=TapStackExchange, config=SAMPLE_CONFIG, suite_config=TEST_SUITE_CONFIG
)
Check out singer_sdk/testing/config.py
for available config options.
Writing new tests is as easy as subclassing the appropriate class.
Check out singer_sdk/testing/tap_tests.py
and singer_sdk/testing/target_tests.py
for inspiration.
class TapCLIPrintsTest(TapTestTemplate):
"Test that the tap is able to print standard metadata."
name = "cli_prints"
def test(self):
self.tap.print_version()
self.tap.print_about()
self.tap.print_about(format="json")
Once you have created some tests, add them to a suite:
my_custom_tap_tests = TestSuite(
type="tap", tests=[TapCLIPrintsTest]
)
This suite can now be passed to get_tap_test_class
or get_target_test_class
in a list of custom_suites
along with any other suites, to generate your custom test class.
If your new test covers a common or general case, consider contributing to the standard test library via a pull request to meltano/sdk.