You are viewing a plain text version of this content. The canonical link for it is here.
Posted to notifications@skywalking.apache.org by ke...@apache.org on 2020/08/22 10:50:35 UTC

[skywalking-python] branch master updated: test: run multiple versions of supported libraries (#66)

This is an automated email from the ASF dual-hosted git repository.

kezhenxu94 pushed a commit to branch master
in repository https://gitbox.apache.org/repos/asf/skywalking-python.git


The following commit(s) were added to refs/heads/master by this push:
     new 21011a9  test: run multiple versions of supported libraries (#66)
21011a9 is described below

commit 21011a9e6dfbbe6026688d5ca539ee07296420f2
Author: kezhenxu94 <ke...@apache.org>
AuthorDate: Sat Aug 22 18:50:25 2020 +0800

    test: run multiple versions of supported libraries (#66)
---
 .github/PULL_REQUEST_TEMPLATE                      |  1 -
 .github/workflows/build.yaml                       |  2 +-
 .gitignore                                         |  3 +-
 Makefile                                           |  2 +-
 docs/Plugins.md                                    | 30 +++----
 requirements.txt                                   | 16 +++-
 setup.py                                           | 11 +--
 tests/plugin/__init__.py                           | 91 ----------------------
 tests/plugin/{__init__.py => base.py}              | 56 ++-----------
 tests/plugin/conftest.py                           | 72 +++++++++++++++++
 tests/plugin/{sw_http/test_http.py => pytest.ini}  | 23 +-----
 tests/plugin/sw_django/docker-compose.yml          |  8 +-
 tests/plugin/sw_django/test_django.py              | 35 ++++-----
 tests/plugin/sw_elasticsearch/docker-compose.yml   |  4 +-
 .../plugin/sw_elasticsearch/test_elasticsearch.py  | 31 ++++----
 tests/plugin/sw_flask/docker-compose.yml           |  8 +-
 tests/plugin/sw_flask/test_flask.py                | 36 ++++-----
 tests/plugin/sw_http/test_http.py                  | 22 +++---
 tests/plugin/sw_http_wsgi/docker-compose.yml       |  6 +-
 tests/plugin/sw_http_wsgi/test_http_wsgi.py        | 25 +++---
 tests/plugin/sw_kafka/docker-compose.yml           | 10 +--
 tests/plugin/sw_kafka/test_kafka.py                | 35 ++++-----
 tests/plugin/sw_pymongo/docker-compose.yml         |  8 +-
 tests/plugin/sw_pymongo/test_pymongo.py            | 32 ++++----
 tests/plugin/sw_pymysql/docker-compose.yml         |  8 +-
 tests/plugin/sw_pymysql/test_pymysql.py            | 35 ++++-----
 tests/plugin/sw_rabbitmq/docker-compose.yml        | 10 +--
 tests/plugin/sw_rabbitmq/test_kafka.py             | 43 ----------
 .../test_http.py => sw_rabbitmq/test_rabbitmq.py}  | 25 +++---
 tests/plugin/sw_redis/docker-compose.yml           |  8 +-
 tests/plugin/sw_redis/test_redis.py                | 35 ++++-----
 tests/plugin/sw_requests/docker-compose.yml        |  9 ++-
 tests/plugin/sw_requests/test_request.py           | 32 ++++----
 tests/plugin/sw_tornado/docker-compose.yml         |  8 +-
 tests/plugin/sw_tornado/test_tornado.py            | 32 ++++----
 35 files changed, 330 insertions(+), 482 deletions(-)

diff --git a/.github/PULL_REQUEST_TEMPLATE b/.github/PULL_REQUEST_TEMPLATE
index 78cd184..cf29efa 100644
--- a/.github/PULL_REQUEST_TEMPLATE
+++ b/.github/PULL_REQUEST_TEMPLATE
@@ -3,6 +3,5 @@
 - [ ] Add a test case for the new plugin
 - [ ] Add a component id in [the main repo](https://github.com/apache/skywalking/blob/master/oap-server/server-bootstrap/src/main/resources/component-libraries.yml#L415)
 - [ ] Add a logo in [the UI repo](https://github.com/apache/skywalking-rocketbot-ui/tree/master/src/views/components/topology/assets)
-- [ ] Add the library/module that this plugin instruments into the `setup.py` (`extras_require/test`), such as `pymysql`, `flask`, `django`, etc.
 - [ ] Rebuild the `requirements.txt` by running `tools/env/build_requirements_(linux|windows).sh`
 -->
diff --git a/.github/workflows/build.yaml b/.github/workflows/build.yaml
index fedc27a..69d871c 100644
--- a/.github/workflows/build.yaml
+++ b/.github/workflows/build.yaml
@@ -46,4 +46,4 @@ jobs:
       - name: Check license header
         run: make license
       - name: Run unit tests
-        run: make test || make test || make test
+        run: make test
diff --git a/.gitignore b/.gitignore
index 6c8f812..d7ffd47 100644
--- a/.gitignore
+++ b/.gitignore
@@ -8,4 +8,5 @@ build/
 dist/
 *.pyc
 /apache_skywalking.egg-info/
-**/venv/
\ No newline at end of file
+**/venv/
+tests/**/requirements.txt
diff --git a/Makefile b/Makefile
index ad0cc7e..92c4508 100644
--- a/Makefile
+++ b/Makefile
@@ -44,7 +44,7 @@ license: clean
 	python3 tools/check-license-header.py skywalking tests tools
 
 test: gen setup-test
-	python3 -m unittest  -v
+	python3 -m pytest -v tests
 
 install: gen
 	python3 setup.py install --force
diff --git a/docs/Plugins.md b/docs/Plugins.md
index dbe077a..a660d88 100644
--- a/docs/Plugins.md
+++ b/docs/Plugins.md
@@ -1,16 +1,18 @@
 # Supported Libraries
 
-Library | Plugin Name
-| :--- | :--- |
-| [http.server](https://docs.python.org/3/library/http.server.html) | `sw_http_server` |
-| [urllib.request](https://docs.python.org/3/library/urllib.request.html) | `sw_urllib_request` |
-| [requests](https://requests.readthedocs.io/en/master/) | `sw_requests` |
-| [Flask](https://flask.palletsprojects.com/en/1.1.x/) | `sw_flask` |
-| [PyMySQL](https://pymysql.readthedocs.io/en/latest/) | `sw_pymysql` |
-| [Django](https://www.djangoproject.com/) | `sw_django` |
-| [redis-py](https://github.com/andymccurdy/redis-py/) | `sw_redis` |
-| [kafka-python](https://kafka-python.readthedocs.io/en/master/) | `sw_kafka` |
-| [tornado](https://www.tornadoweb.org/en/stable/) | `sw_tornado` |
-| [pika](https://pika.readthedocs.io/en/stable/) | `sw_rabbitmq` |
-| [pymongo](https://pymongo.readthedocs.io/en/stable/) | `sw_pymongo` |
-| [elasticsearch](https://github.com/elastic/elasticsearch-py) | `sw_elasticsearch` |
+Library | Versions | Plugin Name
+| :--- | :--- | :--- |
+| [http.server](https://docs.python.org/3/library/http.server.html) | Python 3.5 ~ 3.8 | `sw_http_server` |
+| [urllib.request](https://docs.python.org/3/library/urllib.request.html) | Python 3.5 ~ 3.8 | `sw_urllib_request` |
+| [requests](https://requests.readthedocs.io/en/master/) | >= 2.9.0 < 2.15.0, >= 2.17.0 <= 2.24.0 | `sw_requests` |
+| [Flask](https://flask.palletsprojects.com/en/1.1.x/) | >=1.0.4 <= 1.1.2 | `sw_flask` |
+| [PyMySQL](https://pymysql.readthedocs.io/en/latest/) | 0.10.0 | `sw_pymysql` |
+| [Django](https://www.djangoproject.com/) | >=2.0 <= 3.1 | `sw_django` |
+| [redis-py](https://github.com/andymccurdy/redis-py/) | 3.5.3 | `sw_redis` |
+| [kafka-python](https://kafka-python.readthedocs.io/en/master/) | 2.0.1 | `sw_kafka` |
+| [tornado](https://www.tornadoweb.org/en/stable/) | 6.0.4 | `sw_tornado` |
+| [pika](https://pika.readthedocs.io/en/stable/) | 1.1.0 | `sw_rabbitmq` |
+| [pymongo](https://pymongo.readthedocs.io/en/stable/) | 3.11.0 | `sw_pymongo` |
+| [elasticsearch](https://github.com/elastic/elasticsearch-py) | 7.9.0 | `sw_elasticsearch` |
+
+The column `Versions` only indicates that the versions are tested, if you found the newer versions are also supported, welcome to add the newer version into the table.
diff --git a/requirements.txt b/requirements.txt
index 6bbffba..1c3889f 100755
--- a/requirements.txt
+++ b/requirements.txt
@@ -1,4 +1,5 @@
 asgiref==3.2.10
+attrs==19.3.0
 blindspin==2.0.1
 certifi==2020.6.20
 chardet==3.0.4
@@ -8,20 +9,30 @@ crayons==0.3.1
 deprecation==2.1.0
 Django==3.1
 docker==4.2.2
+elasticsearch==7.8.0
 Flask==1.1.2
+gevent==20.6.2
+greenlet==0.4.16
 grpcio==1.31.0
 grpcio-tools==1.31.0
 idna==2.10
+importlib-metadata==1.7.0
+iniconfig==1.0.1
 itsdangerous==1.1.0
 Jinja2==2.11.2
 kafka-python==2.0.1
 MarkupSafe==1.1.1
+more-itertools==8.4.0
 packaging==20.4
+parameterized==0.7.4
 pika==1.1.0
+pluggy==0.13.1
 protobuf==3.12.4
+py==1.9.0
 pymongo==3.11.0
 PyMySQL==0.10.0
 pyparsing==2.4.7
+pytest==6.0.1
 pytz==2020.1
 PyYAML==5.3.1
 redis==3.5.3
@@ -29,9 +40,12 @@ requests==2.24.0
 six==1.15.0
 sqlparse==0.3.1
 testcontainers==3.0.3
+toml==0.10.1
 tornado==6.0.4
 urllib3==1.25.10
 websocket-client==0.57.0
 Werkzeug==1.0.1
 wrapt==1.12.1
-elasticsearch==7.8.0
+zipp==3.1.0
+zope.event==4.4
+zope.interface==5.1.0
diff --git a/setup.py b/setup.py
index 27c1089..9d34faf 100644
--- a/setup.py
+++ b/setup.py
@@ -44,16 +44,7 @@ setup(
         "test": [
             "testcontainers",
             "pyyaml",
-            "django",
-            "flask",
-            "Werkzeug",
-            "pymysql",
-            "redis",
-            "kafka-python",
-            "tornado",
-            "pika",
-            "pymongo",
-            "elasticsearch",
+            "pytest",
         ],
     },
     classifiers=[
diff --git a/tests/plugin/__init__.py b/tests/plugin/__init__.py
index f858a1f..b1312a0 100644
--- a/tests/plugin/__init__.py
+++ b/tests/plugin/__init__.py
@@ -14,94 +14,3 @@
 # See the License for the specific language governing permissions and
 # limitations under the License.
 #
-import inspect
-import os
-import sys
-import unittest
-from abc import ABC
-from collections import namedtuple
-from difflib import Differ
-from os.path import dirname
-
-import requests
-import yaml
-from requests import Response
-from testcontainers.compose import DockerCompose
-
-try:
-    from yaml import CLoader as Loader
-except ImportError:
-    from yaml import Loader
-
-HostPort = namedtuple('HostPort', 'host port')
-ServicePort = namedtuple('ServicePort', 'service port')
-
-
-class BasePluginTest(unittest.TestCase, ABC):
-    compose = None  # type: DockerCompose
-
-    @classmethod
-    def setUpClass(cls):
-        cls.compose = DockerCompose(filepath=dirname(inspect.getfile(cls)))
-        cls.compose.start()
-
-        cls.compose.wait_for(cls.url(cls.collector_address()))
-
-    @classmethod
-    def tearDownClass(cls):
-        cls.compose.stop()
-
-    @classmethod
-    def host(cls, service_port):
-        # type: (ServicePort) -> str
-        service, port = service_port
-        return cls.compose.get_service_host(service_name=service, port=port)
-
-    @classmethod
-    def port(cls, service_port):
-        # type: (ServicePort) -> str
-        service, port = service_port
-        return cls.compose.get_service_port(service_name=service, port=port)
-
-    @classmethod
-    def url(cls, service_port, path=''):
-        # type: (ServicePort, str) -> str
-        return 'http://%s:%s/%s' % (cls.host(service_port), cls.port(service_port), path.lstrip('/'))
-
-    @classmethod
-    def collector_address(cls):
-        # type: () -> ServicePort
-        return ServicePort(service='collector', port='12800')
-
-    def validate(self, expected_file_name=None):
-        # type: (str) -> Response
-
-        if expected_file_name is None:
-            expected_file_name = os.path.join(dirname(inspect.getfile(self.__class__)), 'expected.data.yml')
-
-        with open(expected_file_name) as expected_data_file:
-            expected_data = os.linesep.join(expected_data_file.readlines())
-
-            response = requests.post(
-                url=self.__class__.url(self.__class__.collector_address(), path='/dataValidate'),
-                data=expected_data,
-            )
-
-            if response.status_code != 200:
-                res = requests.get(url=self.__class__.url(self.__class__.collector_address(), path='/receiveData'))
-
-                actual_data = yaml.dump(yaml.load(res.content, Loader=Loader))
-
-                differ = Differ()
-                diff_list = list(differ.compare(
-                    actual_data.splitlines(keepends=True),
-                    yaml.dump(yaml.load(expected_data, Loader=Loader)).splitlines(keepends=True)
-                ))
-
-                print('diff list: ')
-
-                sys.stdout.writelines(diff_list)
-
-            self.assertEqual(response.status_code, 200)
-
-            return response
diff --git a/tests/plugin/__init__.py b/tests/plugin/base.py
similarity index 53%
copy from tests/plugin/__init__.py
copy to tests/plugin/base.py
index f858a1f..25bffa3 100644
--- a/tests/plugin/__init__.py
+++ b/tests/plugin/base.py
@@ -14,81 +14,41 @@
 # See the License for the specific language governing permissions and
 # limitations under the License.
 #
+
 import inspect
 import os
 import sys
-import unittest
+import time
 from abc import ABC
-from collections import namedtuple
 from difflib import Differ
 from os.path import dirname
 
 import requests
 import yaml
 from requests import Response
-from testcontainers.compose import DockerCompose
 
 try:
     from yaml import CLoader as Loader
 except ImportError:
     from yaml import Loader
 
-HostPort = namedtuple('HostPort', 'host port')
-ServicePort = namedtuple('ServicePort', 'service port')
-
-
-class BasePluginTest(unittest.TestCase, ABC):
-    compose = None  # type: DockerCompose
-
-    @classmethod
-    def setUpClass(cls):
-        cls.compose = DockerCompose(filepath=dirname(inspect.getfile(cls)))
-        cls.compose.start()
-
-        cls.compose.wait_for(cls.url(cls.collector_address()))
-
-    @classmethod
-    def tearDownClass(cls):
-        cls.compose.stop()
-
-    @classmethod
-    def host(cls, service_port):
-        # type: (ServicePort) -> str
-        service, port = service_port
-        return cls.compose.get_service_host(service_name=service, port=port)
-
-    @classmethod
-    def port(cls, service_port):
-        # type: (ServicePort) -> str
-        service, port = service_port
-        return cls.compose.get_service_port(service_name=service, port=port)
-
-    @classmethod
-    def url(cls, service_port, path=''):
-        # type: (ServicePort, str) -> str
-        return 'http://%s:%s/%s' % (cls.host(service_port), cls.port(service_port), path.lstrip('/'))
-
-    @classmethod
-    def collector_address(cls):
-        # type: () -> ServicePort
-        return ServicePort(service='collector', port='12800')
 
+class TestPluginBase(ABC):
     def validate(self, expected_file_name=None):
         # type: (str) -> Response
 
         if expected_file_name is None:
             expected_file_name = os.path.join(dirname(inspect.getfile(self.__class__)), 'expected.data.yml')
 
+        time.sleep(10)
+
         with open(expected_file_name) as expected_data_file:
             expected_data = os.linesep.join(expected_data_file.readlines())
 
-            response = requests.post(
-                url=self.__class__.url(self.__class__.collector_address(), path='/dataValidate'),
-                data=expected_data,
-            )
+            response = requests.post(url='http://0.0.0.0:12800/dataValidate', data=expected_data)
 
             if response.status_code != 200:
-                res = requests.get(url=self.__class__.url(self.__class__.collector_address(), path='/receiveData'))
+                res = requests.get('http://0.0.0.0:12800/receiveData')
 
                 actual_data = yaml.dump(yaml.load(res.content, Loader=Loader))
 
@@ -102,6 +62,6 @@ class BasePluginTest(unittest.TestCase, ABC):
 
                 sys.stdout.writelines(diff_list)
 
-            self.assertEqual(response.status_code, 200)
+            assert response.status_code == 200
 
             return response
diff --git a/tests/plugin/conftest.py b/tests/plugin/conftest.py
new file mode 100644
index 0000000..66c74c7
--- /dev/null
+++ b/tests/plugin/conftest.py
@@ -0,0 +1,72 @@
+#
+# Licensed to the Apache Software Foundation (ASF) under one or more
+# contributor license agreements.  See the NOTICE file distributed with
+# this work for additional information regarding copyright ownership.
+# The ASF licenses this file to You under the Apache License, Version 2.0
+# (the "License"); you may not use this file except in compliance with
+# the License.  You may obtain a copy of the License at
+#
+#     http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+#
+
+import inspect
+import os
+import time
+from os.path import dirname
+from typing import Callable
+
+import pytest
+from _pytest.fixtures import FixtureRequest
+from testcontainers.compose import DockerCompose
+
+
+@pytest.fixture
+def version():
+    # type: () -> str
+    return ''
+
+
+# noinspection PyUnusedLocal
+@pytest.fixture
+def prepare():
+    # type: () -> Callable
+    return lambda *_: None
+
+
+@pytest.fixture
+def docker_compose(request, prepare, version):
+    # type: (FixtureRequest, Callable, str) -> None
+
+    module = request.module
+    cwd = dirname(inspect.getfile(module))
+
+    if version:
+        with open(os.path.join(cwd, 'requirements.txt'), mode='w') as req:
+            req.write(version)
+
+    compose = DockerCompose(filepath=cwd)
+
+    compose.start()
+
+    exception = None
+    for _ in range(0, 10):
+        try:
+            prepare()
+            exception = None
+            break
+        except Exception as e:
+            time.sleep(10)
+            exception = e
+    if exception:
+        compose.stop()
+        raise Exception("""Wait time exceeded {0} sec. Exception {1}""".format(100, exception))
+
+    yield compose
+
+    compose.stop()
diff --git a/tests/plugin/sw_http/test_http.py b/tests/plugin/pytest.ini
similarity index 70%
copy from tests/plugin/sw_http/test_http.py
copy to tests/plugin/pytest.ini
index 8aa24b4..82a1b14 100644
--- a/tests/plugin/sw_http/test_http.py
+++ b/tests/plugin/pytest.ini
@@ -15,23 +15,6 @@
 # limitations under the License.
 #
 
-import time
-import unittest
-
-import requests
-
-from tests.plugin import BasePluginTest
-
-
-class TestPlugin(BasePluginTest):
-
-    def test_plugin(self):
-        print('traffic: ', requests.post(url=self.url(('consumer', '9090'))))
-
-        time.sleep(10)
-
-        self.validate()
-
-
-if __name__ == '__main__':
-    unittest.main()
+[pytest]
+usefixtures = docker_compose
+addopts = -x
diff --git a/tests/plugin/sw_django/docker-compose.yml b/tests/plugin/sw_django/docker-compose.yml
index d27afb0..247a051 100644
--- a/tests/plugin/sw_django/docker-compose.yml
+++ b/tests/plugin/sw_django/docker-compose.yml
@@ -30,8 +30,8 @@ services:
     ports:
       - 9091:9091
     volumes:
-      - ./services/provider.py:/app/provider.py
-    command: ['bash', '-c', 'pip install django && python3 /app/provider.py runserver 0.0.0.0:9091']
+      - .:/app
+    command: ['bash', '-c', 'pip install -r /app/requirements.txt && python3 /app/services/provider.py runserver 0.0.0.0:9091']
     depends_on:
       collector:
         condition: service_healthy
@@ -48,8 +48,8 @@ services:
     ports:
       - 9090:9090
     volumes:
-      - ./services/consumer.py:/app/consumer.py
-    command: ['bash', '-c', 'pip install django && python3 /app/consumer.py runserver 0.0.0.0:9090']
+      - .:/app
+    command: ['bash', '-c', 'pip install -r /app/requirements.txt && python3 /app/services/consumer.py runserver 0.0.0.0:9090']
     depends_on:
       collector:
         condition: service_healthy
diff --git a/tests/plugin/sw_django/test_django.py b/tests/plugin/sw_django/test_django.py
index b2315d8..b8a6515 100644
--- a/tests/plugin/sw_django/test_django.py
+++ b/tests/plugin/sw_django/test_django.py
@@ -14,29 +14,26 @@
 # See the License for the specific language governing permissions and
 # limitations under the License.
 #
-import inspect
-import time
-import unittest
-from os.path import dirname
+from typing import Callable
 
-from testcontainers.compose import DockerCompose
+import pytest
+import requests
 
-from tests.plugin import BasePluginTest
+from tests.plugin.base import TestPluginBase
 
 
-class TestPlugin(BasePluginTest):
-    @classmethod
-    def setUpClass(cls):
-        cls.compose = DockerCompose(filepath=dirname(inspect.getfile(cls)))
-        cls.compose.start()
+@pytest.fixture
+def prepare():
+    # type: () -> Callable
+    return lambda *_: requests.get('http://0.0.0.0:9090/users?test=test1&test=test2&test2=test2')
 
-        cls.compose.wait_for(cls.url(('consumer', '9090'), 'users?test=test1&test=test2&test2=test2'))
-
-    def test_plugin(self):
-        time.sleep(10)
 
+class TestPlugin(TestPluginBase):
+    @pytest.mark.parametrize('version', [
+        'django==2.0',
+        'django==2.2',
+        'django==3.0',
+        'django==3.1',
+    ])
+    def test_plugin(self, docker_compose, version):
         self.validate()
-
-
-if __name__ == '__main__':
-    unittest.main()
diff --git a/tests/plugin/sw_elasticsearch/docker-compose.yml b/tests/plugin/sw_elasticsearch/docker-compose.yml
index 1793521..9b008cc 100644
--- a/tests/plugin/sw_elasticsearch/docker-compose.yml
+++ b/tests/plugin/sw_elasticsearch/docker-compose.yml
@@ -49,8 +49,8 @@ services:
     ports:
       - 9090:9090
     volumes:
-      - ./services/consumer.py:/app/consumer.py
-    command: ['bash', '-c', 'pip install flask &&  pip install elasticsearch && python3 /app/consumer.py']
+      - .:/app
+    command: ['bash', '-c', 'pip install flask && pip install -r /app/requirements.txt && python3 /app/services/consumer.py']
     depends_on:
       collector:
         condition: service_healthy
diff --git a/tests/plugin/sw_elasticsearch/test_elasticsearch.py b/tests/plugin/sw_elasticsearch/test_elasticsearch.py
index c0064cf..85fe3f3 100644
--- a/tests/plugin/sw_elasticsearch/test_elasticsearch.py
+++ b/tests/plugin/sw_elasticsearch/test_elasticsearch.py
@@ -14,26 +14,23 @@
 # See the License for the specific language governing permissions and
 # limitations under the License.
 #
-import inspect
-import time
-import unittest
-from os.path import dirname
-from testcontainers.compose import DockerCompose
-from tests.plugin import BasePluginTest
+from typing import Callable
 
+import pytest
+import requests
 
-class TestPlugin(BasePluginTest):
-    @classmethod
-    def setUpClass(cls):
-        cls.compose = DockerCompose(filepath=dirname(inspect.getfile(cls)))
-        cls.compose.start()
-        cls.compose.wait_for(cls.url(('consumer', '9090'), 'users'))
+from tests.plugin.base import TestPluginBase
 
-    def test_plugin(self):
-        time.sleep(10)
 
-        self.validate()
+@pytest.fixture
+def prepare():
+    # type: () -> Callable
+    return lambda *_: requests.get('http://0.0.0.0:9090/users')
 
 
-if __name__ == '__main__':
-    unittest.main()
+class TestPlugin(TestPluginBase):
+    @pytest.mark.parametrize('version', [
+        'elasticsearch==7.9.0',
+    ])
+    def test_plugin(self, docker_compose, version):
+        self.validate()
diff --git a/tests/plugin/sw_flask/docker-compose.yml b/tests/plugin/sw_flask/docker-compose.yml
index 61f3be7..28565da 100644
--- a/tests/plugin/sw_flask/docker-compose.yml
+++ b/tests/plugin/sw_flask/docker-compose.yml
@@ -30,8 +30,8 @@ services:
     ports:
       - 9091:9091
     volumes:
-      - ./services/provider.py:/app/provider.py
-    command: ['bash', '-c', 'pip install flask && python3 /app/provider.py']
+      - .:/app
+    command: ['bash', '-c', 'pip install flask && pip install -r /app/requirements.txt && python3 /app/services/provider.py']
     depends_on:
       collector:
         condition: service_healthy
@@ -48,8 +48,8 @@ services:
     ports:
       - 9090:9090
     volumes:
-      - ./services/consumer.py:/app/consumer.py
-    command: ['bash', '-c', 'pip install flask && python3 /app/consumer.py']
+      - .:/app
+    command: ['bash', '-c', 'pip install flask && pip install -r /app/requirements.txt && python3 /app/services/consumer.py']
     depends_on:
       collector:
         condition: service_healthy
diff --git a/tests/plugin/sw_flask/test_flask.py b/tests/plugin/sw_flask/test_flask.py
index 4efe2e5..1f7c3b8 100644
--- a/tests/plugin/sw_flask/test_flask.py
+++ b/tests/plugin/sw_flask/test_flask.py
@@ -14,32 +14,28 @@
 # See the License for the specific language governing permissions and
 # limitations under the License.
 #
-import inspect
-import time
-import unittest
-from os.path import dirname
+from typing import Callable
 
+import pytest
 import requests
-from testcontainers.compose import DockerCompose
 
-from tests.plugin import BasePluginTest
+from tests.plugin.base import TestPluginBase
 
 
-class TestPlugin(BasePluginTest):
-    @classmethod
-    def setUpClass(cls):
-        cls.compose = DockerCompose(filepath=dirname(inspect.getfile(cls)))
-        cls.compose.start()
-        cls.compose.wait_for(cls.url(('consumer', '9090'), 'users?test=test1&test=test2&test2=test2'))
+@pytest.fixture
+def prepare():
+    # type: () -> Callable
+    return lambda *_: requests.get('http://0.0.0.0:9090/users?test=test1&test=test2&test2=test2')
 
-    def test_plugin(self):
-        time.sleep(10)
 
+class TestPlugin(TestPluginBase):
+    @pytest.mark.parametrize('version', [
+        'flask==1.1.2',
+        'flask==1.0.4',
+    ])
+    def test_plugin(self, docker_compose, version):
         self.validate()
-        response = requests.get(TestPlugin.url(('consumer', '9090'), 'users'))
-        self.assertEqual(response.status_code, 200)
-        self.assertEqual(response.json()["correlation"], "correlation")
 
-
-if __name__ == '__main__':
-    unittest.main()
+        response = requests.get('http://0.0.0.0:9090/users')
+        assert response.status_code == 200
+        assert response.json()['correlation'] == 'correlation'
diff --git a/tests/plugin/sw_http/test_http.py b/tests/plugin/sw_http/test_http.py
index 8aa24b4..d9317ba 100644
--- a/tests/plugin/sw_http/test_http.py
+++ b/tests/plugin/sw_http/test_http.py
@@ -14,24 +14,20 @@
 # See the License for the specific language governing permissions and
 # limitations under the License.
 #
+from typing import Callable
 
-import time
-import unittest
-
+import pytest
 import requests
 
-from tests.plugin import BasePluginTest
-
+from tests.plugin.base import TestPluginBase
 
-class TestPlugin(BasePluginTest):
 
-    def test_plugin(self):
-        print('traffic: ', requests.post(url=self.url(('consumer', '9090'))))
+@pytest.fixture
+def prepare():
+    # type: () -> Callable
+    return lambda *_: requests.post('http://0.0.0.0:9090')
 
-        time.sleep(10)
 
+class TestPlugin(TestPluginBase):
+    def test_plugin(self, docker_compose, version):
         self.validate()
-
-
-if __name__ == '__main__':
-    unittest.main()
diff --git a/tests/plugin/sw_http_wsgi/docker-compose.yml b/tests/plugin/sw_http_wsgi/docker-compose.yml
index 4233f03..db53dcd 100644
--- a/tests/plugin/sw_http_wsgi/docker-compose.yml
+++ b/tests/plugin/sw_http_wsgi/docker-compose.yml
@@ -30,7 +30,8 @@ services:
     ports:
       - 9091:9091
     volumes:
-      - ./services/provider.py:/entrypoint.py
+      - .:/app
+    command: ['bash', '-c', 'pip install -r /app/requirements.txt && python3 /app/services/provider.py']
     depends_on:
       collector:
         condition: service_healthy
@@ -47,7 +48,8 @@ services:
     ports:
       - 9090:9090
     volumes:
-      - ./services/consumer.py:/entrypoint.py
+      - .:/app
+    command: ['bash', '-c', 'pip install -r /app/requirements.txt && python3 /app/services/consumer.py']
     depends_on:
       collector:
         condition: service_healthy
diff --git a/tests/plugin/sw_http_wsgi/test_http_wsgi.py b/tests/plugin/sw_http_wsgi/test_http_wsgi.py
index 8aa24b4..15e0f69 100644
--- a/tests/plugin/sw_http_wsgi/test_http_wsgi.py
+++ b/tests/plugin/sw_http_wsgi/test_http_wsgi.py
@@ -14,24 +14,23 @@
 # See the License for the specific language governing permissions and
 # limitations under the License.
 #
+from typing import Callable
 
-import time
-import unittest
-
+import pytest
 import requests
 
-from tests.plugin import BasePluginTest
-
+from tests.plugin.base import TestPluginBase
 
-class TestPlugin(BasePluginTest):
 
-    def test_plugin(self):
-        print('traffic: ', requests.post(url=self.url(('consumer', '9090'))))
+@pytest.fixture
+def prepare():
+    # type: () -> Callable
+    return lambda *_: requests.post('http://0.0.0.0:9090')
 
-        time.sleep(10)
 
+class TestPlugin(TestPluginBase):
+    @pytest.mark.parametrize('version', [
+        'werkzeug==1.0.1',
+    ])
+    def test_plugin(self, docker_compose, version):
         self.validate()
-
-
-if __name__ == '__main__':
-    unittest.main()
diff --git a/tests/plugin/sw_kafka/docker-compose.yml b/tests/plugin/sw_kafka/docker-compose.yml
index 9f81de5..964869e 100644
--- a/tests/plugin/sw_kafka/docker-compose.yml
+++ b/tests/plugin/sw_kafka/docker-compose.yml
@@ -64,8 +64,8 @@ services:
     ports:
       - 9090:9090
     volumes:
-      - ./services/producer.py:/app/producer.py
-    command: ['bash', '-c', 'pip install flask && pip install kafka-python && python3 /app/producer.py']
+      - .:/app
+    command: ['bash', '-c', 'pip install flask && pip install -r /app/requirements.txt && python3 /app/services/producer.py']
     healthcheck:
       test: ["CMD", "bash", "-c", "cat < /dev/null > /dev/tcp/127.0.0.1/9090"]
       interval: 5s
@@ -86,10 +86,10 @@ services:
     ports:
       - 9091:9091
     volumes:
-      - ./services/consumer.py:/app/consumer.py
-    command: ['bash', '-c', 'pip install flask && pip install kafka-python && python3 /app/consumer.py']
+      - .:/app
+    command: ['bash', '-c', 'pip install flask && pip install -r /app/requirements.txt && python3 /app/services/consumer.py']
     healthcheck:
-      test: ["CMD", "bash", "-c", "ps -ef | grep /app/consumer | grep -v grep"]
+      test: ["CMD", "bash", "-c", "ps -ef | grep /app/services/consumer | grep -v grep"]
       interval: 5s
       timeout: 60s
       retries: 120
diff --git a/tests/plugin/sw_kafka/test_kafka.py b/tests/plugin/sw_kafka/test_kafka.py
index 62dfcb2..6f981a2 100644
--- a/tests/plugin/sw_kafka/test_kafka.py
+++ b/tests/plugin/sw_kafka/test_kafka.py
@@ -14,30 +14,23 @@
 # See the License for the specific language governing permissions and
 # limitations under the License.
 #
+from typing import Callable
 
-import os
-import time
-import unittest
-from os.path import abspath, dirname
+import pytest
+import requests
 
-from testcontainers.compose import DockerCompose
+from tests.plugin.base import TestPluginBase
 
-from tests.plugin import BasePluginTest
 
+@pytest.fixture
+def prepare():
+    # type: () -> Callable
+    return lambda *_: requests.get('http://0.0.0.0:9090/users')
 
-class TestPlugin(BasePluginTest):
-    @classmethod
-    def setUpClass(cls):
-        cls.compose = DockerCompose(filepath=dirname(abspath(__file__)))
-        cls.compose.start()
 
-        cls.compose.wait_for(cls.url(('producer', '9090'), 'users'))
-
-    def test_request_plugin(self):
-        time.sleep(10)
-
-        self.validate(expected_file_name=os.path.join(dirname(abspath(__file__)), 'expected.data.yml'))
-
-
-if __name__ == '__main__':
-    unittest.main()
+class TestPlugin(TestPluginBase):
+    @pytest.mark.parametrize('version', [
+        'kafka-python==2.0.1',
+    ])
+    def test_plugin(self, docker_compose, version):
+        self.validate()
diff --git a/tests/plugin/sw_pymongo/docker-compose.yml b/tests/plugin/sw_pymongo/docker-compose.yml
index 596f5be..e1a289c 100644
--- a/tests/plugin/sw_pymongo/docker-compose.yml
+++ b/tests/plugin/sw_pymongo/docker-compose.yml
@@ -43,8 +43,8 @@ services:
     ports:
       - 9091:9091
     volumes:
-      - ./services/provider.py:/app/provider.py
-    command: ['bash', '-c', 'pip install flask && pip install pymongo && python3 /app/provider.py']
+      - .:/app
+    command: ['bash', '-c', 'pip install flask && pip install -r /app/requirements.txt && python3 /app/services/provider.py']
     depends_on:
       collector:
         condition: service_healthy
@@ -63,8 +63,8 @@ services:
     ports:
       - 9090:9090
     volumes:
-      - ./services/consumer.py:/app/consumer.py
-    command: ['bash', '-c', 'pip install flask && python3 /app/consumer.py']
+      - .:/app
+    command: ['bash', '-c', 'pip install flask && python3 /app/services/consumer.py']
     depends_on:
       collector:
         condition: service_healthy
diff --git a/tests/plugin/sw_pymongo/test_pymongo.py b/tests/plugin/sw_pymongo/test_pymongo.py
index f7eb788..61f382e 100644
--- a/tests/plugin/sw_pymongo/test_pymongo.py
+++ b/tests/plugin/sw_pymongo/test_pymongo.py
@@ -14,29 +14,23 @@
 # See the License for the specific language governing permissions and
 # limitations under the License.
 #
-import inspect
-import time
-import unittest
-from os.path import dirname
+from typing import Callable
 
-from testcontainers.compose import DockerCompose
+import pytest
+import requests
 
-from tests.plugin import BasePluginTest
+from tests.plugin.base import TestPluginBase
 
 
-class TestPlugin(BasePluginTest):
-    @classmethod
-    def setUpClass(cls):
-        cls.compose = DockerCompose(filepath=dirname(inspect.getfile(cls)))
-        cls.compose.start()
+@pytest.fixture
+def prepare():
+    # type: () -> Callable
+    return lambda *_: requests.get('http://0.0.0.0:9090/users')
 
-        cls.compose.wait_for(cls.url(('consumer', '9090'), 'users'))
-
-    def test_plugin(self):
-        time.sleep(10)
 
+class TestPlugin(TestPluginBase):
+    @pytest.mark.parametrize('version', [
+        'pymongo==3.11.0',
+    ])
+    def test_plugin(self, docker_compose, version):
         self.validate()
-
-
-if __name__ == '__main__':
-    unittest.main()
diff --git a/tests/plugin/sw_pymysql/docker-compose.yml b/tests/plugin/sw_pymysql/docker-compose.yml
index b606417..4d0b558 100644
--- a/tests/plugin/sw_pymysql/docker-compose.yml
+++ b/tests/plugin/sw_pymysql/docker-compose.yml
@@ -47,8 +47,8 @@ services:
     ports:
       - 9091:9091
     volumes:
-      - ./services/provider.py:/app/provider.py
-    command: ['bash', '-c', 'pip install flask && pip install PyMySQL && python3 /app/provider.py']
+      - .:/app
+    command: ['bash', '-c', 'pip install flask && pip install -r /app/requirements.txt && python3 /app/services/provider.py']
     depends_on:
       collector:
         condition: service_healthy
@@ -67,8 +67,8 @@ services:
     ports:
       - 9090:9090
     volumes:
-      - ./services/consumer.py:/app/consumer.py
-    command: ['bash', '-c', 'pip install flask && python3 /app/consumer.py']
+      - .:/app
+    command: ['bash', '-c', 'pip install flask && python3 /app/services/consumer.py']
     depends_on:
       collector:
         condition: service_healthy
diff --git a/tests/plugin/sw_pymysql/test_pymysql.py b/tests/plugin/sw_pymysql/test_pymysql.py
index 9d7c0dd..c4af0a1 100644
--- a/tests/plugin/sw_pymysql/test_pymysql.py
+++ b/tests/plugin/sw_pymysql/test_pymysql.py
@@ -14,30 +14,23 @@
 # See the License for the specific language governing permissions and
 # limitations under the License.
 #
+from typing import Callable
 
-import os
-import time
-import unittest
-from os.path import abspath, dirname
+import pytest
+import requests
 
-from testcontainers.compose import DockerCompose
+from tests.plugin.base import TestPluginBase
 
-from tests.plugin import BasePluginTest
 
+@pytest.fixture
+def prepare():
+    # type: () -> Callable
+    return lambda *_: requests.get('http://0.0.0.0:9090/users')
 
-class TestPlugin(BasePluginTest):
-    @classmethod
-    def setUpClass(cls):
-        cls.compose = DockerCompose(filepath=dirname(abspath(__file__)))
-        cls.compose.start()
 
-        cls.compose.wait_for(cls.url(('consumer', '9090'), 'users'))
-
-    def test_request_plugin(self):
-        time.sleep(10)
-
-        self.validate(expected_file_name=os.path.join(dirname(abspath(__file__)), 'expected.data.yml'))
-
-
-if __name__ == '__main__':
-    unittest.main()
+class TestPlugin(TestPluginBase):
+    @pytest.mark.parametrize('version', [
+        'PyMySQL==0.10.0',
+    ])
+    def test_plugin(self, docker_compose, version):
+        self.validate()
diff --git a/tests/plugin/sw_rabbitmq/docker-compose.yml b/tests/plugin/sw_rabbitmq/docker-compose.yml
index ecbecec..acb8043 100644
--- a/tests/plugin/sw_rabbitmq/docker-compose.yml
+++ b/tests/plugin/sw_rabbitmq/docker-compose.yml
@@ -48,8 +48,8 @@ services:
     ports:
       - 9090:9090
     volumes:
-      - ./services/producer.py:/app/producer.py
-    command: ['bash', '-c', 'pip install flask && pip install pika && python3 /app/producer.py']
+      - .:/app
+    command: ['bash', '-c', 'pip install flask && pip install -r /app/requirements.txt && python3 /app/services/producer.py']
     healthcheck:
       test: ["CMD", "bash", "-c", "cat < /dev/null > /dev/tcp/127.0.0.1/9090"]
       interval: 5s
@@ -70,10 +70,10 @@ services:
     ports:
       - 9091:9091
     volumes:
-      - ./services/consumer.py:/app/consumer.py
-    command: ['bash', '-c', 'pip install flask && pip install pika && python3 /app/consumer.py']
+      - .:/app
+    command: ['bash', '-c', 'pip install flask && pip install -r /app/requirements.txt && python3 /app/services/consumer.py']
     healthcheck:
-      test: ["CMD", "bash", "-c", "ps -ef | grep /app/consumer | grep -v grep"]
+      test: ["CMD", "bash", "-c", "ps -ef | grep /app/services/consumer | grep -v grep"]
       interval: 5s
       timeout: 60s
       retries: 120
diff --git a/tests/plugin/sw_rabbitmq/test_kafka.py b/tests/plugin/sw_rabbitmq/test_kafka.py
deleted file mode 100644
index 62dfcb2..0000000
--- a/tests/plugin/sw_rabbitmq/test_kafka.py
+++ /dev/null
@@ -1,43 +0,0 @@
-#
-# Licensed to the Apache Software Foundation (ASF) under one or more
-# contributor license agreements.  See the NOTICE file distributed with
-# this work for additional information regarding copyright ownership.
-# The ASF licenses this file to You under the Apache License, Version 2.0
-# (the "License"); you may not use this file except in compliance with
-# the License.  You may obtain a copy of the License at
-#
-#     http://www.apache.org/licenses/LICENSE-2.0
-#
-# Unless required by applicable law or agreed to in writing, software
-# distributed under the License is distributed on an "AS IS" BASIS,
-# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-# See the License for the specific language governing permissions and
-# limitations under the License.
-#
-
-import os
-import time
-import unittest
-from os.path import abspath, dirname
-
-from testcontainers.compose import DockerCompose
-
-from tests.plugin import BasePluginTest
-
-
-class TestPlugin(BasePluginTest):
-    @classmethod
-    def setUpClass(cls):
-        cls.compose = DockerCompose(filepath=dirname(abspath(__file__)))
-        cls.compose.start()
-
-        cls.compose.wait_for(cls.url(('producer', '9090'), 'users'))
-
-    def test_request_plugin(self):
-        time.sleep(10)
-
-        self.validate(expected_file_name=os.path.join(dirname(abspath(__file__)), 'expected.data.yml'))
-
-
-if __name__ == '__main__':
-    unittest.main()
diff --git a/tests/plugin/sw_http/test_http.py b/tests/plugin/sw_rabbitmq/test_rabbitmq.py
similarity index 69%
copy from tests/plugin/sw_http/test_http.py
copy to tests/plugin/sw_rabbitmq/test_rabbitmq.py
index 8aa24b4..24086c9 100644
--- a/tests/plugin/sw_http/test_http.py
+++ b/tests/plugin/sw_rabbitmq/test_rabbitmq.py
@@ -14,24 +14,23 @@
 # See the License for the specific language governing permissions and
 # limitations under the License.
 #
+from typing import Callable
 
-import time
-import unittest
-
+import pytest
 import requests
 
-from tests.plugin import BasePluginTest
-
+from tests.plugin.base import TestPluginBase
 
-class TestPlugin(BasePluginTest):
 
-    def test_plugin(self):
-        print('traffic: ', requests.post(url=self.url(('consumer', '9090'))))
+@pytest.fixture
+def prepare():
+    # type: () -> Callable
+    return lambda *_: requests.get('http://0.0.0.0:9090/users')
 
-        time.sleep(10)
 
+class TestPlugin(TestPluginBase):
+    @pytest.mark.parametrize('version', [
+        'pika==1.1.0',
+    ])
+    def test_plugin(self, docker_compose, version):
         self.validate()
-
-
-if __name__ == '__main__':
-    unittest.main()
diff --git a/tests/plugin/sw_redis/docker-compose.yml b/tests/plugin/sw_redis/docker-compose.yml
index 202d816..441cfc2 100644
--- a/tests/plugin/sw_redis/docker-compose.yml
+++ b/tests/plugin/sw_redis/docker-compose.yml
@@ -43,8 +43,8 @@ services:
     ports:
       - 9091:9091
     volumes:
-      - ./services/provider.py:/app/provider.py
-    command: ['bash', '-c', 'pip install flask && pip install redis && python3 /app/provider.py']
+      - .:/app
+    command: ['bash', '-c', 'pip install flask && pip install -r /app/requirements.txt && python3 /app/services/provider.py']
     depends_on:
       collector:
         condition: service_healthy
@@ -62,8 +62,8 @@ services:
     ports:
       - 9090:9090
     volumes:
-      - ./services/consumer.py:/app/consumer.py
-    command: ['bash', '-c', 'pip install flask && python3 /app/consumer.py']
+      - .:/app
+    command: ['bash', '-c', 'pip install flask && python3 /app/services/consumer.py']
     depends_on:
       collector:
         condition: service_healthy
diff --git a/tests/plugin/sw_redis/test_redis.py b/tests/plugin/sw_redis/test_redis.py
index 9d7c0dd..3d240b5 100644
--- a/tests/plugin/sw_redis/test_redis.py
+++ b/tests/plugin/sw_redis/test_redis.py
@@ -14,30 +14,23 @@
 # See the License for the specific language governing permissions and
 # limitations under the License.
 #
+from typing import Callable
 
-import os
-import time
-import unittest
-from os.path import abspath, dirname
+import pytest
+import requests
 
-from testcontainers.compose import DockerCompose
+from tests.plugin.base import TestPluginBase
 
-from tests.plugin import BasePluginTest
 
+@pytest.fixture
+def prepare():
+    # type: () -> Callable
+    return lambda *_: requests.get('http://0.0.0.0:9090/users')
 
-class TestPlugin(BasePluginTest):
-    @classmethod
-    def setUpClass(cls):
-        cls.compose = DockerCompose(filepath=dirname(abspath(__file__)))
-        cls.compose.start()
 
-        cls.compose.wait_for(cls.url(('consumer', '9090'), 'users'))
-
-    def test_request_plugin(self):
-        time.sleep(10)
-
-        self.validate(expected_file_name=os.path.join(dirname(abspath(__file__)), 'expected.data.yml'))
-
-
-if __name__ == '__main__':
-    unittest.main()
+class TestPlugin(TestPluginBase):
+    @pytest.mark.parametrize('version', [
+        'redis==3.5.3',
+    ])
+    def test_plugin(self, docker_compose, version):
+        self.validate()
diff --git a/tests/plugin/sw_requests/docker-compose.yml b/tests/plugin/sw_requests/docker-compose.yml
index a0a80d4..b8c9561 100644
--- a/tests/plugin/sw_requests/docker-compose.yml
+++ b/tests/plugin/sw_requests/docker-compose.yml
@@ -30,10 +30,11 @@ services:
     ports:
       - 9091:9091
     volumes:
-      - ./services/provider.py:/entrypoint.py
+      - .:/app
     depends_on:
       collector:
         condition: service_healthy
+    command: ['bash', '-c', 'pip install -r /app/requirements.txt && python3 /app/services/provider.py']
     healthcheck:
       test: ["CMD", "bash", "-c", "cat < /dev/null > /dev/tcp/127.0.0.1/9091"]
       interval: 5s
@@ -47,13 +48,13 @@ services:
     ports:
       - 9090:9090
     volumes:
-      - ./services/consumer.py:/entrypoint.py
+      - .:/app
+    command: ['bash', '-c', 'pip install -r /app/requirements.txt && python3 /app/services/consumer.py']
     depends_on:
       collector:
         condition: service_healthy
       provider:
         condition: service_healthy
 
-
 networks:
-  beyond:
\ No newline at end of file
+  beyond:
diff --git a/tests/plugin/sw_requests/test_request.py b/tests/plugin/sw_requests/test_request.py
index 8aa24b4..7b50c2f 100644
--- a/tests/plugin/sw_requests/test_request.py
+++ b/tests/plugin/sw_requests/test_request.py
@@ -14,24 +14,30 @@
 # See the License for the specific language governing permissions and
 # limitations under the License.
 #
+from typing import Callable
 
-import time
-import unittest
-
+import pytest
 import requests
 
-from tests.plugin import BasePluginTest
-
+from tests.plugin.base import TestPluginBase
 
-class TestPlugin(BasePluginTest):
 
-    def test_plugin(self):
-        print('traffic: ', requests.post(url=self.url(('consumer', '9090'))))
+@pytest.fixture
+def prepare():
+    # type: () -> Callable
+    return lambda *_: requests.post('http://0.0.0.0:9090')
 
-        time.sleep(10)
 
+class TestPlugin(TestPluginBase):
+    @pytest.mark.parametrize('version', [
+        'requests==2.24.0',
+        'requests==2.20.0',
+        'requests==2.19.0',
+        'requests==2.18.0',
+        'requests==2.17.0',
+        'requests==2.13.0',
+        'requests==2.11.0',
+        'requests==2.9.0',
+    ])
+    def test_plugin(self, docker_compose, version):
         self.validate()
-
-
-if __name__ == '__main__':
-    unittest.main()
diff --git a/tests/plugin/sw_tornado/docker-compose.yml b/tests/plugin/sw_tornado/docker-compose.yml
index 98bb544..06eefbd 100644
--- a/tests/plugin/sw_tornado/docker-compose.yml
+++ b/tests/plugin/sw_tornado/docker-compose.yml
@@ -30,8 +30,8 @@ services:
     ports:
       - 9091:9091
     volumes:
-      - ./services/provider.py:/provider.py
-    command: ['bash', '-c', 'pip3 install tornado && python3 /provider.py']
+      - .:/app
+    command: ['bash', '-c', 'pip3 install -r /app/requirements.txt && python3 /app/services/provider.py']
     depends_on:
       collector:
         condition: service_healthy
@@ -48,8 +48,8 @@ services:
     ports:
       - 9090:9090
     volumes:
-      - ./services/consumer.py:/consumer.py
-    command: ['bash', '-c', 'pip3 install tornado && python3 /consumer.py']
+      - .:/app
+    command: ['bash', '-c', 'pip3 install -r /app/requirements.txt && python3 /app/services/consumer.py']
     depends_on:
       collector:
         condition: service_healthy
diff --git a/tests/plugin/sw_tornado/test_tornado.py b/tests/plugin/sw_tornado/test_tornado.py
index f7eb788..cd27fb7 100644
--- a/tests/plugin/sw_tornado/test_tornado.py
+++ b/tests/plugin/sw_tornado/test_tornado.py
@@ -14,29 +14,23 @@
 # See the License for the specific language governing permissions and
 # limitations under the License.
 #
-import inspect
-import time
-import unittest
-from os.path import dirname
+from typing import Callable
 
-from testcontainers.compose import DockerCompose
+import pytest
+import requests
 
-from tests.plugin import BasePluginTest
+from tests.plugin.base import TestPluginBase
 
 
-class TestPlugin(BasePluginTest):
-    @classmethod
-    def setUpClass(cls):
-        cls.compose = DockerCompose(filepath=dirname(inspect.getfile(cls)))
-        cls.compose.start()
+@pytest.fixture
+def prepare():
+    # type: () -> Callable
+    return lambda *_: requests.get('http://0.0.0.0:9090/users')
 
-        cls.compose.wait_for(cls.url(('consumer', '9090'), 'users'))
-
-    def test_plugin(self):
-        time.sleep(10)
 
+class TestPlugin(TestPluginBase):
+    @pytest.mark.parametrize('version', [
+        'tornado==6.0.4',
+    ])
+    def test_plugin(self, docker_compose, version):
         self.validate()
-
-
-if __name__ == '__main__':
-    unittest.main()