#!/usr/bin/env python """ Простой скрипт для запуска тестов, обходящий проблемы с pytest и pdbpp Использует стандартный Django test runner с улучшенными возможностями Поддерживает coverage и дополнительные опции """ import os import sys from io import StringIO import argparse import django def setup_django(): """Настройка Django окружения""" # Монкипатчим проблематичные модули sys.modules["ipdb"] = type("MockModule", (), {"__getattr__": lambda s, n: None})() # Добавляем src в PYTHONPATH src_path = os.path.join(os.path.dirname(__file__), "src") if src_path not in sys.path: sys.path.insert(0, src_path) # Устанавливаем настройки Django (принудительно для тестов) os.environ["DJANGO_SETTINGS_MODULE"] = "config.settings.test" # Инициализируем Django django.setup() def run_tests_with_args(test_args, options): """Запуск тестов с заданными аргументами""" from django.conf import settings from django.test.utils import get_runner # Получаем test runner TestRunner = get_runner(settings) # Настройки для test runner runner_kwargs = { "verbosity": options.verbose, "interactive": False, "keepdb": options.keepdb, "failfast": options.failfast, } # Добавляем parallel если указано if options.parallel: runner_kwargs["parallel"] = options.parallel test_runner = TestRunner(**runner_kwargs) # Запускаем тесты failures = test_runner.run_tests(test_args) return failures def parse_arguments(): """Парсинг аргументов командной строки""" parser = argparse.ArgumentParser(description="Запуск Django тестов с дополнительными возможностями") parser.add_argument( "targets", nargs="*", help="Цели тестирования (по умолчанию: все тесты)", default=["tests"] ) parser.add_argument( "--coverage", "--cov", action="store_true", help="Запуск тестов с измерением покрытия кода" ) parser.add_argument( "--fast", action="store_true", help="Запуск только быстрых тестов (исключает медленные)" ) parser.add_argument( "--failfast", action="store_true", help="Остановка при первой ошибке" ) parser.add_argument( "--verbose", "-v", action="count", default=2, help="Уровень детализации вывода" ) parser.add_argument( "--keepdb", action="store_true", help="Сохранить тестовую базу данных" ) parser.add_argument( "--parallel", type=int, metavar="N", help="Запуск тестов в N параллельных процессах" ) args = parser.parse_args() # Преобразуем пути для удобства использования test_targets = [] for target in args.targets: # Преобразование путей файлов в модули Django if target.endswith(".py"): # Убираем расширение .py target = target[:-3] # Заменяем слеши на точки для модульных путей if "/" in target: target = target.replace("/", ".") # Добавляем префикс tests если его нет if not target.startswith("tests"): if target == "user": # Если просто "user", запускаем все тесты user app target = "tests.apps.user" elif target in ["models", "views", "serializers", "services"]: # Если это простые ключевые слова, добавляем test_ префикс target = f"tests.apps.user.test_{target}" elif ( "test_" in target or "models" in target or "views" in target or "serializers" in target or "services" in target ): # Если это конкретный файл тестов с префиксом или содержит ключевые слова if not target.startswith("test_"): target = f"tests.apps.user.test_{target}" else: target = f"tests.apps.user.{target}" else: # Общий случай target = f"tests.{target}" test_targets.append(target) args.targets = test_targets if test_targets else ["tests"] return args def print_test_info(test_targets, options): """Вывод информации о запуске тестов""" print("🧪 Запуск тестов (Django test runner)...") if test_targets == ["tests"]: print("📁 Цель: Все тесты в проекте") else: print(f"📁 Цели: {', '.join(test_targets)}") print(f"⚙️ Настройки Django: {os.environ.get('DJANGO_SETTINGS_MODULE')}") print(f"📦 Путь к исходникам: {os.path.join(os.path.dirname(__file__), 'src')}") # Дополнительные опции options_info = [] if options.coverage: options_info.append("📊 Измерение покрытия") if options.fast: options_info.append("🚀 Только быстрые тесты") if options.failfast: options_info.append("❌ Остановка при первой ошибке") if options.keepdb: options_info.append("💾 Сохранение тестовой БД") if options.parallel: options_info.append(f"⚡ Параллельность: {options.parallel}") if options_info: print("🔧 Опции:", " | ".join(options_info)) print("-" * 60) def setup_coverage(): """Настройка coverage""" try: import coverage cov = coverage.Coverage(config_file="pyproject.toml") cov.start() return cov except ImportError: print("⚠️ Модуль coverage не установлен. Измерение покрытия недоступно.") return None def finalize_coverage(cov): """Завершение измерения покрытия""" if cov: cov.stop() cov.save() print("\n📊 Отчет о покрытии кода:") print("-" * 40) cov.report() # Создание HTML отчета try: cov.html_report() print("\n📄 HTML отчет создан в директории: htmlcov/") except Exception as e: print(f"⚠️ Не удалось создать HTML отчет: {e}") def main(): """Основная функция""" cov = None try: # Парсинг аргументов options = parse_arguments() # Настройка coverage если нужно if options.coverage: cov = setup_coverage() # Настройка Django setup_django() # Настройка фильтрации тестов if options.fast: os.environ["PYTEST_CURRENT_TEST_FILTER"] = "not slow" # Вывод информации print_test_info(options.targets, options) # Запуск тестов failures = run_tests_with_args(options.targets, options) # Завершение coverage if cov: finalize_coverage(cov) # Результат if failures: print(f"\n❌ Тесты завершились с ошибками: {failures} неудачных тестов") sys.exit(1) else: print(f"\n✅ Все тесты прошли успешно!") if cov: print("📊 Отчет о покрытии сохранен") sys.exit(0) except KeyboardInterrupt: print("\n❌ Тесты прерваны пользователем") if cov: cov.stop() sys.exit(1) except Exception as e: print(f"\n❌ Ошибка при запуске тестов: {e}") if cov: cov.stop() import traceback traceback.print_exc() sys.exit(1) if __name__ == "__main__": main()