test: 添加插件依赖机制自动化回归测试

- 新增 7 个测试用例固化插件依赖关系
- 验证 http 插件依赖 video
- 验证 ble/wifi/video/screen 插件无依赖
- 验证所有插件 ID 唯一性
- 验证拓扑排序正确性(依赖项先初始化)
- 所有 31 个测试通过

防止依赖关系漂移,确保架构稳定性。
This commit is contained in:
showen
2026-03-12 13:11:47 +08:00
parent 7091008f09
commit c48340dece
2 changed files with 153 additions and 0 deletions

View File

@@ -2008,3 +2008,58 @@ cargo check
## 结论
BLE GATT notify 功能已完整实现,符合 BlueZ GATT 规范。WiFi 状态变更时会主动推送 PropertiesChanged 信号给已订阅的 BLE 客户端。无需修改代码。
---
## 周雅婷 (测试工程师) - 2026-03-12
### 任务:插件依赖机制自动化回归测试
根据架构师王思远的要求,为插件依赖声明机制编写自动化回归测试,防止依赖关系漂移。
### 测试实现
在 `src/core/tests.rs` 中新增 7 个测试用例:
#### 1. 依赖关系固化测试
- `http_plugin_must_depend_on_video`: 验证 http 插件必须依赖 video (line 324-329)
- `ble_plugin_must_have_no_dependencies`: 验证 ble 插件无依赖 (line 331-336)
- `wifi_plugin_must_have_no_dependencies`: 验证 wifi 插件无依赖 (line 338-343)
- `video_plugin_must_have_no_dependencies`: 验证 video 插件无依赖 (line 345-350)
- `screen_plugin_must_have_no_dependencies`: 验证 screen 插件无依赖 (line 352-357)
#### 2. 插件 ID 唯一性测试
- `all_plugin_ids_must_be_unique`: 验证所有插件 ID 不重复 (line 359-385)
- 测试所有 5 个插件ble, http, screen, video, wifi
- 使用 HashSet 检测重复 ID
#### 3. 拓扑排序验证测试
- `topological_sort_places_http_after_video`: 验证依赖拓扑排序正确 (line 387-413)
- 使用 TestPlugin 模拟 http 依赖 video
- 验证 video 在 http 之前初始化
### 测试结果
```bash
export PATH=/home/showen/.rustup/toolchains/stable-aarch64-unknown-linux-gnu/bin:$PATH
cargo test --lib
```
✅ **所有 31 个测试通过**(包括新增的 7 个测试)
- 编译时间46.75s
- 测试执行时间0.17s
- 0 个失败0 个忽略
### 测试覆盖
- ✅ http 插件依赖 video
- ✅ ble/wifi/video/screen 插件无依赖
- ✅ 所有插件 ID 唯一
- ✅ 拓扑排序正确(依赖项先初始化)
### 技术细节
- 直接调用各插件的 `dependencies()` 方法验证返回值
- 使用 `assert_eq!` 和 `assert!` 进行断言
- 拓扑排序测试通过检查事件日志中的初始化顺序验证
### 价值
这些测试固化了当前的插件依赖关系,任何未经审查的依赖变更都会导致测试失败,防止依赖漂移。

View File

@@ -319,3 +319,101 @@ fn wifi_result_sent_to_manager_is_broadcast_to_plugins() {
assert!(has_event(&events, "msg:alpha:wifi_result:connected"));
assert!(has_event(&events, "msg:beta:wifi_result:connected"));
}
#[test]
fn http_plugin_must_depend_on_video() {
use crate::plugins::http::HttpPlugin;
let plugin = HttpPlugin::new();
let deps = plugin.dependencies();
assert_eq!(deps, vec!["video"], "http plugin must depend on video");
}
#[test]
fn ble_plugin_must_have_no_dependencies() {
use crate::plugins::ble::BlePlugin;
let plugin = BlePlugin::new();
let deps = plugin.dependencies();
assert!(deps.is_empty(), "ble plugin must have no dependencies");
}
#[test]
fn wifi_plugin_must_have_no_dependencies() {
use crate::plugins::wifi::WifiPlugin;
let plugin = WifiPlugin::new();
let deps = plugin.dependencies();
assert!(deps.is_empty(), "wifi plugin must have no dependencies");
}
#[test]
fn video_plugin_must_have_no_dependencies() {
use crate::plugins::video::VideoPlugin;
let plugin = VideoPlugin::new();
let deps = plugin.dependencies();
assert!(deps.is_empty(), "video plugin must have no dependencies");
}
#[test]
fn screen_plugin_must_have_no_dependencies() {
use crate::plugins::screen::ScreenPlugin;
let plugin = ScreenPlugin::new();
let deps = plugin.dependencies();
assert!(deps.is_empty(), "screen plugin must have no dependencies");
}
#[test]
fn all_plugin_ids_must_be_unique() {
use crate::plugins::ble::BlePlugin;
use crate::plugins::http::HttpPlugin;
use crate::plugins::screen::ScreenPlugin;
use crate::plugins::video::VideoPlugin;
use crate::plugins::wifi::WifiPlugin;
use std::collections::HashSet;
let plugins: Vec<Box<dyn Plugin>> = vec![
Box::new(BlePlugin::new()),
Box::new(HttpPlugin::new()),
Box::new(ScreenPlugin::new()),
Box::new(VideoPlugin::new()),
Box::new(WifiPlugin::new()),
];
let mut ids = HashSet::new();
for plugin in plugins {
let id = plugin.id();
assert!(ids.insert(id), "duplicate plugin id detected: '{}'", id);
}
}
#[test]
fn topological_sort_places_http_after_video() {
let events = Arc::new(Mutex::new(Vec::new()));
let mut manager = ServiceManager::new(test_config());
manager.register(Box::new(TestPlugin::new(
"http",
vec!["video"],
events.clone(),
)));
manager.register(Box::new(TestPlugin::new("video", vec![], events.clone())));
manager
.start_all()
.expect("start_all should succeed with http depending on video");
let event_log = lock_events(&events).clone();
let http_init_pos = event_log
.iter()
.position(|e| e == "init:http")
.expect("http should be initialized");
let video_init_pos = event_log
.iter()
.position(|e| e == "init:video")
.expect("video should be initialized");
assert!(
video_init_pos < http_init_pos,
"video must be initialized before http (video at {}, http at {})",
video_init_pos,
http_init_pos
);
}