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

feat: improve included tap and target tests in singer_sdk.testing #1171

Merged
merged 119 commits into from
Jan 30, 2023
Merged
Show file tree
Hide file tree
Changes from 60 commits
Commits
Show all changes
119 commits
Select commit Hold shift + click to select a range
2c83a1e
first pass at new testing framework
Nov 11, 2022
775aaa8
tidying up
Nov 11, 2022
5d8e944
first pass at target tests
Nov 11, 2022
27e38a9
working target tests
Nov 11, 2022
087cd62
Merge branch 'main' into kgpayne/issue1169
Nov 11, 2022
465fde5
working test parameterization
Nov 16, 2022
3ae3f73
Merge branch 'main' into kgpayne/issue1169
Nov 16, 2022
117cd8c
support resource fixture
Nov 16, 2022
88ef67a
Merge branch 'main' into kgpayne/issue1169
Nov 16, 2022
c764b31
tweak
Nov 17, 2022
003b149
Merge branch 'kgpayne/issue1169' of github.com:meltano/sdk into kgpay…
Nov 17, 2022
c0f1d5c
Merge branch 'main' into kgpayne/issue1169
Nov 17, 2022
f9884ca
refactor sample tests
Nov 17, 2022
11543b8
Merge branch 'kgpayne/issue1169' of github.com:meltano/sdk into kgpay…
Nov 17, 2022
87b60c3
refactor external tests
Nov 17, 2022
dfafea1
clean out unused tap-snowflake external test
Nov 17, 2022
2da611c
fix 'fixture called directly' error
Nov 17, 2022
49d8594
tweak runner kwargs
Nov 17, 2022
a4c73df
doh
Nov 17, 2022
1f27847
make external tests not kill discovery
Nov 17, 2022
f9a68e5
use default kwargs when creating second tap
Nov 17, 2022
9a3c307
Merge branch 'main' into kgpayne/issue1169
Nov 17, 2022
f5099cb
Merge branch 'kgpayne/issue1169' of github.com:meltano/sdk into kgpay…
Nov 17, 2022
8952b4e
fix 3.11 tests
Nov 17, 2022
428f6b1
doh
Nov 17, 2022
19abd28
Merge branch 'main' into kgpayne/issue1169
Nov 17, 2022
cb88b19
doh
Nov 17, 2022
539db02
Merge branch 'kgpayne/issue1169' of github.com:meltano/sdk into kgpay…
Nov 17, 2022
fe525ee
last doh
Nov 17, 2022
cd38864
Merge branch 'main' into kgpayne/issue1169
Nov 18, 2022
2737588
Update cookiecutters
Nov 22, 2022
e9e97a1
Add docs
Nov 22, 2022
bc3c29b
Merge branch 'kgpayne/issue1169' of github.com:meltano/sdk into kgpay…
Nov 22, 2022
a6b4dc2
Allow pendulum to fall back to dateutils
Nov 22, 2022
680ccd5
fix cookiecutter tests
Nov 22, 2022
df45eea
undo dateutils fallback
Nov 22, 2022
41851d0
make sample tap-gitlab tests pass
Nov 22, 2022
1f6502c
Merge branch 'main' into kgpayne/issue1169
Nov 22, 2022
8f09c37
run tests slow and sequential
Nov 22, 2022
f41c40a
remove singleton tap/target instances
Nov 23, 2022
7bc95b3
reintroduce try around external tests
Nov 23, 2022
e8d0b0d
typo
Nov 23, 2022
e3394e3
update schema
Nov 23, 2022
6601b4d
lint and mypy
Nov 23, 2022
b61f88d
more linting
Nov 23, 2022
13a4222
more linting
Nov 23, 2022
5596563
Merge branch 'main' into kgpayne/issue1169
Nov 23, 2022
c8b36e0
even more linting
Nov 23, 2022
b5b9aad
Merge branch 'kgpayne/issue1169' of github.com:meltano/sdk into kgpay…
Nov 23, 2022
ed071ba
even more linting
Nov 23, 2022
2250f0d
all the mypy
Nov 23, 2022
bee38dd
Merge branch 'main' into kgpayne/issue1169
Nov 23, 2022
38bc251
I hate pyupgrade and mypy
Nov 23, 2022
425f141
Merge branch 'kgpayne/issue1169' of github.com:meltano/sdk into kgpay…
Nov 23, 2022
74d1595
add pytest as dependency
Nov 23, 2022
e123ac0
fix abc bug
Nov 23, 2022
a207144
disable pyupgrade on runners.py because it confilcts with mypy
Nov 23, 2022
bebeca0
more mypy faff
Nov 23, 2022
488085d
even more mypy faff
Nov 23, 2022
3aea856
last of the mypy faff 🤞
Nov 23, 2022
b35f245
Merge branch 'main' into kgpayne/issue1169
Nov 29, 2022
2ed48aa
Update docs/testing.md
Nov 29, 2022
31b9c7c
Update docs/testing.md
Nov 29, 2022
7f80876
Update docs/testing.md
Nov 29, 2022
e37bf95
Update docs/testing.md
Nov 29, 2022
14083ab
Merge branch 'main' into kgpayne/issue1169
Nov 30, 2022
136211d
Update singer_sdk/testing/tap_tests.py
Nov 30, 2022
a560404
Update singer_sdk/testing/tap_tests.py
Nov 30, 2022
b392d58
Merge branch 'main' into kgpayne/issue1169
Dec 2, 2022
2fc9f54
defer runner.sync_all() call using fixture
Dec 2, 2022
583322f
enable pytest duration
Dec 2, 2022
300caab
typo in enabling pytest durations
Dec 2, 2022
b7fec8c
add pytest to mypy testing
Dec 2, 2022
ed04f28
Merge branch 'main' into kgpayne/issue1169
Dec 6, 2022
8611de9
Merge branch 'main' into kgpayne/issue1169
Dec 6, 2022
534689b
Merge branch 'main' into kgpayne/issue1169
Dec 12, 2022
f42bf5d
make singer_sdk a poetry plugin
Dec 12, 2022
babdef7
move ignore to single file
Dec 12, 2022
acb97e5
undo runners.py ignore
Dec 12, 2022
4895787
Merge branch 'main' into kgpayne/issue1169
Dec 12, 2022
018a4d2
Merge branch 'main' into kgpayne/issue1169
Dec 14, 2022
fd515ce
do not discover cookiecutter conftest.py files
Dec 14, 2022
f4f6426
Merge branch 'kgpayne/issue1169' of github.com:meltano/sdk into kgpay…
Dec 14, 2022
9f2e885
fix precommit
Dec 14, 2022
cdd8399
ignore runners in pyupgrade
Dec 14, 2022
07e4fdb
update lock
Dec 15, 2022
ab69626
remove exclude
Dec 15, 2022
04dcf75
Merge branch 'main' into kgpayne/issue1169
Dec 15, 2022
a505861
improve interface
Dec 15, 2022
61e81b9
Merge branch 'kgpayne/issue1169' of github.com:meltano/sdk into kgpay…
Dec 15, 2022
1e7e6ff
lint
Dec 15, 2022
016ea4a
Merge branch 'main' into kgpayne/issue1169
Jan 3, 2023
9931651
Merge branch 'main' into kgpayne/issue1169
Jan 3, 2023
cc2c40f
update lock
Jan 3, 2023
11f72b2
Update singer_sdk/testing/factory.py
Jan 24, 2023
449ae35
Update singer_sdk/testing/factory.py
Jan 24, 2023
25e0d4f
parse env vars by default, and support limiting results
Jan 24, 2023
9579f1c
poetry lock
Jan 24, 2023
bc61053
support configuring individual tests via suite_config
Jan 24, 2023
3aacb00
Merge branch 'main' into kgpayne/issue1169
Jan 24, 2023
994d4ca
make suite_config a dataclass
Jan 24, 2023
b1127a3
make suite_config a dataclass
Jan 24, 2023
d2956bd
make suite_config a dataclass
Jan 24, 2023
a3a7be7
update hints
Jan 24, 2023
f4c5558
mypy
Jan 24, 2023
10dd052
failing tests on MaxRecordsLimitException
Jan 24, 2023
09f26b3
remove max_records_limit
Jan 25, 2023
6fd69f2
Merge branch 'main' into kgpayne/issue1169
Jan 25, 2023
935b7d7
linting
Jan 25, 2023
20a19cc
Merge branch 'kgpayne/issue1169' of github.com:meltano/sdk into kgpay…
Jan 25, 2023
5520826
pr feedback
Jan 25, 2023
f0481f3
poetry lock --no-update
Jan 25, 2023
cf15bae
Merge branch 'main' into kgpayne/issue1169
Jan 25, 2023
24fef19
don't try to decode empty lines
Jan 26, 2023
56d50a6
Merge branch 'main' into kgpayne/issue1169
Jan 27, 2023
ae00c15
Merge branch 'main' into kgpayne/issue1169
Jan 30, 2023
d5c3295
retry ci pre-commit pipeline
Jan 30, 2023
1731acd
poetry update isort
Jan 30, 2023
6885df0
upgrate isort pre-commit
Jan 30, 2023
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
2 changes: 1 addition & 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, ANN401
kgpayne marked this conversation as resolved.
Show resolved Hide resolved
max-line-length = 88
exclude = cookiecutter
per-file-ignores =
Expand Down
3 changes: 2 additions & 1 deletion .pre-commit-config.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -70,7 +70,8 @@ repos:
exclude: |
(?x)^(
singer_sdk/helpers/_simpleeval.py|
tests/core/test_simpleeval.py
tests/core/test_simpleeval.py|
singer_sdk/testing/runners.py
kgpayne marked this conversation as resolved.
Show resolved Hide resolved
)$

