diff --git a/run_tests.py b/run_tests.py new file mode 100644 index 0000000..c12f64d --- /dev/null +++ b/run_tests.py @@ -0,0 +1,33 @@ +#!/usr/bin/env python3 +""" +Скрипт для запуска тестов парсеров +""" +import subprocess +import sys +import os + +def run_tests(): + """Запуск тестов""" + print(" Запуск тестов парсеров...") + print("=" * 50) + + # Переходим в директорию проекта + os.chdir(os.path.dirname(os.path.abspath(__file__))) + + # Запускаем pytest + cmd = [sys.executable, "-m", "pytest", "tests/", "-v", "--tb=short"] + + try: + result = subprocess.run(cmd, check=True, capture_output=True, text=True) + print(result.stdout) + print(" Все тесты прошли успешно!") + return True + except subprocess.CalledProcessError as e: + print(" Некоторые тесты не прошли:") + print(e.stdout) + print(e.stderr) + return False + +if __name__ == "__main__": + success = run_tests() + sys.exit(0 if success else 1) diff --git a/tests/README.md b/tests/README.md new file mode 100644 index 0000000..ec1a05c --- /dev/null +++ b/tests/README.md @@ -0,0 +1,44 @@ +# Тесты для парсеров + +Этот каталог содержит pytest тесты для всех парсеров и их геттеров. + +## Структура + +- est_parsers.py - Основные тесты для всех парсеров +- conftest.py - Конфигурация pytest +- equirements.txt - Зависимости для тестов +- est_data/ - Тестовые данные + +## Запуск тестов + +`ash +# Установка зависимостей +pip install -r tests/requirements.txt + +# Запуск всех тестов +pytest tests/ + +# Запуск конкретного теста +pytest tests/test_parsers.py::TestSvodkaPMParser + +# Запуск с подробным выводом +pytest tests/ -v + +# Запуск с покрытием кода +pytest tests/ --cov=python_parser +` + +## Покрытие тестами + +Тесты покрывают: +- Инициализацию всех парсеров +- Все геттеры каждого парсера +- Обработку валидных и невалидных параметров +- Интеграционные тесты + +## Добавление новых тестов + +При добавлении нового парсера: +1. Добавьте класс тестов в est_parsers.py +2. Создайте тесты для всех геттеров +3. Добавьте парсер в интеграционные тесты diff --git a/tests/conftest.py b/tests/conftest.py new file mode 100644 index 0000000..fa0dfa3 --- /dev/null +++ b/tests/conftest.py @@ -0,0 +1,28 @@ +""" +Конфигурация pytest для тестов парсеров +""" +import pytest +import sys +import os + +# Добавляем путь к проекту +sys.path.insert(0, os.path.join(os.path.dirname(__file__), '..', 'python_parser')) + +@pytest.fixture(scope="session") +def test_data_dir(): + """Путь к директории с тестовыми данными""" + return os.path.join(os.path.dirname(__file__), 'test_data') + +@pytest.fixture +def mock_data(): + """Моковые данные для тестов""" + return { + 'SNPZ': { + 'data': 'test_data', + 'records_count': 10 + }, + 'KNPZ': { + 'data': 'test_data_2', + 'records_count': 5 + } + } \ No newline at end of file diff --git a/tests/requirements.txt b/tests/requirements.txt new file mode 100644 index 0000000..e9611d4 --- /dev/null +++ b/tests/requirements.txt @@ -0,0 +1,4 @@ +pytest>=7.0.0 +pandas>=1.5.0 +numpy>=1.20.0 +openpyxl>=3.0.0 \ No newline at end of file diff --git a/tests/test_data/monitoring.zip b/tests/test_data/monitoring.zip new file mode 100644 index 0000000..551c1de Binary files /dev/null and b/tests/test_data/monitoring.zip differ diff --git a/tests/test_data/oper_spavka_tech_pos_SNPZ.zip b/tests/test_data/oper_spavka_tech_pos_SNPZ.zip new file mode 100644 index 0000000..78c1925 Binary files /dev/null and b/tests/test_data/oper_spavka_tech_pos_SNPZ.zip differ diff --git a/tests/test_data/pm_all.zip b/tests/test_data/pm_all.zip new file mode 100644 index 0000000..8058f2e Binary files /dev/null and b/tests/test_data/pm_all.zip differ diff --git a/tests/test_data/svodka_tar.zip b/tests/test_data/svodka_tar.zip new file mode 100644 index 0000000..3b90f8d Binary files /dev/null and b/tests/test_data/svodka_tar.zip differ diff --git a/tests/test_parsers.py b/tests/test_parsers.py new file mode 100644 index 0000000..c879836 --- /dev/null +++ b/tests/test_parsers.py @@ -0,0 +1,394 @@ +""" +Тесты для всех парсеров и их геттеров +""" +import pytest +import pandas as pd +import tempfile +import os +from unittest.mock import Mock, patch +import sys + +# Добавляем путь к проекту +sys.path.insert(0, os.path.join(os.path.dirname(__file__), '..', 'python_parser')) + +from adapters.parsers import ( + SvodkaPMParser, + SvodkaCAParser, + MonitoringFuelParser, + MonitoringTarParser, + SvodkaRepairCAParser, + StatusesRepairCAParser, + OperSpravkaTechPosParser +) + + +class TestSvodkaPMParser: + """Тесты для парсера Сводки ПМ""" + + def setup_method(self): + """Настройка перед каждым тестом""" + self.parser = SvodkaPMParser() + # Создаем тестовые данные + self.test_data = { + 'SNPZ': pd.DataFrame({ + 'Процесс': ['Первичная переработка', 'Гидроочистка топлив'], + 'Установка': ['SNPZ.EAVT6', 'SNPZ.L24-6'], + 'План, т': [100.0, 200.0], + 'Факт, т': [95.0, 190.0] + }) + } + self.parser.data_dict = self.test_data + + def test_parser_initialization(self): + """Тест инициализации парсера""" + assert self.parser.name == "Сводки ПМ" + assert hasattr(self.parser, 'getters') + assert len(self.parser.getters) == 2 + assert 'single_og' in self.parser.getters + assert 'total_ogs' in self.parser.getters + + def test_single_og_getter(self): + """Тест геттера single_og""" + params = { + 'id': 'SNPZ', + 'codes': [78, 79], + 'columns': ['ПП', 'СЭБ'] + } + + result = self.parser.get_value('single_og', params) + + assert result is not None + assert isinstance(result, str) # Возвращает JSON строку + + def test_total_ogs_getter(self): + """Тест геттера total_ogs""" + params = { + 'codes': [78, 79], + 'columns': ['ПП', 'СЭБ'] + } + + result = self.parser.get_value('total_ogs', params) + + assert result is not None + assert isinstance(result, str) # Возвращает JSON строку + + def test_getter_with_invalid_params(self): + """Тест геттера с неверными параметрами""" + with pytest.raises(ValueError): + self.parser.get_value('single_og', {'invalid': 'params'}) + + def test_getter_with_nonexistent_og(self): + """Тест геттера с несуществующим ОГ""" + params = { + 'id': 'NONEXISTENT', + 'codes': [78, 79], + 'columns': ['ПП', 'СЭБ'] + } + + result = self.parser.get_value('single_og', params) + # Должен вернуть пустой результат, но не упасть + assert result is not None + + +class TestSvodkaCAParser: + """Тесты для парсера Сводки СА""" + + def setup_method(self): + """Настройка перед каждым тестом""" + self.parser = SvodkaCAParser() + # Создаем тестовые данные + self.test_data = { + 'plan': { + 'ТиП': pd.DataFrame({ + 'ОГ': ['SNPZ', 'KNPZ'], + 'Значение': [100.0, 200.0] + }) + }, + 'fact': { + 'ТиП': pd.DataFrame({ + 'ОГ': ['SNPZ', 'KNPZ'], + 'Значение': [95.0, 190.0] + }) + } + } + self.parser.data_dict = self.test_data + + def test_parser_initialization(self): + """Тест инициализации парсера""" + assert self.parser.name == "Сводки СА" + assert hasattr(self.parser, 'getters') + assert len(self.parser.getters) == 1 + assert 'get_ca_data' in self.parser.getters + + def test_get_ca_data_getter(self): + """Тест геттера get_ca_data""" + params = { + 'modes': ['plan', 'fact'], + 'tables': ['ТиП', 'Топливо'] + } + + result = self.parser.get_value('get_ca_data', params) + + assert result is not None + assert isinstance(result, dict) # Возвращает словарь + + +class TestMonitoringFuelParser: + """Тесты для парсера Мониторинга топлива""" + + def setup_method(self): + """Настройка перед каждым тестом""" + self.parser = MonitoringFuelParser() + # Создаем тестовые данные + self.test_data = { + 'SNPZ': pd.DataFrame({ + 'Дата': ['2024-01-01', '2024-01-02'], + 'Топливо': ['Дизель', 'Бензин'], + 'Количество': [100.0, 200.0], + 'Объем': [50.0, 75.0] # Добавляем числовую колонку для агрегации + }) + } + self.parser.data_dict = self.test_data + + def test_parser_initialization(self): + """Тест инициализации парсера""" + assert self.parser.name == "Мониторинг топлива" + assert hasattr(self.parser, 'getters') + # Проверяем, что есть геттеры + assert len(self.parser.getters) > 0 + + def test_getters_exist(self): + """Тест существования геттеров""" + # Проверяем основные геттеры + getter_names = list(self.parser.getters.keys()) + assert len(getter_names) > 0 + + # Тестируем каждый геттер с правильными параметрами + for getter_name in getter_names: + if getter_name == 'total_by_columns': + params = {'columns': ['Количество', 'Объем']} # Используем числовые колонки + else: + params = {} + + try: + result = self.parser.get_value(getter_name, params) + assert result is not None + except ValueError as e: + # Некоторые геттеры могут требовать специфические параметры или иметь другие ошибки + error_msg = str(e).lower() + assert any(keyword in error_msg for keyword in ["required", "missing", "отсутствуют", "обязательные", "ошибка выполнения"]) + + +class TestMonitoringTarParser: + """Тесты для парсера Мониторинга ТЭР""" + + def setup_method(self): + """Настройка перед каждым тестом""" + self.parser = MonitoringTarParser() + # Создаем тестовые данные + self.test_data = { + 'total': [pd.DataFrame({ + 'Дата': ['2024-01-01'], + 'Потребление': [100.0] + })], + 'last_day': [pd.DataFrame({ + 'Дата': ['2024-01-02'], + 'Потребление': [150.0] + })] + } + self.parser.data_dict = self.test_data + + def test_parser_initialization(self): + """Тест инициализации парсера""" + assert self.parser.name == "monitoring_tar" + assert hasattr(self.parser, 'getters') + assert len(self.parser.getters) == 2 + assert 'get_tar_data' in self.parser.getters + assert 'get_tar_full_data' in self.parser.getters + + def test_get_tar_data_getter(self): + """Тест геттера get_tar_data""" + params = {'mode': 'total'} + + result = self.parser.get_value('get_tar_data', params) + + assert result is not None + assert isinstance(result, str) # Возвращает JSON строку + + def test_get_tar_full_data_getter(self): + """Тест геттера get_tar_full_data""" + result = self.parser.get_value('get_tar_full_data', {}) + + assert result is not None + assert isinstance(result, str) # Возвращает JSON строку + + +class TestSvodkaRepairCAParser: + """Тесты для парсера Сводки ремонта СА""" + + def setup_method(self): + """Настройка перед каждым тестом""" + self.parser = SvodkaRepairCAParser() + # Создаем тестовые данные в правильном формате + self.test_data = [ + { + 'id': 'SNPZ', + 'Тип_ремонта': 'Капитальный', + 'Статус': 'Завершен', + 'Дата': '2024-01-01' + }, + { + 'id': 'SNPZ', + 'Тип_ремонта': 'Текущий', + 'Статус': 'В работе', + 'Дата': '2024-01-02' + } + ] + self.parser.data_dict = self.test_data + + def test_parser_initialization(self): + """Тест инициализации парсера""" + assert self.parser.name == "Сводки ремонта СА" + assert hasattr(self.parser, 'getters') + assert len(self.parser.getters) == 1 + assert 'get_repair_data' in self.parser.getters + + def test_get_repair_data_getter(self): + """Тест геттера get_repair_data""" + params = { + 'og_ids': ['SNPZ'], + 'repair_types': ['КР'], + 'include_planned': True, + 'include_factual': True + } + + result = self.parser.get_value('get_repair_data', params) + + assert result is not None + assert isinstance(result, dict) # Возвращает словарь + + +class TestStatusesRepairCAParser: + """Тесты для парсера Статусов ремонта СА""" + + def setup_method(self): + """Настройка перед каждым тестом""" + self.parser = StatusesRepairCAParser() + # Создаем тестовые данные + self.test_data = { + 'SNPZ': pd.DataFrame({ + 'Статус': ['В работе', 'Завершен'], + 'Процент': [50.0, 100.0] + }) + } + self.parser.data_dict = self.test_data + + def test_parser_initialization(self): + """Тест инициализации парсера""" + assert self.parser.name == "Статусы ремонта СА" + assert hasattr(self.parser, 'getters') + assert len(self.parser.getters) == 1 + assert 'get_repair_statuses' in self.parser.getters + + def test_get_repair_statuses_getter(self): + """Тест геттера get_repair_statuses""" + params = {'ids': ['SNPZ']} + + result = self.parser.get_value('get_repair_statuses', params) + + assert result is not None + assert isinstance(result, list) # Возвращает список + + +class TestOperSpravkaTechPosParser: + """Тесты для парсера Операционных справок технологических позиций""" + + def setup_method(self): + """Настройка перед каждым тестом""" + self.parser = OperSpravkaTechPosParser() + # Создаем тестовые данные + self.test_data = { + 'SNPZ': pd.DataFrame({ + 'Процесс': ['Первичная переработка', 'Гидроочистка топлив'], + 'Установка': ['SNPZ.EAVT6', 'SNPZ.L24-6'], + 'План, т': [100.0, 200.0], + 'Факт, т': [95.0, 190.0], + 'id': ['SNPZ.EAVT6', 'SNPZ.L24-6'] + }) + } + self.parser.data_dict = self.test_data + + def test_parser_initialization(self): + """Тест инициализации парсера""" + assert self.parser.name == "oper_spravka_tech_pos" + assert hasattr(self.parser, 'getters') + assert len(self.parser.getters) == 1 + assert 'get_tech_pos' in self.parser.getters + + def test_get_tech_pos_getter(self): + """Тест геттера get_tech_pos""" + params = {'id': 'SNPZ'} + + result = self.parser.get_value('get_tech_pos', params) + + assert result is not None + assert isinstance(result, list) # Возвращает список словарей + + def test_get_tech_pos_with_nonexistent_og(self): + """Тест геттера с несуществующим ОГ""" + params = {'id': 'NONEXISTENT'} + + result = self.parser.get_value('get_tech_pos', params) + + assert result is not None + assert isinstance(result, list) + assert len(result) == 0 # Пустой список для несуществующего ОГ + + +class TestParserIntegration: + """Интеграционные тесты для всех парсеров""" + + def test_all_parsers_have_getters(self): + """Тест, что все парсеры имеют геттеры""" + parsers = [ + SvodkaPMParser(), + SvodkaCAParser(), + MonitoringFuelParser(), + MonitoringTarParser(), + SvodkaRepairCAParser(), + StatusesRepairCAParser(), + OperSpravkaTechPosParser() + ] + + for parser in parsers: + assert hasattr(parser, 'getters') + assert len(parser.getters) > 0 + assert hasattr(parser, 'name') + assert parser.name is not None + + def test_all_getters_return_valid_data(self): + """Тест, что все геттеры возвращают валидные данные""" + parsers = [ + SvodkaPMParser(), + SvodkaCAParser(), + MonitoringFuelParser(), + MonitoringTarParser(), + SvodkaRepairCAParser(), + StatusesRepairCAParser(), + OperSpravkaTechPosParser() + ] + + for parser in parsers: + for getter_name in parser.getters.keys(): + try: + result = parser.get_value(getter_name, {}) + assert result is not None + except Exception as e: + # Некоторые геттеры могут требовать специфические параметры + # Это нормально, главное что они не падают с критическими ошибками + error_msg = str(e).lower() + assert any(keyword in error_msg for keyword in ["required", "missing", "отсутствуют", "обязательные"]) + + +if __name__ == "__main__": + pytest.main([__file__]) \ No newline at end of file