Replies: 2 comments
-
Related discussions in the pytest issues here and here. TLDR; is that class inheritance (as described above) is the best current solution for our use-case. The trouble with using hooks to override the built-in collection mechanisms seems to be that fixture resolution (and potential clashes) won't work out-of-the-box, and there is currently no easy way to override the fixture discovery mechanisms to also inject external fixtures. |
Beta Was this translation helpful? Give feedback.
-
Jumping in here after a bit of headbanging while researching #1164 yesterday. Focusing on taps for now, I think it would be very helpful to (re)validate actual taps output on all tests. At the start of this conversation, you mention
while this is true, it is very easy for tap developers to break this correct behaviour by overriding some method in a child class, or by unknowingly using an untested corner case scenario. One thing in particular that I would have liked for a while is for the sdk to prevent me from writing "obviously wrong" code. For instance, it should check that my tap:
All the above may seem obvious, but I can think of a handful of bugs we've had with I had been thinking of creating a from singer_sdk.testing import sdk_magic_stdout_mock
# sdk_magic_stdout_mock.__enter__() mocks sys.stdout so that all singer messages are captured during the test
def test_in_tap_xyz():
with sdk_magic_stdout_mock() as m:
run_my_tap_with_some_use_case()
assert this == that
assert len(m.messages) == 42
# sdk_magic_stdout_mock.__exit__() runs a bunch of asserts for me to ensure
# that I followed the singer spec Ideally the context manager would expose a hook/the list of messages so that I can do extra checks specific to my test, things like:
This way, I get the certainty that my tap follows the spec with 1 line of code and a bunch of extra whitespace :) I'm not sure what this would look like for targets, but maybe something similar. |
Beta Was this translation helpful? Give feedback.
-
Having spent some time recently talking with @visch @BuzzCutNorman and others on target testing, and in reviewing this tap testing contribution from @stkbailey it makes sense to collate all these thoughts into a discussion. Broadly the questions surrounding testing are:
How best to package built-in tests applicable to all taps/targets?
Ideally tests can be imported from the existing
singer_sdk.testing
module and would be discoverable by pytest as individual tests. The current approach uses a factory function (get_standard_tap_tests
) which returns a list of test functions to be run sequentially. Whilst this works well as a first iteration, this approach does not leverage the benefits of pytest discovery (i.e. itemised tests) and in particular IDE integrations that simplify the running and debugging via discovered itemised tests.E.g.
The above can readily be implemented using pytest classes in
singer_sdk.testing
that are subclassed in specific tap/target implementations:Passing of
Target
,config
andsqlalchemy_connection
instances can be handled using required fixtures, and similar test classes can be provided for suites of tests by type (tap and target).How best to provide tools and frameworks that allow tap/target developers to write more and better tests?
I believe this is the intent of this MR by @stkbailey, providing both a
TapTestRunner
class to wrap and simplify the execution ofTap
instances and also aTestTemplate
base class for individual tests. Stephens' approach leverages@pytest.parametrize
to advertise tests to pytest, but could easily be adapted to the class-based approach to offer fully-itemised test definitions (rather than a large single parameterised test).In order to more easily facilitate mocking, we might benefit from breaking out the http interactions onto a separate
HTTPConnector
class, as per @edgarrmondragon suggestion here. This would allow developers to easily create mock connector classes for the purposes of testing, allowing core functionality to be tested without access to remote systems.Beta Was this translation helpful? Give feedback.
All reactions