- repo: https://github.com/python-poetry/poetry
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,16 @@

import datetime

from singer_sdk.testing import get_standard_tap_tests
from singer_sdk.testing import (
TapTestRunner,
get_test_class,
pytest_generate_tests # pytest hook function, required for standard tests
)
from singer_sdk.testing.suites import (
tap_stream_attribute_tests,
tap_stream_tests,
tap_tests,
)

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

Expand All @@ -13,14 +22,13 @@


# 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 }},
TestTap{{ cookiecutter.source_name }} = get_test_class(
test_runner=TapTestRunner(
tap_class=Tap{{ cookiecutter.source_name }},
config=SAMPLE_CONFIG
)
for test in tests:
test()
),
test_suites=[tap_tests, tap_stream_tests, tap_stream_attribute_tests],
)


# TODO: Create additional tests as appropriate for your tap.
Original file line number Diff line number Diff line change
@@ -1,27 +1,40 @@
"""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 TargetTestRunner, get_test_class
from singer_sdk.testing.suites import target_tests

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_test_class(
test_runner=TargetTestRunner(
target_class=Target{{ cookiecutter.destination_name }}, config=SAMPLE_CONFIG
),
test_suites=[target_tests],
)


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.
1 change: 1 addition & 0 deletions docs/index.rst
Original file line number Diff line number Diff line change
Expand Up @@ -71,6 +71,7 @@ Advanced Topics
CONTRIBUTING
implementation/index
typing
testing

.. _Singer: https://singer.io
.. _Singer Spec: https://hub.meltano.com/singer/spec
Expand Down
115 changes: 115 additions & 0 deletions docs/testing.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,115 @@
# Testing Taps & Targets
edgarrmondragon marked this conversation as resolved.
Show resolved Hide resolved

The Meltano SDK includes suits of standard tests for both Taps and Targets to help you get started.
kgpayne marked this conversation as resolved.
Show resolved Hide resolved
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 framwrok consists of 4 main components:
kgpayne marked this conversation as resolved.
Show resolved Hide resolved

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. A `get_test_class` factory method, to take 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/<library_name>_core.py`.
kgpayne marked this conversation as resolved.
Show resolved Hide resolved

### Testing Taps

```python
import datetime

from singer_sdk.testing import (
TapTestRunner,
get_test_class,
pytest_generate_tests # pytest hook function, required for standard tests
kgpayne marked this conversation as resolved.
Show resolved Hide resolved
)
from singer_sdk.testing.suites import (
tap_stream_attribute_tests,
tap_stream_tests,
tap_tests,
)

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_test_class(
test_runner=TapTestRunner(tap_class=TapExample, config=SAMPLE_CONFIG),
test_suites=[tap_tests, tap_stream_tests, tap_stream_attribute_tests],
)
```

### Testing Targets

```python
import pytest
from typing import Dict, Any

from singer_sdk.testing import TargetTestRunner, get_test_class
from singer_sdk.testing.suites import target_tests

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_test_class(
test_runner=TargetTestRunner(
target_class=TargetTargetExample, config=SAMPLE_CONFIG
),
test_suites=[target_tests],
)


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

## 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.
kgpayne marked this conversation as resolved.
Show resolved Hide resolved

```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_test_class` 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).
1 change: 0 additions & 1 deletion noxfile.py
Original file line number Diff line number Diff line change
Expand Up @@ -86,7 +86,6 @@ def tests(session: Session) -> None:
"--parallel",
"-m",
"pytest",
"-x",
"-v",
*session.posargs,
)
Expand Down
12 changes: 6 additions & 6 deletions poetry.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

1 change: 1 addition & 0 deletions pyproject.toml
Original file line number Diff line number Diff line change
Expand Up @@ -57,6 +57,7 @@ python-dotenv = ">=0.20,<0.22"
typing-extensions = "^4.2.0"
simplejson = "^3.17.6"
jsonschema = "^4.16.0"
pytest = "^7.2.0"
kgpayne marked this conversation as resolved.
Show resolved Hide resolved
pytz = "^2022.2.1"
PyYAML = "^6.0"

Expand Down
4 changes: 2 additions & 2 deletions samples/sample_tap_gitlab/gitlab_rest_streams.py
Original file line number Diff line number Diff line change
Expand Up @@ -100,7 +100,7 @@ class ReleasesStream(ProjectBasedStream):

name = "releases"
path = "/projects/{project_id}/releases"
primary_keys = ["project_id", "commit_id", "tag_name"]
primary_keys = ["project_id", "tag_name"]
replication_key = None
schema_filepath = SCHEMAS_DIR / "releases.json"

Expand All @@ -112,7 +112,7 @@ class IssuesStream(ProjectBasedStream):
path = "/projects/{project_id}/issues?scope=all&updated_after={start_date}"
primary_keys = ["id"]
replication_key = "updated_at"
is_sorted = True
is_sorted = False
schema_filepath = SCHEMAS_DIR / "issues.json"


Expand Down
Loading