重构后键盘部分的测试方案规划
在博客系列的《6.5 串口实现的逻辑优化、配置管理与协议完善》完成后,项目左侧键盘部分(包括 UI 布局、按键交互、LCD 显示、指示灯反馈和串口集成)已趋于成熟,但测试一直是项目中的缺失环节。早期单文件版(KBD300A_main.py)仅依赖手动运行验证,重构后多文件结构虽提升了可测试性,但缺乏系统化测试会导致潜在 bug(如按键信号丢失或串口线程异常)在生产环境中暴露。测试不仅是验证功能正确性,还能提升代码质量、便于 CI/CD 和未来维护。
本规划聚焦重构后键盘部分的全面测试方案,覆盖单元测试(单个组件)、集成测试(模块间交互)、端到端测试(全链路)和手动测试(手感/UI)。基于 Python 3.7(Windows 7 兼容),使用 pytest 框架(pip install pytest==7.4.0,兼容旧系统)和 mock(unittest.mock)模拟依赖。目标覆盖率 80%+,测试目录为 tests/(新增到项目根)。这一方案可作为博客新增篇目(如6.7:重构后键盘测试方案与实践),承接6.6的键盘优化,过渡到7篇的宏系统。规划分类型展开,每类包含目标、工具、步骤、代码示例和注意事项。
1. 测试总体原则与准备
- 原则:TDD(测试驱动开发)优先(先写测试再优化代码);黑盒/白盒结合(功能 vs 内部逻辑);自动化为主,手动为辅。Win7 兼容:测试环境用 virtualenv,模拟旧硬件(e.g., COM 端口)。
- 工具 :
- pytest:单元/集成测试框架,支持 fixtures 参数化。
- unittest.mock:模拟信号/串口(e.g., mock SerialManager.write)。
- coverage.py(pip install coverage==6.5.0):测覆盖率(coverage run -m pytest;coverage report)。
- PyQt5 测试:QTest(模拟按键/鼠标),但 Win7 用 QApplication.testTimeOut 防超时。
- 准备 :
- 新建 tests/keyboard/:test_panel.py(KeyboardPanel)、test_integration.py等。
- requirements-dev.txt:pytest, coverage, mock。
- fixtures:pytest fixture 模拟 QApplication(@pytest.fixture def app(): app = QApplication([]); yield app; app.quit())。
- 运行:pytest tests/keyboard/ --cov=ui/keyboard --cov-report=html(生成报告)。
- 覆盖范围:键盘模块(panel.py/lcd.py/joystick.py/indicator_manager.py)和集成(main_window.py 的键盘部分)。
- 目标覆盖率:单元 90%、集成 70%、端到端 50%(手动补)。
2. 单元测试(单个组件隔离测试)
目标:验证独立功能(如按键回调、LCD 显示),不依赖外部(串口/信号)。快速运行,捕获边缘 case(如输入溢出)。
-
步骤:
- 测试 _on_digit:模拟数字输入,检查 input_buffer 和 lcd.display 调用。
- 测试 _set_mode:检查 mode 更新、lcd.display_text 调用和 mode_changed emit。
- 测试 LCD display_text:mock theme,检查 _mode_label.setText/setStyleSheet。
- 测试 IndicatorManager flash:用 mock.patch("PyQt5.QtCore.QTimer") 验证定时 off。
- 测试 Joystick mouseMoveEvent:模拟 pos,检查 pan_tilt_changed emit 值。
-
代码示例(test_panel.py,从最终代码推导;用 code_execution 验证语法无误):
python# tests/keyboard/test_panel.py # 目标:测试单个文件(如 panel.py 的 _on_digit),不跑 GUI/串口。快速反馈。 import pytest from unittest.mock import patch, MagicMock, call from ui.keyboard.panel import KeyboardPanel @pytest.fixture def keyboard(qapp): panel = KeyboardPanel() yield panel panel.close() def test_on_digit_limit(keyboard): # mock lcd.display with patch.object(keyboard.lcd, 'display') as mock_display: for i in range(5): # 输入5位 keyboard._on_digit(1) assert len(keyboard.input_buffer) == 4 # 限长4 mock_display.assert_has_calls([call(1), call(11), call(111), call(1111)], any_order=False) def test_set_mode(keyboard): keyboard.mode_changed = MagicMock() with patch.object(keyboard.lcd, 'display_text') as mock_display_text: keyboard._set_mode("PRESET") assert keyboard.mode == "PRESET" keyboard.mode_changed.emit.assert_called_with("PRESET") mock_display_text.assert_called_once_with("PRESET", is_mode=True, mode_key="MODE_PRESET") ...... # Win7 兼容:无特殊,pytest 运行正常 -
注意事项:Win7 用 --no-qt-logging 防 Qt 日志干扰。mock theme dict 模拟 dark/light。
-
执行 :pytest tests/keyboard/test_panel.py(预期 100% 通过,覆盖 on* 方法)。
3. 集成测试(模块间交互测试)
目标:验证键盘内部集成(如按键触发 LCD/灯)和与核心的桥接(信号 emit 到串口)。模拟依赖(如 mock SerialManager)。
-
步骤:
- 测试 _on_enter:mock preset_requested.emit,检查 try-except(异常时 ERR on)。
- 测试模式切换集成:_set_mode 后,检查 lcd.display_text 和 indicator_manager(如果模式灯扩展)。
- 测试指示灯与日志:mock _on_log_entry,检查 flash("TX") 调用 _style_on 和 QTimer。
- 测试摇杆与串口:mock joystick_moved.connect,模拟 moveEvent 检查 ptz_control 调用。
- 测试配置加载:mock json.load,检查 SerialManager 初始化参数。
-
代码示例(test_integration.py):
python# tests/keyboard/test_integration.py # 目标:测试键盘与核心/mock 串口的交互(如按键 emit → 串口 write)。 import pytest from unittest.mock import patch, MagicMock from ui.main_window import AppWindow @pytest.fixture def full_window(qapp): # 禁用 QSS 加载,避免测试环境崩溃 with patch.object(AppWindow, "_apply_qss", lambda self, theme: None): win = AppWindow() win.show() qapp.processEvents() # mock serial_mgr._thread.isRunning win.serial_mgr._thread = MagicMock() win.serial_mgr._thread.isRunning.return_value = True yield win win.close() def test_joystick_ptz_control(full_window): # patch _safe_call,让它直接调用目标函数 with patch.object(full_window, "_safe_call", lambda func, *a, **kw: func(*a, **kw)), \ patch("ui.main_window.ptz_control") as mock_ptz, \ patch("ui.main_window.ptz_stop") as mock_stop: full_window._on_joystick_moved(10, 20) mock_ptz.assert_called_once_with(full_window.serial_mgr, cam_id=1, pan_speed=10, tilt_speed=-20) full_window._on_joystick_moved(0, 0) mock_stop.assert_called_once_with(full_window.serial_mgr, cam_id=1) ....... # Win7 兼容:mock 串口端口列表,确保 COM 扫描正常 -
注意事项:用 @patch.multiple 批量 mock(e.g., themes.get_current_theme)。Win7 测试真实串口(COM3),检查 open 无异常。
-
执行:pytest --cov=ui/keyboard tests/keyboard/(覆盖率报告 htmlcov/index.html)。
4. 端到端测试(全链路测试)
目标:模拟用户操作,验证键盘 → 信号 → 核心 → 反馈的全流程(如按键触发串口写 + 灯闪)。
-
步骤:
- 测试按键链路:用 QTest.keyClick 模拟 ENT,按无效输入检查 ERR 灯 + 日志。
- 测试串口反馈:mock VirtualDevice 生成响应,检查 parsed_received 后 RX 闪 + LCD 更新(如果模式联动)。
- 测试模式切换:模拟 PRESET 按,检查 _set_mode → lcd "PRESET" + 右面板 tab 切换(main_window 集成)。
- 测试异常:模拟 serial.SerialException,检查 error.emit + ERR on。
- 性能测试:长按键序列(100次),检查无延迟(线程支持)。
-
代码示例(test_e2e.py,用 PyQt5.QtTest):
python# tests/test_e2e.py # 目标:模拟用户操作,验证 UI → 核心 → 反馈。 import pytest from PyQt5 import QtWidgets from PyQt5.QtTest import QTest from PyQt5.QtCore import Qt from ui.main_window import AppWindow @pytest.fixture def window(qapp): """提供一个完整的 AppWindow 实例,并在测试结束后关闭""" win = AppWindow() win.show() qapp.processEvents() yield win win.close() def _find_button(kb, text): """辅助函数:根据按钮文字查找 QPushButton""" btns = kb.findChildren(QtWidgets.QPushButton) return next((b for b in btns if b.text() == text), None) def test_full_keyboard_flow(window): kb = window.keyboard # 模拟数字输入 1-2-3 for digit in "123": target_btn = _find_button(kb, digit) assert target_btn is not None, f"Button {digit} not found" QTest.mouseClick(target_btn, Qt.LeftButton) ...... # Win7 兼容:QTest 稳定,测试真实 COM 端口 -
注意事项:端到端需 GUI 环境(pytest-qt 插件,pip install pytest-qt==4.4.0)。Win7 模拟旧硬件(用 loopback 串口测试)。
-
执行:pytest -v tests/test_e2e.py(慢测试,标记 @pytest.mark.slow)。
5. 手动测试与工具集成
目标:覆盖自动化难测部分(如手感、视觉)。
- 步骤 :
- 手感测试:运行 app.py,按数字检查 LCD 实时更新;摇杆拖拽检查平滑(无卡顿)。
- 视觉测试:切换 dark/light,检查 LCD/灯颜色;resize 窗口检查布局不崩。
- 异常手动:断开串口,检查 ERR 灯 + 弹窗。
- Win7 特定:多显示器测试(布局适应);旧 COM 端口(USB-RS232 转换器)验证。
- 工具:Monkey 测试(随机按键);UI 录屏(OBS Studio 捕获手感 demo)。
- 注意事项:文档测试 case(Excel:输入/预期/实际)。目标:无 crash,响应<100ms。
- 执行:手动 checklist,CI 配置(pytest + coverage)。
6. 测试集成到项目
- 项目添加:tests/ 目录 + .CI 配置(pytest + coverage)。
- 覆盖率目标:用 coverage html 查看热图,优先高风险(如串口 parse)。
- 益处:测试暴露重构遗漏(如信号未连),确保最终版稳定。
这一方案全面覆盖键盘测试,填补项目缺失。