TEST02.重构后键盘部分的测试操作一步一步详细指导
在上一篇《重构后键盘部分的测试方案规划》中,我们从宏观角度梳理了测试的整体思路:为什么要测、测什么、覆盖率目标以及测试分类。那篇文章更像是一份蓝图,帮助我们建立测试体系的战略方向。
本篇《重构后键盘部分的测试操作一步一步详细指导》则承接前文,聚焦于具体的落地实践。我们将从环境准备开始,逐步讲解如何编写单元测试、集成测试、端到端测试,并补充手动验证环节。目标是让读者不仅理解测试的重要性,还能亲手跑通整个流程,获得可量化的覆盖率结果。预计耗时 2--4 小时,适合在 Windows 7 环境下完整演练。
前提:项目已克隆到本地,Python 3.7 环境激活,pyserial/PyQt5 已安装。Win7 注意:用管理员运行命令提示符,避免权限问题。
步骤1:环境准备(10--15 分钟)
-
安装测试依赖:
-
打开命令提示符(cmd.exe),导航到项目根目录。
-
创建/激活 virtualenv(推荐,避免全局污染):
bashpython -m venv venv venv\Scripts\activate -
安装依赖(requirements.txt 已含 PyQt5/pyserial,再加测试工具):
bashpip install pytest==7.4.0 pytest-qt==4.4.0 coverage==6.5.0(这些版本兼容 Python 3.7 和 Win7,无高版本依赖冲突。)
-
-
创建 tests/ 目录和测试文件:
-
在项目根新建
tests/keyboard/。 -
添加示例测试文件(从规划复制,稍后详述)。初始结构如下:
texttests/ ├── keyboard/ │ ├── test_panel.py # 单元:按键 / LCD │ ├── test_integration.py # 集成:信号 + 串口 mock ├── analyze_coverage.py # 覆盖率分析脚本 ├── test_e2e.py # 端到端:全链路 └── conftest.py # 全局 fixtures(如 QApplication)简短的说明表格:
文件/目录 作用说明 keyboard/键盘相关测试模块目录 test_panel.py单元测试:按键输入、LCD 显示 test_integration.py集成测试:信号与串口交互 test_e2e.py端到端测试:完整链路验证 analyze_coverage.py覆盖率分析辅助脚本 conftest.py全局 pytest fixtures(QApplication 等)
-
-
添加 conftest.py(全局 fixture):
python# tests/conftest.py # 全局 fixture 配置 import sys import os import pytest from PyQt5.QtWidgets import QApplication from unittest.mock import MagicMock # 添加项目根目录到 sys.path(tests/ 的上两级) project_root = os.path.abspath(os.path.join(os.path.dirname(__file__), '..')) if project_root not in sys.path: sys.path.insert(0, project_root) @pytest.fixture(scope="session") def qapp(): """ 全局 QApplication fixture,整个测试会话只初始化一次。 """ app = QApplication.instance() or QApplication([]) yield app app.quit() @pytest.fixture def app_window(qapp): """ 提供一个完整的 AppWindow 实例,并在测试结束后自动关闭。 避免在各个测试文件中重复定义。 """ from ui.main_window import AppWindow win = AppWindow() win.show() qapp.processEvents() yield win win.close() @pytest.fixture def keyboard_panel(qapp): """ 提供一个独立的 KeyboardPanel 实例,用于单元测试。 """ from ui.keyboard.panel import KeyboardPanel panel = KeyboardPanel() qapp.processEvents() yield panel panel.close()这样,所有测试文件都可以直接使用
qappfixture,而无需重复初始化 QApplication。 -
添加 analyze_coverage.py(覆盖率分析辅助脚本):
python# tests/analyze_coverage.py import json def main(): with open("coverage.json", "r", encoding="utf-8") as f: data = json.load(f) totals = data["totals"] print(f"整体覆盖率: {totals['percent_covered_display']}%") for filename, info in data["files"].items(): pct = info["summary"]["percent_covered_display"] if int(pct) < 50: print(f"{filename}: 覆盖率 {pct}%") print("缺失行:", info["missing_lines"]) if __name__ == "__main__": main()运行
python tests/analyze_coverage.py可以快速定位覆盖率薄弱的文件和缺失行,指导后续补充测试。 -
验证环境:
-
运行
pytest --version,预期输出 pytest 7.4.0。 -
运行项目:
python app.py,检查 UI 正常(键盘显示)。 -
检查
pytest.ini配置:inimarkers = keyboard: 测试键盘 UI 与交互 filterwarnings = ignore:.*sipPyTypeDict.*:DeprecationWarning这样运行时可以用
pytest -m keyboard来跑所有键盘相关测试,并且不会刷屏显示 PyQt5 的弃用告警。
-
步骤2:单元测试(隔离组件)
目标:测试单个文件(如 panel.py 的 _on_digit),不跑 GUI/串口。快速反馈。
-
编写/运行 test_panel.py:
python- 预期:所有 `@pytest.mark.keyboard` 的测试通过。 - 如果失败:检查 import 路径(from ui.keyboard.panel import KeyboardPanel)。pytest tests/keyboard/test_panel.py -v -W ignore::DeprecationWarning# tests/keyboard/test_panel.py import pytest from ui.keyboard.panel import KeyboardPanel from unittest.mock import MagicMock @pytest.fixture def keyboard(qapp): return KeyboardPanel() @pytest.mark.keyboard def test_on_digit_limit(keyboard): for i in range(5): # 输入5位 keyboard._on_digit(1) assert len(keyboard.input_buffer) == 4 # 限长4 assert keyboard.lcd.display.called # mock 检查调用 @pytest.mark.keyboard def test_set_mode(keyboard): keyboard.mode_changed = MagicMock() keyboard.lcd.display_text = MagicMock() keyboard._set_mode("PRESET") assert keyboard.mode == "PRESET" keyboard.mode_changed.emit.assert_called_with("PRESET") keyboard.lcd.display_text.assert_called_with("PRESET", ...) -
运行
shpytest -m keyboard -v- 预期:所有 test_ 通过(e.g., 2 passed)。
- 如果失败:检查 import 路径(from ui.keyboard.panel import KeyboardPanel)。
-
类似测试 LCD/indicator:
- test_lcd.py:mock theme,检查 display_text 的 setStyleSheet。
- test_indicator.py:mock QTimer,检查 flash 后 off 调用。
-
覆盖率检查:
coverage run -m pytest tests/keyboard/ coverage report -m # 显示缺失行 coverage html # 生成 htmlcov/index.html 查看- 目标:ui/keyboard/ 覆盖 90%+。
步骤3:集成测试(模块交互)
目标:测试键盘与核心/mock 串口的交互(如按键 emit → 串口 write)。
-
编写 test_integration.py:
python# tests/keyboard/test_integration.py import pytest from unittest.mock import patch, MagicMock from ui.keyboard.panel import KeyboardPanel from core.protocol import ptz_control @pytest.fixture def keyboard(qapp): kb = KeyboardPanel() kb.serial_mgr = MagicMock() # mock 串口 return kb @pytest.mark.keyboard def test_joystick_to_protocol(keyboard): with patch('core.protocol.ptz_control') as mock_ptz: keyboard.joystick_moved.emit(10, -20) mock_ptz.assert_called_with(keyboard.serial_mgr, ..., 10, -20) @pytest.mark.keyboard def test_enter_error_indicator(keyboard): keyboard.input_buffer = "invalid" keyboard.indicator_manager.on = MagicMock() keyboard._on_enter() keyboard.indicator_manager.on.assert_called_with("ERR") -
运行:
pytest -m keyboard -v- 预期:通过,mock assert_called 检查交互。
-
协议集成测试:
- 新建 test_protocol_integration.py:mock build_pelco_d,检查 checksum。

