Skip to content

Commit

Permalink
Merge remote-tracking branch 'origin/main' into kgpayne/issue1205
Browse files Browse the repository at this point in the history
  • Loading branch information
Ken Payne committed Feb 2, 2023
2 parents 1e290fd + 5db1b50 commit 8286517
Show file tree
Hide file tree
Showing 82 changed files with 2,991 additions and 1,137 deletions.
4 changes: 3 additions & 1 deletion .flake8
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
[flake8]
ignore = W503, C901, ANN101
ignore = W503, C901, ANN101, ANN102
max-line-length = 88
exclude = cookiecutter
per-file-ignores =
Expand All @@ -13,6 +13,8 @@ per-file-ignores =
# singer_sdk/helpers/_classproperty.py:D105
# Ignore unused imports in __init__.py files
singer_sdk/_singerlib/__init__.py:F401
# Templates support a generic resource of type Any.
singer_sdk/testing/templates.py:ANN401
max-complexity = 10
docstring-convention = google
allow-star-arg-any = true
4 changes: 2 additions & 2 deletions .github/CODEOWNERS
Validating CODEOWNERS rules …
Original file line number Diff line number Diff line change
Expand Up @@ -11,10 +11,10 @@

# Docs (General)
/docs/ @meltano/engineering @meltano/marketing
/README.md @afolson @tayloramurphy @meltano/engineering @meltano/marketing
/README.md @tayloramurphy @meltano/engineering @meltano/marketing

# Docs (Contributing)
/docs/CONTRIBUTING.md @afolson @tayloramurphy @meltano/engineering
/docs/CONTRIBUTING.md @tayloramurphy @meltano/engineering

# Release Ops (see `/.pyproject.toml` for list of bumped files)
/cookiecutter/*/*/pyproject.toml @meltano/engineering
Expand Down
2 changes: 1 addition & 1 deletion .github/ISSUE_TEMPLATE/bug.yml
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,7 @@ body:
attributes:
label: Singer SDK Version
description: Version of the library you are using
placeholder: "0.18.0"
placeholder: "0.19.0"
validations:
required: true
- type: dropdown
Expand Down
2 changes: 1 addition & 1 deletion .github/workflows/constraints.txt
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
pip==22.3.1
pip==23.0
poetry==1.3.2
nox==2022.11.21
nox-poetry==1.0.2
4 changes: 2 additions & 2 deletions .pre-commit-config.yaml
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
ci:
autofix_prs: false
autofix_prs: true
autoupdate_schedule: weekly
autoupdate_commit_msg: 'chore: pre-commit autoupdate'
skip: [poetry-lock]
Expand Down Expand Up @@ -47,7 +47,7 @@ repos:
)$
- repo: https://github.com/pycqa/isort
rev: 5.11.4
rev: 5.12.0
hooks:
- id: isort
exclude: (cookiecutter/.*|singer_sdk/helpers/_simpleeval/.*)
Expand Down
14 changes: 14 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,20 @@ All notable changes to this project will be documented in this file.
The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/),
and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).

## v0.19.0 (2023-01-30)

### ✨ New

