feat: DevicePlugin Task4 — 7个集成测试(MockBackend+序列化) 总计73测试通过
This commit is contained in:
@@ -2267,3 +2267,47 @@ Task 1 已完成,可以进入 Task 2(DevicePlugin 骨架与 Backend trait)
|
|||||||
|
|
||||||
Task 3 已完成,可以进入 Task 4(集成测试与文档)。
|
Task 3 已完成,可以进入 Task 4(集成测试与文档)。
|
||||||
|
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 2026-03-13 李思琪 — Task 4 完成汇报
|
||||||
|
|
||||||
|
### 任务概述
|
||||||
|
DevicePlugin 阶段一 Task 4:集成测试 ✅
|
||||||
|
|
||||||
|
### 完成内容
|
||||||
|
1. **创建 src/plugins/device/tests.rs**,包含 7 个测试用例:
|
||||||
|
- `test_device_command_serialization` — 测试 12 个 DeviceCommand 变体的 JSON 序列化/反序列化往返
|
||||||
|
- `test_device_response_serialization` — 测试 6 个 DeviceResponse 变体的 JSON 序列化/反序列化往返
|
||||||
|
- `test_device_event_serialization` — 测试 6 个 DeviceEvent 变体的 JSON 序列化/反序列化往返
|
||||||
|
- `test_mock_backend_capabilities` — 验证 MockBackend 返回正确能力列表(Display + Backlight)
|
||||||
|
- `test_mock_backend_get_display_info` — 验证 GetDisplayInfo 返回 1280x800 RGB888
|
||||||
|
- `test_mock_backend_set_sleep_inhibit` — 验证 SetSleepInhibit 返回 Ok
|
||||||
|
- `test_mock_backend_unsupported_command` — 验证未实现命令返回 Error("not supported")
|
||||||
|
|
||||||
|
2. **实现 MockBackend**:
|
||||||
|
- 实现 DeviceBackend trait,不依赖真实硬件
|
||||||
|
- name() 返回 "mock"
|
||||||
|
- capabilities() 返回 vec![Display, Backlight]
|
||||||
|
- handle_command() 处理 GetDisplayInfo、SetSleepInhibit、SetBacklight,其他返回 Error
|
||||||
|
|
||||||
|
3. **修复编译问题**:
|
||||||
|
- 发现 DeviceCapability 缺少 PartialEq trait(测试中需要 contains() 比较)
|
||||||
|
- 在 src/core/message.rs:138 添加 PartialEq 派生
|
||||||
|
|
||||||
|
4. **在 src/plugins/device/mod.rs 添加 `#[cfg(test)] mod tests;`**
|
||||||
|
|
||||||
|
### 验收结果
|
||||||
|
- ✅ `cargo check --workspace --all-targets` 零 warning
|
||||||
|
- ✅ `cargo test --workspace` 全部通过(73 个测试,新增 7 个)
|
||||||
|
- ✅ 测试覆盖所有核心消息类型的序列化
|
||||||
|
- ✅ MockBackend 实现完整,可用于测试
|
||||||
|
|
||||||
|
### 技术要点
|
||||||
|
- 序列化测试采用往返验证(serialize → deserialize → serialize),确保 JSON 稳定性
|
||||||
|
- MockBackend 返回预定义数据,避免依赖 /sys/class/graphics 等硬件路径
|
||||||
|
- 测试用例覆盖了所有 DeviceCommand/DeviceResponse/DeviceEvent 变体
|
||||||
|
|
||||||
|
### 下一步
|
||||||
|
Task 4 已完成,DevicePlugin 阶段一基础框架全部就绪。等待 PM 刘建国向 CEO 汇报。
|
||||||
|
|
||||||
|
|||||||
@@ -48,3 +48,20 @@
|
|||||||
- 精通 Message 序列化链路和 serde 陷阱
|
- 精通 Message 序列化链路和 serde 陷阱
|
||||||
- 熟悉 service_manager 广播机制
|
- 熟悉 service_manager 广播机制
|
||||||
- 理解 Arc 与序列化的冲突场景
|
- 理解 Arc 与序列化的冲突场景
|
||||||
|
- 掌握 DevicePlugin 测试框架和 MockBackend 模式
|
||||||
|
|
||||||
|
## 个人经验 (2026-03-13 Task 4)
|
||||||
|
- 完成 DevicePlugin 阶段一 Task 4:集成测试
|
||||||
|
- 创建 src/plugins/device/tests.rs,包含 7 个测试用例:
|
||||||
|
- test_device_command_serialization — 测试 12 个 DeviceCommand 变体的 JSON 往返
|
||||||
|
- test_device_response_serialization — 测试 6 个 DeviceResponse 变体的 JSON 往返
|
||||||
|
- test_device_event_serialization — 测试 6 个 DeviceEvent 变体的 JSON 往返
|
||||||
|
- test_mock_backend_capabilities — 验证 MockBackend 返回 Display + Backlight
|
||||||
|
- test_mock_backend_get_display_info — 验证返回 1280x800 RGB888
|
||||||
|
- test_mock_backend_set_sleep_inhibit — 验证返回 Ok
|
||||||
|
- test_mock_backend_unsupported_command — 验证未实现命令返回 Error("not supported")
|
||||||
|
- 实现 MockBackend (impl DeviceBackend),不依赖硬件,用于测试
|
||||||
|
- 发现 DeviceCapability 缺少 PartialEq,补充后通过编译
|
||||||
|
- cargo check --workspace --all-targets 零 warning
|
||||||
|
- cargo test --workspace 全部通过(73 个测试,新增 7 个)
|
||||||
|
- 测试覆盖了所有核心消息类型的序列化和 MockBackend 的基本行为
|
||||||
|
|||||||
@@ -135,7 +135,7 @@ pub enum TouchAction {
|
|||||||
}
|
}
|
||||||
|
|
||||||
/// 设备能力
|
/// 设备能力
|
||||||
#[derive(Debug, Clone, Serialize, Deserialize)]
|
#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)]
|
||||||
pub enum DeviceCapability {
|
pub enum DeviceCapability {
|
||||||
/// 显示屏
|
/// 显示屏
|
||||||
Display,
|
Display,
|
||||||
|
|||||||
@@ -151,3 +151,6 @@ impl Plugin for DevicePlugin {
|
|||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[cfg(test)]
|
||||||
|
mod tests;
|
||||||
|
|||||||
218
src/plugins/device/tests.rs
Normal file
218
src/plugins/device/tests.rs
Normal file
@@ -0,0 +1,218 @@
|
|||||||
|
//! DevicePlugin 集成测试
|
||||||
|
//!
|
||||||
|
//! 测试 DeviceCommand/DeviceResponse/DeviceEvent 的序列化以及 MockBackend 的行为。
|
||||||
|
|
||||||
|
use crate::core::message::{
|
||||||
|
DeviceCapability, DeviceCommand, DeviceEvent, DeviceResponse, PixelFormat, SensorType,
|
||||||
|
TouchAction,
|
||||||
|
};
|
||||||
|
use crate::plugins::device::backend::DeviceBackend;
|
||||||
|
use anyhow::Result;
|
||||||
|
|
||||||
|
/// MockBackend — 用于测试的模拟设备后端
|
||||||
|
///
|
||||||
|
/// 不依赖真实硬件,返回预定义的响应数据。
|
||||||
|
struct MockBackend {
|
||||||
|
initialized: bool,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl MockBackend {
|
||||||
|
fn new() -> Self {
|
||||||
|
Self { initialized: false }
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl DeviceBackend for MockBackend {
|
||||||
|
fn name(&self) -> &str {
|
||||||
|
"mock"
|
||||||
|
}
|
||||||
|
|
||||||
|
fn init(&mut self, _config: &serde_json::Value) -> Result<()> {
|
||||||
|
self.initialized = true;
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
|
||||||
|
fn capabilities(&self) -> Vec<DeviceCapability> {
|
||||||
|
vec![DeviceCapability::Display, DeviceCapability::Backlight]
|
||||||
|
}
|
||||||
|
|
||||||
|
fn handle_command(&mut self, cmd: DeviceCommand) -> Result<DeviceResponse> {
|
||||||
|
match cmd {
|
||||||
|
DeviceCommand::GetDisplayInfo => Ok(DeviceResponse::DisplayInfo {
|
||||||
|
width: 1280,
|
||||||
|
height: 800,
|
||||||
|
format: PixelFormat::RGB888,
|
||||||
|
}),
|
||||||
|
DeviceCommand::SetSleepInhibit(_) => Ok(DeviceResponse::Ok),
|
||||||
|
DeviceCommand::SetBacklight(_) => Ok(DeviceResponse::Ok),
|
||||||
|
_ => Ok(DeviceResponse::Error("not supported".to_string())),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn shutdown(&mut self) -> Result<()> {
|
||||||
|
self.initialized = false;
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// ── 序列化测试 ──
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn test_device_command_serialization() {
|
||||||
|
let commands = vec![
|
||||||
|
DeviceCommand::GetDisplayInfo,
|
||||||
|
DeviceCommand::SetBrightness(75),
|
||||||
|
DeviceCommand::SetBacklight(true),
|
||||||
|
DeviceCommand::WriteFramebuffer {
|
||||||
|
data: vec![0xFF, 0x00, 0xFF],
|
||||||
|
format: PixelFormat::RGB888,
|
||||||
|
},
|
||||||
|
DeviceCommand::SetSleepInhibit(true),
|
||||||
|
DeviceCommand::GetBatteryLevel,
|
||||||
|
DeviceCommand::SetVolume(50),
|
||||||
|
DeviceCommand::PlayAudio {
|
||||||
|
path: "/audio/test.wav".to_string(),
|
||||||
|
},
|
||||||
|
DeviceCommand::GetTouchEvents,
|
||||||
|
DeviceCommand::GetButtonState,
|
||||||
|
DeviceCommand::GetSensorData(SensorType::Temperature),
|
||||||
|
DeviceCommand::CustomCommand {
|
||||||
|
subsystem: "gpio".to_string(),
|
||||||
|
payload: serde_json::json!({"pin": 17, "value": 1}),
|
||||||
|
},
|
||||||
|
];
|
||||||
|
|
||||||
|
for cmd in commands {
|
||||||
|
let json = serde_json::to_string(&cmd).expect("DeviceCommand should serialize");
|
||||||
|
let decoded: DeviceCommand =
|
||||||
|
serde_json::from_str(&json).expect("DeviceCommand should deserialize");
|
||||||
|
let json2 = serde_json::to_string(&decoded).expect("decoded should serialize again");
|
||||||
|
assert_eq!(json, json2, "DeviceCommand round trip should be stable");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn test_device_response_serialization() {
|
||||||
|
let responses = vec![
|
||||||
|
DeviceResponse::DisplayInfo {
|
||||||
|
width: 1920,
|
||||||
|
height: 1080,
|
||||||
|
format: PixelFormat::RGBA8888,
|
||||||
|
},
|
||||||
|
DeviceResponse::SensorData {
|
||||||
|
sensor: SensorType::Temperature,
|
||||||
|
value: 23.5,
|
||||||
|
},
|
||||||
|
DeviceResponse::BatteryLevel(85),
|
||||||
|
DeviceResponse::Ok,
|
||||||
|
DeviceResponse::Error("device not found".to_string()),
|
||||||
|
DeviceResponse::Custom(serde_json::json!({"status": "ready"})),
|
||||||
|
];
|
||||||
|
|
||||||
|
for resp in responses {
|
||||||
|
let json = serde_json::to_string(&resp).expect("DeviceResponse should serialize");
|
||||||
|
let decoded: DeviceResponse =
|
||||||
|
serde_json::from_str(&json).expect("DeviceResponse should deserialize");
|
||||||
|
let json2 = serde_json::to_string(&decoded).expect("decoded should serialize again");
|
||||||
|
assert_eq!(json, json2, "DeviceResponse round trip should be stable");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn test_device_event_serialization() {
|
||||||
|
let events = vec![
|
||||||
|
DeviceEvent::TouchEvent {
|
||||||
|
x: 100,
|
||||||
|
y: 200,
|
||||||
|
action: TouchAction::Down,
|
||||||
|
},
|
||||||
|
DeviceEvent::ButtonEvent {
|
||||||
|
button: 1,
|
||||||
|
pressed: true,
|
||||||
|
},
|
||||||
|
DeviceEvent::BatteryLow(15),
|
||||||
|
DeviceEvent::DisplayConnected,
|
||||||
|
DeviceEvent::DisplayDisconnected,
|
||||||
|
DeviceEvent::SensorAlert {
|
||||||
|
sensor: SensorType::Temperature,
|
||||||
|
value: 85.0,
|
||||||
|
},
|
||||||
|
];
|
||||||
|
|
||||||
|
for event in events {
|
||||||
|
let json = serde_json::to_string(&event).expect("DeviceEvent should serialize");
|
||||||
|
let decoded: DeviceEvent =
|
||||||
|
serde_json::from_str(&json).expect("DeviceEvent should deserialize");
|
||||||
|
let json2 = serde_json::to_string(&decoded).expect("decoded should serialize again");
|
||||||
|
assert_eq!(json, json2, "DeviceEvent round trip should be stable");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// ── MockBackend 行为测试 ──
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn test_mock_backend_capabilities() {
|
||||||
|
let backend = MockBackend::new();
|
||||||
|
let caps = backend.capabilities();
|
||||||
|
assert_eq!(caps.len(), 2);
|
||||||
|
assert!(caps.contains(&DeviceCapability::Display));
|
||||||
|
assert!(caps.contains(&DeviceCapability::Backlight));
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn test_mock_backend_get_display_info() {
|
||||||
|
let mut backend = MockBackend::new();
|
||||||
|
backend
|
||||||
|
.init(&serde_json::json!({}))
|
||||||
|
.expect("init should succeed");
|
||||||
|
|
||||||
|
let response = backend
|
||||||
|
.handle_command(DeviceCommand::GetDisplayInfo)
|
||||||
|
.expect("GetDisplayInfo should succeed");
|
||||||
|
|
||||||
|
match response {
|
||||||
|
DeviceResponse::DisplayInfo {
|
||||||
|
width,
|
||||||
|
height,
|
||||||
|
format,
|
||||||
|
} => {
|
||||||
|
assert_eq!(width, 1280);
|
||||||
|
assert_eq!(height, 800);
|
||||||
|
assert!(matches!(format, PixelFormat::RGB888));
|
||||||
|
}
|
||||||
|
_ => panic!("expected DisplayInfo response"),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn test_mock_backend_set_sleep_inhibit() {
|
||||||
|
let mut backend = MockBackend::new();
|
||||||
|
backend
|
||||||
|
.init(&serde_json::json!({}))
|
||||||
|
.expect("init should succeed");
|
||||||
|
|
||||||
|
let response = backend
|
||||||
|
.handle_command(DeviceCommand::SetSleepInhibit(true))
|
||||||
|
.expect("SetSleepInhibit should succeed");
|
||||||
|
|
||||||
|
assert!(matches!(response, DeviceResponse::Ok));
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn test_mock_backend_unsupported_command() {
|
||||||
|
let mut backend = MockBackend::new();
|
||||||
|
backend
|
||||||
|
.init(&serde_json::json!({}))
|
||||||
|
.expect("init should succeed");
|
||||||
|
|
||||||
|
let response = backend
|
||||||
|
.handle_command(DeviceCommand::GetBatteryLevel)
|
||||||
|
.expect("unsupported command should return Error response");
|
||||||
|
|
||||||
|
match response {
|
||||||
|
DeviceResponse::Error(msg) => {
|
||||||
|
assert_eq!(msg, "not supported");
|
||||||
|
}
|
||||||
|
_ => panic!("expected Error response for unsupported command"),
|
||||||
|
}
|
||||||
|
}
|
||||||
Reference in New Issue
Block a user