diff --git a/.gitignore b/.gitignore
index a7f7de0..e2967ea 100644
--- a/.gitignore
+++ b/.gitignore
@@ -1,6 +1,7 @@
 # ignore IDE files
 .vscode
 .idea
+venv.*
 
 # ignore temp files under any path
 .DS_Store
diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md
index 5996820..fdcaec7 100644
--- a/CONTRIBUTING.md
+++ b/CONTRIBUTING.md
@@ -63,9 +63,9 @@ $ git checkout -b feature/123-something_awesome
 ```
 
 ### Test your changes
-Before submitting any changes to the library, please, make sure that all unit tests are passing. To run the tests, please, use the following command:
+Before submitting any changes to the library, please, make sure that linter and all unit tests are passing. To run the tests, please, use the following command:
 ```shell
-$ python -m unittest discover --start-directory tests
+$ make validation
 ```
 
 To enable tests that interact with SSH server, please, setup SSH server and set the following environment variables before running the tests:
diff --git a/Makefile b/Makefile
index c746608..9dbd86c 100644
--- a/Makefile
+++ b/Makefile
@@ -3,6 +3,8 @@ PYTHON_VERSION := 3.10
 VENV_DIR := venv.frostfs-testlib
 
 current_dir := $(shell pwd)
+DIRECTORIES := $(sort $(dir $(wildcard ../frostfs-testlib-plugin-*/ ../*-testcases/)))
+FROM_VENV := . ${VENV_DIR}/bin/activate &&
 
 venv: create requirements paths precommit
 	@echo Ready
@@ -14,14 +16,34 @@ precommit:
 paths:
 	@echo Append paths for project
 	@echo Virtual environment: ${VENV_DIR}
-	@sudo rm -rf ${VENV_DIR}/lib/python${PYTHON_VERSION}/site-packages/_paths.pth
-	@sudo touch ${VENV_DIR}/lib/python${PYTHON_VERSION}/site-packages/_paths.pth
-	@echo ${current_dir}/src/frostfs_testlib_frostfs_testlib | sudo tee ${VENV_DIR}/lib/python${PYTHON_VERSION}/site-packages/_paths.pth
+	@rm -rf ${VENV_DIR}/lib/python${PYTHON_VERSION}/site-packages/_paths.pth
+	@touch ${VENV_DIR}/lib/python${PYTHON_VERSION}/site-packages/_paths.pth
+	@echo ${current_dir}/src/frostfs_testlib_frostfs_testlib | tee ${VENV_DIR}/lib/python${PYTHON_VERSION}/site-packages/_paths.pth
 
-create:
-	@echo Create virtual environment for 
+create: ${VENV_DIR}
+
+${VENV_DIR}:
+	@echo Create virtual environment ${VENV_DIR}
 	virtualenv --python=python${PYTHON_VERSION} --prompt=frostfs-testlib ${VENV_DIR}
 
 requirements:
 	@echo Isntalling pip requirements
-	. ${VENV_DIR}/bin/activate && pip install -Ur requirements.txt
\ No newline at end of file
+	. ${VENV_DIR}/bin/activate && pip install -Ur requirements.txt
+
+
+#### VALIDATION SECTION ####
+lint: create requirements
+	${FROM_VENV} pylint --disable R,C,W ./src
+
+unit_test:
+	@echo Starting unit tests
+	${FROM_VENV} python -m pytest tests
+
+.PHONY: lint_dependent $(DIRECTORIES)
+lint_dependent: $(DIRECTORIES)
+
+$(DIRECTORIES):
+	@echo checking dependent repo $@
+	$(MAKE) validation -C $@
+
+validation: lint unit_test lint_dependent
\ No newline at end of file
diff --git a/pyproject.toml b/pyproject.toml
index 9140ee0..8fca533 100644
--- a/pyproject.toml
+++ b/pyproject.toml
@@ -64,3 +64,8 @@ push            = false
 [tool.bumpver.file_patterns]
 "pyproject.toml" = ['current_version = "{version}"', 'version = "{version}"']
 "src/frostfs_testlib/__init__.py" = ["{version}"]
+
+[tool.pytest.ini_options]
+filterwarnings = [
+    "ignore:Blowfish has been deprecated:cryptography.utils.CryptographyDeprecationWarning",
+]
\ No newline at end of file
diff --git a/requirements.txt b/requirements.txt
index 5b47640..1fdf844 100644
--- a/requirements.txt
+++ b/requirements.txt
@@ -17,6 +17,7 @@ black==22.8.0
 bumpver==2022.1118
 isort==5.12.0
 pre-commit==2.20.0
+pylint==2.17.4
 
 # Packaging dependencies
 build==0.8.0
diff --git a/src/frostfs_testlib/analytics/test_collector.py b/src/frostfs_testlib/analytics/test_collector.py
index 0f5398e..56ee606 100644
--- a/src/frostfs_testlib/analytics/test_collector.py
+++ b/src/frostfs_testlib/analytics/test_collector.py
@@ -6,6 +6,7 @@ from docstring_parser.google import DEFAULT_SECTIONS, Section, SectionType
 
 DEFAULT_SECTIONS.append(Section("Steps", "steps", SectionType.MULTIPLE))
 
+
 class TestCase:
     """
     Test case object implementation for use in collector and exporters
@@ -106,7 +107,9 @@ class TestCaseCollector:
         # Read test_case suite and section name from test class if possible and get test function from class
         if test.cls:
             suite_name = test.cls.__dict__.get("__test_case_suite_name__", suite_name)
-            suite_section_name = test.cls.__dict__.get("__test_case_suite_section__", suite_section_name)
+            suite_section_name = test.cls.__dict__.get(
+                "__test_case_suite_section__", suite_section_name
+            )
             test_function = test.cls.__dict__[test.originalname]
         else:
             # If no test class, read test function from module
@@ -117,7 +120,9 @@ class TestCaseCollector:
         test_case_title = test_function.__dict__.get("__test_case_title__", None)
         test_case_priority = test_function.__dict__.get("__test_case_priority__", None)
         suite_name = test_function.__dict__.get("__test_case_suite_name__", suite_name)
-        suite_section_name = test_function.__dict__.get("__test_case_suite_section__", suite_section_name)
+        suite_section_name = test_function.__dict__.get(
+            "__test_case_suite_section__", suite_section_name
+        )
 
         # Parce test_steps if they define in __doc__
         doc_string = parse(test_function.__doc__, style=DocstringStyle.GOOGLE)
@@ -125,7 +130,9 @@ class TestCaseCollector:
         if doc_string.short_description:
             test_case_description = doc_string.short_description
             if doc_string.long_description:
-                test_case_description = f"{doc_string.short_description}\r\n{doc_string.long_description}"
+                test_case_description = (
+                    f"{doc_string.short_description}\r\n{doc_string.long_description}"
+                )
 
         if doc_string.meta:
             for meta in doc_string.meta:
@@ -140,25 +147,27 @@ class TestCaseCollector:
             test_case_params = test_case_call_spec.id
             # Format title with params
             if test_case_title:
-                test_case_title = self.__format_string_with_params__(test_case_title,test_case_call_spec.params)
+                test_case_title = self.__format_string_with_params__(
+                    test_case_title, test_case_call_spec.params
+                )
             # Format steps with params
             if test_case_steps:
                 for key, value in test_case_steps.items():
-                    value = self.__format_string_with_params__(value,test_case_call_spec.params)
+                    value = self.__format_string_with_params__(value, test_case_call_spec.params)
                     test_case_steps[key] = value
 
         # If there is set basic test case attributes create TestCase and return
         if test_case_id and test_case_title and suite_name and suite_name:
             test_case = TestCase(
-                            id=test_case_id,
-                            title=test_case_title,
-                            description=test_case_description,
-                            priority=test_case_priority,
-                            steps=test_case_steps,
-                            params=test_case_params,
-                            suite_name=suite_name,
-                            suite_section_name=suite_section_name,
-                        )
+                uuid_id=test_case_id,
+                title=test_case_title,
+                description=test_case_description,
+                priority=test_case_priority,
+                steps=test_case_steps,
+                params=test_case_params,
+                suite_name=suite_name,
+                suite_section_name=suite_section_name,
+            )
             return test_case
         # Return None if there is no enough information for return test case
         return None
@@ -187,4 +196,4 @@ class TestCaseCollector:
             test_case = self.__get_test_case_from_pytest_test__(test)
             if test_case:
                 test_cases.append(test_case)
-        return test_cases
\ No newline at end of file
+        return test_cases
diff --git a/src/frostfs_testlib/analytics/test_exporter.py b/src/frostfs_testlib/analytics/test_exporter.py
index 263995c..5a569c6 100644
--- a/src/frostfs_testlib/analytics/test_exporter.py
+++ b/src/frostfs_testlib/analytics/test_exporter.py
@@ -67,6 +67,6 @@ class TestExporter(ABC):
             steps = [{"content": value, "expected": " "} for key, value in test_case.steps.items()]
 
             if test_case_in_tms:
-                self.update_test_case(test_case, test_case_in_tms)
+                self.update_test_case(test_case, test_case_in_tms, test_suite, test_section)
             else:
-                self.create_test_case(test_case)
+                self.create_test_case(test_case, test_suite, test_section)
diff --git a/src/frostfs_testlib/hosting/docker_host.py b/src/frostfs_testlib/hosting/docker_host.py
index 94ee2ff..3addd92 100644
--- a/src/frostfs_testlib/hosting/docker_host.py
+++ b/src/frostfs_testlib/hosting/docker_host.py
@@ -135,13 +135,19 @@ class DockerHost(Host):
             timeout=service_attributes.start_timeout,
         )
 
-    def wait_for_service_to_be_in_state(self, systemd_service_name: str, expected_state: str, timeout: int) -> None:
+    def wait_for_service_to_be_in_state(
+        self, systemd_service_name: str, expected_state: str, timeout: int
+    ) -> None:
         raise NotImplementedError("Not implemented for docker")
 
-
     def get_data_directory(self, service_name: str) -> str:
         service_attributes = self._get_service_attributes(service_name)
-        return service_attributes.data_directory_path
+
+        client = self._get_docker_client()
+        volume_info = client.inspect_volume(service_attributes.volume_name)
+        volume_path = volume_info["Mountpoint"]
+
+        return volume_path
 
     def delete_metabase(self, service_name: str) -> None:
         raise NotImplementedError("Not implemented for docker")
@@ -159,11 +165,7 @@ class DockerHost(Host):
         raise NotImplementedError("Not implemented for docker")
 
     def delete_storage_node_data(self, service_name: str, cache_only: bool = False) -> None:
-        service_attributes = self._get_service_attributes(service_name)
-
-        client = self._get_docker_client()
-        volume_info = client.inspect_volume(service_attributes.volume_name)
-        volume_path = volume_info["Mountpoint"]
+        volume_path = self.get_data_directory(service_name)
 
         shell = self.get_shell()
         meta_clean_cmd = f"rm -rf {volume_path}/meta*/*"
diff --git a/src/frostfs_testlib/utils/cli_utils.py b/src/frostfs_testlib/utils/cli_utils.py
index 7ed1a27..d869714 100644
--- a/src/frostfs_testlib/utils/cli_utils.py
+++ b/src/frostfs_testlib/utils/cli_utils.py
@@ -68,9 +68,7 @@ def _cmd_run(cmd: str, timeout: int = 90) -> str:
         end_time = datetime.now()
         _attach_allure_log(cmd, cmd_output, return_code, start_time, end_time)
         logger.info(
-            f"Command: {cmd}\n"
-            f"Error:\nreturn code: {return_code}\n"
-            f"Output: {exc.output.decode('utf-8') if type(exc.output) is bytes else exc.output}"
+            f"Command: {cmd}\n" f"Error:\nreturn code: {return_code}\n" f"Output: {cmd_output}"
         )
         raise
 
diff --git a/tests/conftest.py b/tests/conftest.py
new file mode 100644
index 0000000..ea6d681
--- /dev/null
+++ b/tests/conftest.py
@@ -0,0 +1,5 @@
+import os
+import sys
+
+app_dir = os.path.join(os.getcwd(), "src")
+sys.path.insert(0, app_dir)
diff --git a/tests/helpers.py b/tests/helpers.py
index 8391002..b7776fd 100644
--- a/tests/helpers.py
+++ b/tests/helpers.py
@@ -14,11 +14,7 @@ def format_error_details(error: Exception) -> str:
     Returns:
         String containing exception details.
     """
-    detail_lines = traceback.format_exception(
-        etype=type(error),
-        value=error,
-        tb=error.__traceback__,
-    )
+    detail_lines = traceback.format_exception(error)
     return "".join(detail_lines)