Skip to main content
Glama

mcp-dbutils

MIT License
81
  • Linux
  • Apple
testing.md22.4 kB
# Руководство по тестированию *[English](../../en/technical/testing.md) | [中文](../../zh/technical/testing.md) | [Français](../../fr/technical/testing.md) | [Español](../../es/technical/testing.md) | [العربية](../../ar/technical/testing.md) | Русский* Этот документ описывает стратегию тестирования, инструменты и лучшие практики для тестирования MCP Database Utilities. ## Философия тестирования Наш подход к тестированию основан на следующих принципах: 1. **Всеобъемлющие тесты**: Покрытие всех аспектов кода, от отдельных функций до полной системы 2. **Автоматизированные тесты**: Все тесты должны быть автоматизированы и выполняемы через CI/CD 3. **Быстрые тесты**: Тесты должны выполняться быстро для обеспечения быстрой обратной связи 4. **Изолированные тесты**: Каждый тест должен быть независимым и не зависеть от других тестов 5. **Детерминированные тесты**: Тесты должны давать одинаковые результаты при каждом выполнении ## Типы тестов ### Модульные тесты Модульные тесты проверяют поведение отдельных компонентов (функций, классов, методов) в изоляции. **Характеристики**: - Быстрое выполнение - Тестируют одну единицу кода - Используют моки для изоляции тестируемого кода - Не зависят от внешних ресурсов (баз данных, сети и т.д.) **Пример**: ```python def test_query_validator(): """Тест, что валидатор запросов правильно идентифицирует неавторизованные запросы.""" validator = QueryValidator() # Валидный SELECT-запрос assert validator.validate("SELECT * FROM users") is True # Неавторизованные запросы assert validator.validate("INSERT INTO users VALUES (1, 'test')") is False assert validator.validate("UPDATE users SET name = 'test' WHERE id = 1") is False assert validator.validate("DELETE FROM users WHERE id = 1") is False assert validator.validate("DROP TABLE users") is False ``` ### Интеграционные тесты Интеграционные тесты проверяют, что различные компоненты правильно работают вместе. **Характеристики**: - Медленнее модульных тестов - Тестируют взаимодействие между несколькими компонентами - Могут использовать реальные или моковые внешние ресурсы - Проверяют, что интерфейсы между компонентами работают правильно **Пример**: ```python def test_sqlite_adapter_integration(): """Интеграционный тест между SQLite-адаптером и менеджером запросов.""" # Создание SQLite базы данных в памяти config = {"type": "sqlite", "path": ":memory:"} adapter = SQLiteAdapter(config) # Создание тестовой таблицы connection = adapter.connect() cursor = connection.cursor() cursor.execute("CREATE TABLE test (id INTEGER PRIMARY KEY, name TEXT)") cursor.execute("INSERT INTO test VALUES (1, 'test1'), (2, 'test2')") connection.commit() # Создание менеджера запросов с адаптером query_manager = QueryManager(adapter) # Выполнение запроса через менеджер results = query_manager.execute("SELECT * FROM test WHERE id = 1") # Проверка результатов assert len(results) == 1 assert results[0]["id"] == 1 assert results[0]["name"] == "test1" ``` ### End-to-End тесты End-to-End тесты проверяют, что полная система работает правильно от начала до конца. **Характеристики**: - Самые медленные в выполнении - Тестируют полную систему в среде, близкой к продакшену - Используют реальные внешние ресурсы - Проверяют реальные сценарии использования **Пример**: ```python def test_mcp_server_end_to_end(): """End-to-End тест MCP-сервера с реальной базой данных.""" # Запуск MCP-сервера с тестовой конфигурацией config_path = "tests/fixtures/test_config.yaml" server = MCPServer(config_path) server.start() try: # Создание моковского MCP-клиента client = MockMCPClient() # Выполнение запроса через MCP-протокол response = client.execute_tool("dbutils-list-tables", {"connection": "test-sqlite"}) # Проверка ответа assert "tables" in response assert "test" in response["tables"] # Выполнение SQL-запроса response = client.execute_tool("dbutils-run-query", { "connection": "test-sqlite", "query": "SELECT * FROM test WHERE id = 1" }) # Проверка результатов assert len(response["results"]) == 1 assert response["results"][0]["id"] == 1 assert response["results"][0]["name"] == "test1" finally: # Остановка сервера server.stop() ``` ### Тесты производительности Тесты производительности проверяют, что система соответствует требованиям к производительности. **Характеристики**: - Измеряют время выполнения, использование памяти и т.д. - Проверяют, что система может справиться с ожидаемой нагрузкой - Выявляют узкие места **Пример**: ```python def test_query_performance(): """Тест производительности запросов.""" # Настройка адаптера с тестовой базой данных config = {"type": "sqlite", "path": ":memory:"} adapter = SQLiteAdapter(config) # Создание тестовой таблицы с множеством строк connection = adapter.connect() cursor = connection.cursor() cursor.execute("CREATE TABLE test (id INTEGER PRIMARY KEY, name TEXT)") # Вставка 10000 строк for i in range(10000): cursor.execute("INSERT INTO test VALUES (?, ?)", (i, f"test{i}")) connection.commit() # Измерение времени выполнения запроса start_time = time.time() results = adapter.execute_query("SELECT * FROM test WHERE id < 1000") end_time = time.time() # Проверка, что запрос выполняется менее чем за 100 мс assert end_time - start_time < 0.1 assert len(results) == 1000 ``` ### Тесты безопасности Тесты безопасности проверяют, что система защищена от атак. **Характеристики**: - Проверяют, что система устойчива к известным атакам - Тестируют механизмы безопасности - Выявляют потенциальные уязвимости **Пример**: ```python def test_sql_injection_prevention(): """Тест, что система защищена от SQL-инъекций.""" # Настройка адаптера с тестовой базой данных config = {"type": "sqlite", "path": ":memory:"} adapter = SQLiteAdapter(config) # Создание тестовой таблицы connection = adapter.connect() cursor = connection.cursor() cursor.execute("CREATE TABLE users (id INTEGER PRIMARY KEY, username TEXT, password TEXT)") cursor.execute("INSERT INTO users VALUES (1, 'admin', 'secret')") connection.commit() # Создание менеджера запросов с адаптером query_manager = QueryManager(adapter) # Попытка SQL-инъекции malicious_query = "SELECT * FROM users WHERE username = 'admin' OR 1=1 --'" # Проверка, что менеджер запросов обнаруживает и предотвращает инъекцию with pytest.raises(SecurityException): query_manager.execute(malicious_query) ``` ## Инструменты тестирования ### pytest Мы используем [pytest](https://docs.pytest.org/) в качестве основного фреймворка для тестирования: ```bash # Установка pytest и плагинов pip install pytest pytest-cov pytest-mock # Запуск всех тестов pytest # Запуск конкретных тестов pytest tests/unit/ pytest tests/integration/ # Запуск с покрытием кода pytest --cov=mcp_dbutils # Создание HTML-отчета о покрытии pytest --cov=mcp_dbutils --cov-report=html ``` ### pytest фикстуры Фикстуры pytest позволяют настраивать среду тестирования: ```python @pytest.fixture def sqlite_adapter(): """Фикстура, предоставляющая SQLite-адаптер, настроенный с базой данных в памяти.""" config = {"type": "sqlite", "path": ":memory:"} adapter = SQLiteAdapter(config) # Создание тестовой таблицы connection = adapter.connect() cursor = connection.cursor() cursor.execute("CREATE TABLE test (id INTEGER PRIMARY KEY, name TEXT)") cursor.execute("INSERT INTO test VALUES (1, 'test1'), (2, 'test2')") connection.commit() yield adapter # Очистка adapter.disconnect() def test_with_fixture(sqlite_adapter): """Тест SQLite-адаптера с использованием фикстуры.""" results = sqlite_adapter.execute_query("SELECT * FROM test WHERE id = 1") assert len(results) == 1 assert results[0]["id"] == 1 assert results[0]["name"] == "test1" ``` ### Моки Мы используем `unittest.mock` или `pytest-mock` для моков: ```python def test_with_mock(mocker): """Тест функции с использованием моков.""" # Создание мока для соединения с базой данных mock_connection = mocker.MagicMock() mock_cursor = mocker.MagicMock() mock_connection.cursor.return_value = mock_cursor # Настройка мока для возврата конкретных результатов mock_cursor.fetchall.return_value = [("test1",), ("test2",)] mock_cursor.description = [("name",)] # Замена метода соединения моком mocker.patch("mcp_dbutils.adapters.sqlite.sqlite3.connect", return_value=mock_connection) # Создание адаптера adapter = SQLiteAdapter({"type": "sqlite", "path": "dummy.db"}) # Выполнение запроса results = adapter.execute_query("SELECT name FROM test") # Проверка, что запрос был выполнен правильно mock_cursor.execute.assert_called_once_with("SELECT name FROM test") assert len(results) == 2 assert results[0]["name"] == "test1" assert results[1]["name"] == "test2" ``` ### Тестовые базы данных Для интеграционных тестов мы используем реальные базы данных: - **SQLite**: база данных в памяти (`:memory:`) - **PostgreSQL**: Docker-версия для тестов - **MySQL**: Docker-версия для тестов ```python @pytest.fixture(scope="session") def postgres_adapter(): """Фикстура, предоставляющая PostgreSQL-адаптер, настроенный с тестовой базой данных.""" # Запуск PostgreSQL с Docker container = start_postgres_container() # Ожидание готовности PostgreSQL wait_for_postgres(container) # Настройка адаптера config = { "type": "postgres", "host": "localhost", "port": container.get_exposed_port(5432), "dbname": "testdb", "user": "postgres", "password": "postgres" } adapter = PostgreSQLAdapter(config) # Создание тестовой таблицы connection = adapter.connect() cursor = connection.cursor() cursor.execute("CREATE TABLE test (id SERIAL PRIMARY KEY, name TEXT)") cursor.execute("INSERT INTO test (name) VALUES ('test1'), ('test2')") connection.commit() yield adapter # Очистка adapter.disconnect() container.stop() ``` ## Организация тестов Тесты организованы в директории `tests/`: ``` tests/ ├── __init__.py ├── conftest.py # Общие фикстуры ├── fixtures/ # Тестовые данные │ ├── sqlite_schema.sql # Схема SQLite для тестов │ ├── postgres_schema.sql # Схема PostgreSQL для тестов │ └── mysql_schema.sql # Схема MySQL для тестов ├── unit/ # Модульные тесты │ ├── __init__.py │ ├── test_adapters.py # Тесты адаптеров │ ├── test_config.py # Тесты конфигурации │ ├── test_mcp.py # Тесты MCP-протокола │ └── test_query.py # Тесты обработки запросов ├── integration/ # Интеграционные тесты │ ├── __init__.py │ ├── test_sqlite.py # Интеграционные тесты SQLite │ ├── test_postgres.py # Интеграционные тесты PostgreSQL │ └── test_mysql.py # Интеграционные тесты MySQL └── e2e/ # End-to-End тесты ├── __init__.py └── test_mcp_server.py # Тесты MCP-сервера ``` ## Лучшие практики ### Именование тестов Следуйте этим соглашениям об именовании: - Файлы тестов начинаются с `test_` - Функции тестов начинаются с `test_` - Имена тестов должны быть описательными и указывать на то, что тестируется ```python # Хорошо def test_query_validator_rejects_insert_statements(): ... # Плохо def test_validator(): ... ``` ### Утверждения Используйте утверждения pytest для более ясных сообщений об ошибках: ```python # Хорошо assert result == expected, f"Ожидалось {expected}, получено {result}" # Плохо if result != expected: raise AssertionError("Тест не пройден") ``` ### Изоляция тестов Каждый тест должен быть независимым: - Не зависит от состояния, оставленного другими тестами - Использует фикстуры для настройки и очистки среды - Избегает глобальных или общих переменных ```python # Хорошо def test_independent_1(sqlite_adapter): sqlite_adapter.execute_query("INSERT INTO test VALUES (3, 'test3')") results = sqlite_adapter.execute_query("SELECT * FROM test WHERE id = 3") assert len(results) == 1 def test_independent_2(sqlite_adapter): # Этот тест не зависит от состояния, созданного test_independent_1 results = sqlite_adapter.execute_query("SELECT * FROM test WHERE id = 1") assert len(results) == 1 ``` ### Параметризованные тесты Используйте параметризованные тесты для тестирования множества случаев: ```python @pytest.mark.parametrize("query,is_valid", [ ("SELECT * FROM users", True), ("INSERT INTO users VALUES (1, 'test')", False), ("UPDATE users SET name = 'test' WHERE id = 1", False), ("DELETE FROM users WHERE id = 1", False), ("DROP TABLE users", False), ]) def test_query_validator_parametrized(query, is_valid): """Тест, что валидатор запросов правильно идентифицирует неавторизованные запросы.""" validator = QueryValidator() assert validator.validate(query) is is_valid ``` ### Регрессионные тесты Создавайте специфические тесты для исправленных ошибок: ```python def test_regression_issue_123(): """Тест, что ошибка #123 исправлена.""" # Ошибка была: SQLite-адаптер неправильно обрабатывал NULL-значения config = {"type": "sqlite", "path": ":memory:"} adapter = SQLiteAdapter(config) # Создание тестовой таблицы connection = adapter.connect() cursor = connection.cursor() cursor.execute("CREATE TABLE test (id INTEGER PRIMARY KEY, name TEXT)") cursor.execute("INSERT INTO test VALUES (1, NULL)") connection.commit() # Проверка, что NULL-значения обрабатываются правильно results = adapter.execute_query("SELECT * FROM test WHERE id = 1") assert len(results) == 1 assert results[0]["id"] == 1 assert results[0]["name"] is None ``` ## Непрерывная интеграция Тесты автоматически запускаются через GitHub Actions: ```yaml # .github/workflows/test.yml name: Test on: push: branches: [ main ] pull_request: branches: [ main ] jobs: test: runs-on: ubuntu-latest strategy: matrix: python-version: [3.10, 3.11, 3.12] steps: - uses: actions/checkout@v3 - name: Set up Python ${{ matrix.python-version }} uses: actions/setup-python@v4 with: python-version: ${{ matrix.python-version }} - name: Install dependencies run: | python -m pip install --upgrade pip pip install -e ".[dev,test]" - name: Lint with ruff run: | ruff check . - name: Test with pytest run: | pytest --cov=mcp_dbutils --cov-report=xml - name: Upload coverage to Codecov uses: codecov/codecov-action@v3 with: file: ./coverage.xml ``` ## Покрытие кода Мы стремимся к покрытию кода не менее 90%: ```bash # Запуск тестов с покрытием pytest --cov=mcp_dbutils # Создание HTML-отчета о покрытии pytest --cov=mcp_dbutils --cov-report=html ``` Отчет о покрытии доступен в директории `htmlcov/`. ## Устранение неполадок с тестами ### Неудачные тесты Если тест не проходит: 1. Внимательно прочитайте сообщение об ошибке 2. Проверьте неудачные утверждения 3. Используйте `pytest -v` для получения дополнительной информации 4. Используйте `pytest --pdb` для интерактивной отладки ### Медленные тесты Если тесты выполняются медленно: 1. Идентифицируйте медленные тесты с помощью `pytest --durations=10` 2. Используйте моки вместо реальных ресурсов, когда это возможно 3. Оптимизируйте фикстуры для уменьшения времени настройки 4. Используйте `pytest-xdist` для параллельного выполнения тестов ### Нестабильные тесты (флаки) Если тесты нестабильны (случайно не проходят): 1. Идентифицируйте нестабильные тесты 2. Проверьте внешние зависимости (базы данных, сеть и т.д.) 3. Убедитесь, что тесты изолированы 4. Используйте `pytest-rerunfailures` для повторного запуска неудачных тестов ## Заключение Тестирование необходимо для обеспечения качества и надежности MCP Database Utilities. Следуя лучшим практикам и используя соответствующие инструменты, мы можем поддерживать всеобъемлющий и эффективный набор тестов, который дает нам уверенность в нашем коде.

MCP directory API

We provide all the information about MCP servers via our MCP API.

curl -X GET 'https://glama.ai/api/mcp/v1/servers/donghao1393/mcp-dbutils'

If you have feedback or need assistance with the MCP directory API, please join our Discord server