第 19 篇:集成测试(serial + protocol + macro)
现在是时候从单元测试 逐步过渡到集成测试阶段了。
我们已经完成了核心纯逻辑模块(协议构建/解析、宏语法、模板渲染、日志发射、规则引擎、模拟器状态机等)的单元测试,覆盖率已经相当不错。接下来需要验证这些模块组合在一起 时是否能正确协作,尤其是涉及串口 → 协议解析 → 命令分发 → 宏执行 → 协议构建 → 串口输出的完整链路。
以下是针对这个项目的集成测试规划建议(重点放在 serial + protocol + macro 链路),以及逐步实施路线。
集成测试目标(优先级排序)
| 优先级 | 测试目标 | 涉及模块链路 | 建议测试类型 | 难度 |
|---|---|---|---|---|
| ★★★★★ | 1. 串口收发 → 协议解析 → 虚拟设备状态变化 | serial.worker + protocol + virtual_device | 模拟器集成 | 中 |
| ★★★★☆ | 2. 宏命令 → 协议构建 → 串口发送 | macro.commands + protocol + serial.manager/worker | Mock串口发送 | 中 |
| ★★★★☆ | 3. 完整宏脚本执行(包含循环/条件/延时) | macro.engine + parser + commands + protocol | 模拟器 + Mock | 高 |
| ★★★☆☆ | 4. 报警规则触发 → 执行宏/动作 | alarm.rules + macro.engine + commands | 模拟器触发 | 中 |
| ★★★☆☆ | 5. 键盘面板 → 宏/PTZ命令 → 协议 → 发送 | ui.keyboard.panel + serial + protocol | UI + Mock串口 | 高 |
| ★★☆☆☆ | 6. 真实串口收发(可选,后期) | 全链路 + 真实设备或两台电脑互连 | 端到端 | 很高 |
推荐的集成测试技术栈与工具
| 目的 | 推荐工具/方式 | 理由 / 优点 |
|---|---|---|
| 模拟串口 | pytest-mock + unittest.mock 或 pyfake-serial |
无需真实硬件,最快、最干净、可控 |
| 协议层 Mock | 直接 mock serial.Serial.write / read |
验证协议构建与解析是否匹配 |
| 虚拟设备注入 | 使用已有的 VirtualDevice |
天然适合作为"下游"被测对象 |
| 宏执行环境 Mock | mock MacroCommands 的硬件方法(如 ptz_control) |
隔离真实串口,专注宏逻辑 |
| 测试异步/线程 | pytest-asyncio 或 pytest-qt + QTimer |
处理 SerialWorker 的定时读取、宏引擎的 QThread |
| UI 集成测试(可选后期) | pytest-qt + qtbot |
可模拟按钮点击、摇杆拖动等,但前期可先跳过 |
逐步实施路线(大概 4~6 周完成核心部分)
第 1 周:搭建集成测试基础框架 + 模拟器闭环测试
目标:验证 串口收到数据 → 解析 → VirtualDevice 状态变化 → 返回响应 的完整链路
python
# tests/integration/test_simulator_loop.py
import pytest
from unittest.mock import MagicMock, patch
from core.serial.worker import SerialWorker
from core.simulator.virtual_device import VirtualDevice
from core.protocol import get_protocol
@pytest.fixture
def virtual_device():
return VirtualDevice(cam_id=1)
@pytest.fixture
def mock_serial():
with patch("serial.Serial") as mock_ser:
mock_ser_instance = mock_ser.return_value
mock_ser_instance.is_open = True
mock_ser_instance.read.side_effect = lambda size: b"" # 默认空
yield mock_ser_instance
def test_receive_ptz_command_updates_virtual_device(qtbot, mock_serial, virtual_device):
# 准备 Pelco-D 绝对移动命令(示例)
cmd = bytes([0xFF, 0x01, 0x00, 0x04, 0x20, 0x20, 0x45]) # pan 32, tilt 32
# 模拟收到数据
worker = SerialWorker(port="COM3", baud=9600, protocol="D")
worker._ser = mock_serial
worker._buffer = bytearray(cmd)
# 注入虚拟设备处理逻辑(实际项目中可通过 dependency injection)
protocol = get_protocol(worker, "D")
protocol._virtual_device = virtual_device # 假设支持注入
# 触发一次读取处理
worker._process_buffer()
status = virtual_device.get_status_dict()
assert "pan" in status
assert abs(float(status["pan"][:-1]) - 32.0 * some_scale_factor) < 1.0 # 根据实际比例断言
第 2~3 周:宏命令 → 协议构建 → 串口发送 集成
目标:运行简单宏 → 验证是否正确调用 serial_mgr.write() 并发出正确协议字节
python
# tests/integration/test_macro_to_serial.py
from core.macro.engine import MacroEngine
from core.macro.commands import MacroCommands
from unittest.mock import MagicMock
@pytest.fixture
def mock_serial_manager():
mgr = MagicMock()
mgr.write = MagicMock()
mgr.protocol = "D"
return mgr
def test_macro_ptz_move_generates_correct_packet(mock_serial_manager):
engine = MacroEngine(mock_serial_manager)
commands = MacroCommands(mock_serial_manager)
script = """
ptz(1, 50, 30) # 摄像机1,pan 50, tilt 30
delay(100)
stop_ptz(1)
"""
engine.set_script(script)
engine.run()
# 验证是否调用了 write 两次(move + stop)
assert mock_serial_manager.write.call_count >= 2
# 进一步断言发出的字节(Pelco-D 格式)
first_call = mock_serial_manager.write.call_args_list[0]
packet = first_call[0][0]
assert packet.startswith(b'\xFF')
assert len(packet) == 7
# ... 校验 checksum、cmd1/cmd2 等
第 4 周:完整宏脚本 + 虚拟设备闭环
目标:运行带循环/延时的宏脚本,验证虚拟设备状态最终是否符合预期
python
def test_pattern_run_macro_updates_virtual_state():
# 准备带 pattern_run 的脚本
script = """
pattern_run(1, 2)
delay(5000)
pattern_stop(1, 2)
"""
device = VirtualDevice(1)
engine = MacroEngine(mock_serial_manager_with_virtual_device)
# 注入虚拟设备
engine.commands._virtual_device = device
engine.set_script(script)
engine.run()
# 等待宏执行完成(可使用 qtbot.wait + 信号)
# 断言 device.pattern_running == False 等状态
第 5 周:报警规则 + 宏联动集成
python
# tests/integration/test_alarm_macro_linkage.py
def test_alarm_triggers_macro():
rules = load_alarm_rules()
# 模拟收到报警码 1002
execute_alarm_action(app_window_mock, 1002, rules)
# 验证是否调用了 MacroEngine.run("emergency_shutdown.macro")
总结建议
- 优先级最高 :先把 模拟器闭环(收 → 解析 → 虚拟设备状态 → 响应)做通,这是最有价值的集成验证点。
- 第二优先:宏 → 协议构建 → Mock 串口发送(验证协议正确性)。
- 第三阶段:完整宏脚本 + 虚拟设备状态断言(最接近真实使用场景)。
- 工具推荐 :
pytest+pytest-mock+pytest-qt(如果涉及 UI) +pytest-timeout(防止死循环)。 - 测试数据:准备一套标准化的 Pelco-D/P 命令字节样本(至少 20~30 种常见命令)作为 fixture。