- [#1171](https://github.com/meltano/sdk/issues/1171) Improve included tap and target tests in `singer_sdk.testing`

### 🐛 Fixes

- [#1345](https://github.com/meltano/sdk/issues/1345) Remove tox dependency from tap/target template

### 📚 Documentation Improvements

- [#1358](https://github.com/meltano/sdk/issues/1358) Fix typo in `if __name__ == ` example

## v0.18.0 (2023-01-23)

### ✨ New
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,7 @@ license = "Apache 2.0"

[tool.poetry.dependencies]
python = "<3.12,>=3.7.1"
singer-sdk = { version="^0.18.0"}
singer-sdk = { version="^0.19.0"}
fs-s3fs = { version = "^1.1.1", optional = true}
{%- if cookiecutter.stream_type in ["REST", "GraphQL"] %}
requests = "^2.28.1"
Expand All @@ -23,7 +23,7 @@ flake8 = "^5.0.4"
black = "^22.12.0"
pydocstyle = "^6.2.1"
mypy = "^0.991"
isort = "^5.11.4"
isort = "^5.12.0"
{%- if cookiecutter.stream_type in ["REST", "GraphQL"] %}
types-requests = "^2.28.11.7"
{%- endif %}
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
"""Test Configuration."""

pytest_plugins = ("singer_sdk.testing.pytest_plugin",)
Original file line number Diff line number Diff line change
Expand Up @@ -2,25 +2,22 @@

import datetime

from singer_sdk.testing import get_standard_tap_tests
from singer_sdk.testing import get_tap_test_class

from {{ cookiecutter.library_name }}.tap import Tap{{ cookiecutter.source_name }}


SAMPLE_CONFIG = {
"start_date": datetime.datetime.now(datetime.timezone.utc).strftime("%Y-%m-%d")
# TODO: Initialize minimal tap config
}


# Run standard built-in tap tests from the SDK:
def test_standard_tap_tests():
"""Run standard tap tests from the SDK."""
tests = get_standard_tap_tests(
Tap{{ cookiecutter.source_name }},
config=SAMPLE_CONFIG
)
for test in tests:
test()
TestTap{{ cookiecutter.source_name }} = get_tap_test_class(
tap_class=Tap{{ cookiecutter.source_name }},
config=SAMPLE_CONFIG
)


# TODO: Create additional tests as appropriate for your tap.
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,7 @@ license = "Apache 2.0"

[tool.poetry.dependencies]
python = "<3.12,>=3.7.1"
singer-sdk = { version="^0.18.0"}
singer-sdk = { version="^0.19.0"}
fs-s3fs = { version = "^1.1.1", optional = true}
{%- if cookiecutter.serialization_method != "SQL" %}
requests = "^2.28.1"
Expand All @@ -23,7 +23,7 @@ flake8 = "^5.0.4"
black = "^22.12.0"
pydocstyle = "^6.2.1"
mypy = "^0.991"
isort = "^5.11.4"
isort = "^5.12.0"
{%- if cookiecutter.serialization_method != "SQL" %}
types-requests = "^2.28.11.7"
{%- endif %}
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
"""Test Configuration."""

pytest_plugins = ("singer_sdk.testing.pytest_plugin",)
Original file line number Diff line number Diff line change
@@ -1,27 +1,39 @@
"""Tests standard target features using the built-in SDK tests library."""

import datetime

import pytest
from typing import Dict, Any

from singer_sdk.testing import get_standard_target_tests
from singer_sdk.testing import get_target_test_class

from {{ cookiecutter.library_name }}.target import Target{{ cookiecutter.destination_name }}


SAMPLE_CONFIG: Dict[str, Any] = {
# TODO: Initialize minimal target config
}


# Run standard built-in target tests from the SDK:
def test_standard_target_tests():
"""Run standard target tests from the SDK."""
tests = get_standard_target_tests(
Target{{ cookiecutter.destination_name }},
config=SAMPLE_CONFIG,
)
for test in tests:
test()
StandardTargetTests = get_target_test_class(
target_class=Target{{ cookiecutter.destination_name }},
config=SAMPLE_CONFIG
)


class TestTarget{{ cookiecutter.destination_name }}(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"


# TODO: Create additional tests as appropriate for your target.
4 changes: 2 additions & 2 deletions docs/conf.py
Original file line number Diff line number Diff line change
Expand Up @@ -25,7 +25,7 @@
author = "Meltano Core Team and Contributors"

# The full version, including alpha/beta/rc tags
release = "0.18.0"
release = "0.19.0"


# -- General configuration ---------------------------------------------------
Expand Down Expand Up @@ -74,5 +74,5 @@
"css/custom.css",
]

# TODO: set this back to 3 after MyST-Parser 0.18.0 is released
# TODO: set this back to 3 after MyST-Parser 0.19.0 is released
myst_heading_anchors = 4
2 changes: 1 addition & 1 deletion docs/dev_guide.md
Original file line number Diff line number Diff line change
Expand Up @@ -212,7 +212,7 @@ An example launch configuration, added to your `launch.json`, might be as follow
The above `module` value relies on an equivalent to the following snippet being added to the end of your `tap.py` or `target.py` file:

```python
if __name__ == "__main":
if __name__ == "__main__":
TapSnowflake.cli()
```

Expand Down
1 change: 1 addition & 0 deletions docs/index.rst
Original file line number Diff line number Diff line change
Expand Up @@ -73,6 +73,7 @@ Advanced Topics
CONTRIBUTING
implementation/index
typing
testing

.. _Singer: https://singer.io
.. _Singer Spec: https://hub.meltano.com/singer/spec
Expand Down
139 changes: 139 additions & 0 deletions docs/testing.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,139 @@
# Testing Taps & Targets

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.

## Test Framework

The Meltano SDK test framework consists of 4 main components:

1. A runner class (`TapTestRunner` and `TargetTestRunner`), responsible for executing Taps/Targets and capturing their output.
1. A suite dataclass, containing a list of tests.
1. A test template classes (`TapTestTemplate`, `StreamTestTemplate`, `AttributeTestTemplate` and `TargetTestTemplate`), with methods to `.setup()`, `.test()`, `.validate()` and `.teardown()` (called in that order using `.run()`).
1. `get_tap_test_class` and `get_target_test_class` factory methods. These wrap a `get_test_class` factory method, which takes a runner and a list of suits and return a `pytest` test class.

## Example Usage

If you created your Tap/Target using the provided cookiecutter templates, you will find the following snippets in `<library_name>/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:

```python
# register the singer_sdk pytest plugin
pytest_plugins = ("singer_sdk.testing.pytest_plugin",)
```

### Testing Taps

```python
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
)
```

### Testing Targets

```python
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"
```

## Configuring Tests

Test suite behaviors can be configured by passing a `SuiteConfig` instance to the `get_test_class` functions:

```python
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`](https://github.com/meltano/sdk/tree/main/singer_sdk/testing/config.py) for available config options.

## Writing New Tests

Writing new tests is as easy as subclassing the appropriate class.
Check out [`singer_sdk/testing/tap_tests.py`](https://github.com/meltano/sdk/tree/main/singer_sdk/testing/tap_tests.py) and [`singer_sdk/testing/target_tests.py`](https://github.com/meltano/sdk/tree/main/singer_sdk/testing/target_tests.py) for inspiration.

```python
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:

```python
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](https://github.com/meltano/sdk).
Loading

0 comments on commit 8286517

Please sign in to comment.