- 新建 test_protocol_integration.py:mock build_pelco_d,检查 checksum。
步骤4:端到端测试(全链路)
目标:模拟用户操作,验证 UI → 核心 → 反馈。
-
安装 pytest-qt (如果未装):
pip install pytest-qt==4.0.2。 -
编写 test_e2e.py:
python# tests/test_e2e.py import pytest from PyQt5.QtTest import QTest from PyQt5.QtCore import Qt from ui.main_window import AppWindow @pytest.mark.keyboard def test_full_keyboard_flow(qapp): window = AppWindow() window.show() # 显示窗口 qapp.processEvents() # 模拟数字输入 for digit in "123": btn = window.keyboard.findChild(QtWidgets.QPushButton, digit) # 假设 text=digit QTest.mouseClick(btn, Qt.LeftButton) assert window.keyboard.lcd.value() == 123 # 检查 LCD # 模拟 ENT ent_btn = window.keyboard.findChild(QtWidgets.QPushButton, "ENT") QTest.mouseClick(ent_btn, Qt.LeftButton) # 检查 TX 闪(indicator_manager.state("TX") 暂 True) # 异常:输入无效后 ENT,检查 ERR on -
运行:
pytest -m keyboard -v- 预期:窗口短暂闪现,通过(Win7 可能慢,加 -s 显示输出)。
- 注意:端到端慢,标记 @pytest.mark.slow。

步骤5:手动测试与验证
目标:覆盖自动化难测(如视觉/手感)。
-
运行项目 :
python app.py。 -
Checklist 操作:
- 输入 1234 → LCD 显示 1234,超限不增。
- 按 PRESET → LCD "PRESET" + 颜色变(themes.py)。
- 摇杆拖拽 → TX 闪(模拟发送)。
- 切换主题(右上按钮) → LCD/灯颜色变。
- resize 窗口 → 布局不崩(stretch 生效)。
- 模拟错误(断串口) → ERR 灯 + 弹窗。
- Win7 特定:多分辨率(1024x768)测试;旧 COM 端口连接真实设备验证。
-
最终覆盖率:
coverage run -m pytest -m keyboard coverage report -m coverage htmlui/keyboard/覆盖率 90%+。
步骤6:常见问题与调试
- 失败:ImportError → 检查 PYTHONPATH(sys.path.append("...") 在 test)。
- Win7 卡顿:减 QTimer 间隔;关防病毒。
- CI:GitHub Actions 加 Win7 runner(自建或第三方)。
- 扩展:加 @pytest.mark.parametrize 参数化(e.g., 多模式测试)。
通过本文的详细操作,你已经能够在本地环境中完成键盘部分的测试:从单元到集成,再到端到端和手动验证,覆盖率报告也能直观呈现测试成果。与上一篇的规划篇形成呼应,本篇提供了实操指南,真正把测试方案落地。
至此,键盘模块的测试体系已经基本完善。下一篇我们将进入宏系统的开发与测试,继续扩展整个项目的可靠性与可维护性。这样,一个完整的测试闭环就逐步建立起来,为后续功能迭代打下坚实基础。