/
marsela13
/
practic
Обзор
Документация
Войти
/
marsela13
/
practic
Код
Запросы
0
Задачи
Вики
Пакеты
0
Релизы
0
CI/CD
Аналитика
Безопасность
master
tests/test_integration_gui.py
778 строк
30 KB
marsela13
Upload files to "tests"
25 дек 2025, 15:34
25 дек 2025, 15:34
00b2246
Код
Авторство
О чём код?
""" Интеграционные тесты графического интерфейса. Тестирует взаимодействие GUI компонентов и пользовательские сценарии. """ import sys import os import tempfile from pathlib import Path from unittest.mock import Mock, patch, MagicMock, call import pytest # PyQt5 может требять GUI среды, пропускаем тесты если её нет try: from PyQt5.QtWidgets import QApplication, QMessageBox from PyQt5.QtCore import Qt, QTimer from PyQt5.QtTest import QTest QT_AVAILABLE = True except ImportError: QT_AVAILABLE = False pytest.skip("PyQt5 не доступен", allow_module_level=True) # Добавление пути к проекту sys.path.insert(0, os.path.dirname(os.path.dirname(os.path.abspath(__file__)))) from scanner_gui import NetworkScannerGUI, ScanThread from network_scanner import NetworkScanner @pytest.fixture(scope="module") def qapp(): """Фикстура для создания QApplication.""" app = QApplication.instance() if app is None: app = QApplication([]) return app class TestGUIInitialization: """Тесты инициализации GUI.""" def test_gui_window_properties(self, qapp): """Тест свойств главного окна.""" gui = NetworkScannerGUI() assert gui.windowTitle() == 'Network Scanner GUI v1.0' assert gui.geometry().width() == 1200 assert gui.geometry().height() == 800 # Проверяем наличие основных виджетов assert hasattr(gui, 'results_table') assert hasattr(gui, 'log_text') assert hasattr(gui, 'progress_bar') assert hasattr(gui, 'status_label') assert hasattr(gui, 'scan_button') assert hasattr(gui, 'stop_button') gui.close() def test_gui_default_settings(self, qapp): """Тест настроек по умолчанию в GUI.""" gui = NetworkScannerGUI() # Проверяем значения по умолчанию assert gui.ip_input.text() == '192.168.1.1-100' assert gui.ports_input.text() == '22,80,443,3389' assert gui.common_ports_check.isChecked() is True assert gui.threads_spin.value() == 50 assert gui.timeout_spin.value() == 1 gui.close() def test_gui_menu_bar(self, qapp): """Тест меню главного окна.""" gui = NetworkScannerGUI() menubar = gui.menuBar() assert menubar is not None # Проверяем наличие меню menus = [menubar.actions()[i].text() for i in range(menubar.actions().count())] assert 'Файл' in menus assert 'Настройки' in menus assert 'Помощь' in menus gui.close() def test_gui_status_bar(self, qapp): """Тест статусной строки.""" gui = NetworkScannerGUI() # Проверяем начальный статус assert gui.status_label.text() == 'Готов к сканированию' # Проверяем обновление статуса gui.status_label.setText('Тестирование...') assert gui.status_label.text() == 'Тестирование...' gui.close() class TestGUIUserInteractions: """Тесты пользовательских взаимодействий с GUI.""" def test_ip_input_validation(self, qapp): """Тест валидации ввода IP диапазона.""" gui = NetworkScannerGUI() # Тестируем корректные значения test_cases = [ ('192.168.1.1', True), ('192.168.1.1-100', True), ('192.168.1.1-192.168.1.100', True), ('192.168.1.0/24', True), ('', False), # Пустая строка ('invalid', False), # Некорректный формат ] for ip_range, should_be_valid in test_cases: gui.ip_input.setText(ip_range) # В реальном приложении валидация происходит при нажатии кнопки # Здесь мы просто проверяем, что текст устанавливается assert gui.ip_input.text() == ip_range gui.close() def test_ports_input_validation(self, qapp): """Тест валидации ввода портов.""" gui = NetworkScannerGUI() # Тестируем корректные значения test_cases = [ ('22,80,443', True), ('22', True), ('22, 80, 443', True), # С пробелами ('22,abc,443', False), # Некорректное значение ('70000', False), # Пор вне диапазона ('', True), # Пустая строка (допустимо) ] for ports, should_be_valid in test_cases: gui.ports_input.setText(ports) assert gui.ports_input.text() == ports gui.close() def test_threads_spin_limits(self, qapp): """Тест ограничений спинбокса потоков.""" gui = NetworkScannerGUI() # Проверяем минимальное значение gui.threads_spin.setValue(1) assert gui.threads_spin.value() == 1 # Проверяем максимальное значение gui.threads_spin.setValue(500) assert gui.threads_spin.value() == 500 # Проверяем, что нельзя установить значение вне диапазона gui.threads_spin.setValue(0) # Должно стать 1 assert gui.threads_spin.value() == 1 gui.threads_spin.setValue(501) # Должно стать 500 assert gui.threads_spin.value() == 500 gui.close() def test_timeout_spin_limits(self, qapp): """Тест ограничений спинбокса таймаута.""" gui = NetworkScannerGUI() # Проверяем минимальное значение gui.timeout_spin.setValue(1) assert gui.timeout_spin.value() == 1 # Проверяем максимальное значение gui.timeout_spin.setValue(30) assert gui.timeout_spin.value() == 30 # Проверяем, что нельзя установить значение вне диапазона gui.timeout_spin.setValue(0) # Должно стать 1 assert gui.timeout_spin.value() == 1 gui.timeout_spin.setValue(31) # Должно стать 30 assert gui.timeout_spin.value() == 30 gui.close() def test_common_ports_checkbox(self, qapp): """Тест чекбокса общих портов.""" gui = NetworkScannerGUI() # Проверяем начальное состояние assert gui.common_ports_check.isChecked() is True # Отключаем gui.common_ports_check.setChecked(False) assert gui.common_ports_check.isChecked() is False # Включаем обратно gui.common_ports_check.setChecked(True) assert gui.common_ports_check.isChecked() is True gui.close() class TestGUIButtonFunctionality: """Тесты функциональности кнопок GUI.""" @patch.object(NetworkScannerGUI, 'start_scan') def test_scan_button_click(self, mock_start_scan, qapp): """Тест нажатия кнопки сканирования.""" gui = NetworkScannerGUI() # Симулируем нажатие кнопки gui.scan_button.click() # Проверяем вызов метода mock_start_scan.assert_called_once() gui.close() @patch.object(NetworkScannerGUI, 'stop_scan') def test_stop_button_click(self, mock_stop_scan, qapp): """Тест нажатия кнопки остановки.""" gui = NetworkScannerGUI() # Включаем кнопку (обычно она включается при старте сканирования) gui.stop_button.setEnabled(True) # Симулируем нажатие gui.stop_button.click() # Проверяем вызов метода mock_stop_scan.assert_called_once() gui.close() @patch.object(NetworkScannerGUI, 'export_results') def test_export_button_click(self, mock_export, qapp): """Тест нажатия кнопки экспорта.""" gui = NetworkScannerGUI() # Включаем кнопку (обычно она включается при наличии результатов) gui.export_button.setEnabled(True) # Симулируем нажатие gui.export_button.click() # Проверяем вызов метода mock_export.assert_called_once() gui.close() @patch.object(NetworkScannerGUI, 'clear_results') def test_clear_button_click(self, mock_clear, qapp): """Тест нажатия кнопки очистки.""" gui = NetworkScannerGUI() # Симулируем нажатие gui.clear_button.click() # Проверяем вызов метода mock_clear.assert_called_once() gui.close() class TestGUIScanFunctionality: """Тесты функциональности сканирования в GUI.""" def test_start_scan_validation_empty_ip(self, qapp): """Тест валидации при пустом IP диапазоне.""" gui = NetworkScannerGUI() # Устанавливаем пустой IP диапазон gui.ip_input.setText('') # Мокаем QMessageBox для проверки вызова with patch('PyQt5.QtWidgets.QMessageBox.warning') as mock_warning: gui.start_scan() # Проверяем, что было показано предупреждение mock_warning.assert_called_once() args, kwargs = mock_warning.call_args assert args[1] == 'Ошибка' # Заголовок assert 'Введите IP диапазон' in args[2] # Текст gui.close() def test_start_scan_validation_invalid_ports(self, qapp): """Тест валидации при некорректных портах.""" gui = NetworkScannerGUI() # Устанавливаем корректный IP, но некорректные порты gui.ip_input.setText('192.168.1.1-10') gui.ports_input.setText('22,abc,443') # 'abc' - некорректный порт with patch('PyQt5.QtWidgets.QMessageBox.warning') as mock_warning: gui.start_scan() # Проверяем предупреждение mock_warning.assert_called_once() args, kwargs = mock_warning.call_args assert 'Некорректный формат портов' in args[2] gui.close() @patch('scanner_gui.ScanThread') def test_start_scan_successful(self, mock_scan_thread_class, qapp): """Тест успешного запуска сканирования.""" gui = NetworkScannerGUI() # Устанавливаем корректные параметры gui.ip_input.setText('192.168.1.1-10') gui.ports_input.setText('22,80,443') gui.common_ports_check.setChecked(True) gui.threads_spin.setValue(50) gui.timeout_spin.setValue(1) # Мокаем ScanThread mock_scan_thread = Mock() mock_scan_thread_class.return_value = mock_scan_thread # Вызываем start_scan gui.start_scan() # Проверяем создание потока mock_scan_thread_class.assert_called_once_with( gui.scanner, '192.168.1.1-10', [21, 22, 23, 25, 53, 80, 110, 111, 135, 139, 143, 443, 445, 993, 995, 1723, 3306, 3389, 5900, 8080, 22, 80, 443] ) # Проверяем подключение сигналов assert mock_scan_thread.scan_progress.connect.called assert mock_scan_thread.scan_finished.connect.called assert mock_scan_thread.scan_error.connect.called # Проверяем запуск потока mock_scan_thread.start.assert_called_once() # Проверяем состояние кнопок assert gui.scan_button.isEnabled() is False assert gui.stop_button.isEnabled() is True assert gui.export_button.isEnabled() is False # Проверяем очистку таблицы assert gui.results_table.rowCount() == 0 # Проверяем сброс прогресс-бара assert gui.progress_bar.value() == 0 gui.close() def test_stop_scan_functionality(self, qapp): """Тест функциональности остановки сканирования.""" gui = NetworkScannerGUI() # Создаем мок потока mock_scan_thread = Mock() mock_scan_thread.isRunning.return_value = True gui.scan_thread = mock_scan_thread # Вызываем stop_scan gui.stop_scan() # Проверяем вызовы gui.scanner.cancel_scan.assert_called_once() mock_scan_thread.quit.assert_called_once() mock_scan_thread.wait.assert_called_once() # Проверяем состояние кнопок assert gui.scan_button.isEnabled() is True assert gui.stop_button.isEnabled() is False gui.close() def test_stop_scan_no_thread(self, qapp): """Тест остановки сканирования без активного потока.""" gui = NetworkScannerGUI() # Нет активного потока gui.scan_thread = None # Должно завершиться без ошибок gui.stop_scan() gui.close() class TestGUIResultsDisplay: """Тесты отображения результатов в GUI.""" def test_update_progress_display(self, qapp): """Тест обновления отображения прогресса.""" gui = NetworkScannerGUI() # Тестовые данные current = 25 total = 100 found = 10 result = { 'ip': '192.168.1.1', 'hostname': 'test.local', 'status': 'Online', 'open_ports': [80, 443], 'timestamp': '2024-01-15T10:00:00.000000' } # Вызываем update_progress gui.update_progress(current, total, found, result) # Проверяем прогресс-бар expected_progress = int((current / total) * 100) assert gui.progress_bar.value() == expected_progress # Проверяем статусную строку expected_status = f"Сканирование... {current}/{total} ({found} найдено)" assert gui.status_label.text() == expected_status # Проверяем добавление в таблицу assert gui.results_table.rowCount() == 1 # Проверяем данные в таблице assert gui.results_table.item(0, 0).text() == '192.168.1.1' assert gui.results_table.item(0, 1).text() == 'test.local' assert gui.results_table.item(0, 2).text() == 'Online' assert gui.results_table.item(0, 3).text() == '80, 443' assert gui.results_table.item(0, 4).text() == '2024-01-15T10:00:00.000000' # Проверяем цвет статуса (должен быть зеленый для Online) status_item = gui.results_table.item(0, 2) assert status_item.foreground().color().name() == '#008000' # Зеленый gui.close() def test_update_progress_no_result(self, qapp): """Тест обновления прогресса без результата.""" gui = NetworkScannerGUI() # Вызываем с None result gui.update_progress(50, 100, 25, None) # Проверяем прогресс-бар assert gui.progress_bar.value() == 50 # Проверяем статус assert gui.status_label.text() == "Сканирование... 50/100 (25 найдено)" # Таблица не должна измениться assert gui.results_table.rowCount() == 0 gui.close() def test_update_progress_total_zero(self, qapp): """Тест обновления прогресса с total=0.""" gui = NetworkScannerGUI() # Вызываем с total=0 (деление на 0 должно быть обработано) gui.update_progress(0, 0, 0, None) # Прогресс-бар должен быть 0 assert gui.progress_bar.value() == 0 gui.close() def test_on_scan_finished(self, qapp): """Тест обработки завершения сканирования.""" gui = NetworkScannerGUI() # Тестовые результаты test_results = [ {'ip': '192.168.1.1', 'status': 'Online', 'open_ports': [80]}, {'ip': '192.168.1.2', 'status': 'Online', 'open_ports': []}, ] # Мокаем QMessageBox with patch('PyQt5.QtWidgets.QMessageBox.information') as mock_info: gui.on_scan_finished(test_results) # Проверяем состояние кнопок assert gui.scan_button.isEnabled() is True assert gui.stop_button.isEnabled() is False assert gui.export_button.isEnabled() is True # Есть результаты # Проверяем прогресс-бар assert gui.progress_bar.value() == 100 # Проверяем информационное сообщение mock_info.assert_called_once() args, kwargs = mock_info.call_args assert 'Сканирование завершено' in args[1] assert '2' in args[2] # Количество найденных хостов gui.close() def test_on_scan_finished_empty(self, qapp): """Тест обработки завершения сканирования без результатов.""" gui = NetworkScannerGUI() # Пустые результаты test_results = [] with patch('PyQt5.QtWidgets.QMessageBox.information') as mock_info: gui.on_scan_finished(test_results) # Кнопка экспорта должна быть отключена assert gui.export_button.isEnabled() is False mock_info.assert_called_once() gui.close() def test_on_scan_error(self, qapp): """Тест обработки ошибки сканирования.""" gui = NetworkScannerGUI() test_error = "Test error message" # Мокаем QMessageBox with patch('PyQt5.QtWidgets.QMessageBox.critical') as mock_critical: gui.on_scan_error(test_error) # Проверяем состояние кнопок assert gui.scan_button.isEnabled() is True assert gui.stop_button.isEnabled() is False # Проверяем вызов диалога ошибки mock_critical.assert_called_once() args, kwargs = mock_critical.call_args assert 'Ошибка' in args[1] assert test_error in args[2] gui.close() class TestGUIExportFunctionality: """Тесты функциональности экспорта в GUI.""" def test_export_results_no_data(self, qapp): """Тест экспорта без данных.""" gui = NetworkScannerGUI() # Нет результатов gui.scanner.results = [] with patch('PyQt5.QtWidgets.QMessageBox.warning') as mock_warning: gui.export_results() # Проверяем предупреждение mock_warning.assert_called_once() args, kwargs = mock_warning.call_args assert 'Нет данных для экспорта' in args[2] gui.close() @patch('PyQt5.QtWidgets.QFileDialog.getSaveFileName') def test_export_results_json(self, mock_dialog, qapp, temp_dir): """Тест экспорта в JSON формат.""" gui = NetworkScannerGUI() # Устанавливаем тестовые результаты gui.scanner.results = [ {'ip': '192.168.1.1', 'status': 'Online', 'open_ports': [80]} ] # Настраиваем диалог для JSON test_file = str(Path(temp_dir) / 'test_export.json') mock_dialog.return_value = (test_file, 'JSON files (*.json)') # Мокаем экспорт with patch.object(gui.scanner, 'export_to_json') as mock_export: mock_export.return_value = True # Мокаем QMessageBox with patch('PyQt5.QtWidgets.QMessageBox.information') as mock_info: gui.export_results() # Проверяем вызов экспорта mock_export.assert_called_once_with(test_file) # Проверяем информационное сообщение mock_info.assert_called_once() gui.close() @patch('PyQt5.QtWidgets.QFileDialog.getSaveFileName') def test_export_results_csv(self, mock_dialog, qapp, temp_dir): """Тест экспорта в CSV формат.""" gui = NetworkScannerGUI() gui.scanner.results = [ {'ip': '192.168.1.1', 'status': 'Online', 'open_ports': [80]} ] # Настраиваем диалог для CSV test_file = str(Path(temp_dir) / 'test_export.csv') mock_dialog.return_value = (test_file, 'CSV files (*.csv)') with patch.object(gui.scanner, 'export_to_csv') as mock_export: mock_export.return_value = True with patch('PyQt5.QtWidgets.QMessageBox.information') as mock_info: gui.export_results() mock_export.assert_called_once_with(test_file) mock_info.assert_called_once() gui.close() @patch('PyQt5.QtWidgets.QFileDialog.getSaveFileName') def test_export_results_cancelled(self, mock_dialog, qapp): """Тест отмены экспорта.""" gui = NetworkScannerGUI() gui.scanner.results = [{'ip': '192.168.1.1', 'status': 'Online'}] # Диалог возвращает пустую строку (отмена) mock_dialog.return_value = ('', '') # Экспорт не должен вызываться with patch.object(gui.scanner, 'export_to_json') as mock_export: gui.export_results() mock_export.assert_not_called() gui.close() @patch('PyQt5.QtWidgets.QFileDialog.getSaveFileName') def test_export_results_unknown_format(self, mock_dialog, qapp, temp_dir): """Тест экспорта в неизвестный формат.""" gui = NetworkScannerGUI() gui.scanner.results = [{'ip': '192.168.1.1', 'status': 'Online'}] # Неизвестное расширение test_file = str(Path(temp_dir) / 'test_export.unknown') mock_dialog.return_value = (test_file, 'All files (*.*)') with patch.object(gui.scanner, 'export_to_json') as mock_export: mock_export.return_value = True gui.export_results() # Должен добавить .json и вызвать JSON экспорт expected_file = test_file + '.json' mock_export.assert_called_once_with(expected_file) gui.close() class TestGUIContextMenu: """Тесты контекстного меню GUI.""" def test_context_menu_copy_ip(self, qapp): """Тест копирования IP из контекстного меню.""" gui = NetworkScannerGUI() # Добавляем тестовые данные в таблицу gui.results_table.setRowCount(1) gui.results_table.setItem(0, 0, gui.QTableWidgetItem('192.168.1.1')) gui.results_table.setItem(0, 1, gui.QTableWidgetItem('test.local')) # Выделяем строку gui.results_table.selectRow(0) # Мокаем QApplication.clipboard with patch('PyQt5.QtWidgets.QApplication.clipboard') as mock_clipboard: mock_clipboard_instance = Mock() mock_clipboard.return_value = mock_clipboard_instance # Вызываем копирование IP gui.copy_selected_ip() # Проверяем, что IP был скопирован mock_clipboard_instance.setText.assert_called_once_with('192.168.1.1') gui.close() def test_context_menu_copy_ip_no_selection(self, qapp): """Тест копирования IP без выделения.""" gui = NetworkScannerGUI() # Нет выделенных строк gui.results_table.clearSelection() # Должно завершиться без ошибок gui.copy_selected_ip() gui.close() @patch.object(NetworkScannerGUI, 'start_scan') def test_context_menu_scan_host(self, mock_start_scan, qapp): """Тест сканирования выбранного хоста из контекстного меню.""" gui = NetworkScannerGUI() # Добавляем тестовые данные gui.results_table.setRowCount(1) gui.results_table.setItem(0, 0, gui.QTableWidgetItem('192.168.1.1')) # Выделяем строку gui.results_table.selectRow(0) # Вызываем сканирование хоста gui.scan_selected_host() # Проверяем, что IP установлен в поле ввода assert gui.ip_input.text() == '192.168.1.1' # Проверяем запуск сканирования mock_start_scan.assert_called_once() gui.close() def test_context_menu_scan_host_no_selection(self, qapp): """Тест сканирования хоста без выделения.""" gui = NetworkScannerGUI() # Нет выделенных строк gui.results_table.clearSelection() # Должно завершиться без ошибок gui.scan_selected_host() # Поле ввода не должно измениться assert gui.ip_input.text() == '192.168.1.1-100' gui.close() class TestGUILogging: """Тесты логирования в GUI.""" def test_log_message_functionality(self, qapp): """Тест функциональности добавления сообщений в лог.""" gui = NetworkScannerGUI() # Начальный лог пустой assert gui.log_text.toPlainText() == '' # Добавляем сообщение test_message = "Test log message" gui.log_message(test_message) # Проверяем лог log_content = gui.log_text.toPlainText() assert test_message in log_content assert "[" in log_content # Должна быть временная метка # Добавляем еще одно сообщение gui.log_message("Second message") log_content = gui.log_text.toPlainText() assert "Second message" in log_content gui.close() def test_log_message_scroll(self, qapp): """Тест автопрокрутки лога.""" gui = NetworkScannerGUI() # Добавляем несколько сообщений for i in range(10): gui.log_message(f"Message {i}") # Проверяем, что скролл внизу scrollbar = gui.log_text.verticalScrollBar() # В тестовой среде точное значение может отличаться # Главное, что метод вызывается gui.close() if __name__ == '__main__': pytest.main([__file__, '-v'])