📋 前置准备
1. 安装 Rust
Windows 安装步骤:
-
下载 Rust 安装器
- 访问:https://rustup.rs/
- 或直接下载:https://win.rustup.rs/x86_64
-
运行安装器
# 下载后运行 rustup-init.exe # 选择默认安装选项(按 1 然后回车) -
验证安装
# 重新打开 PowerShell rustc --version cargo --version -
配置国内镜像(可选,加速下载)
# 创建配置文件 mkdir $env:USERPROFILE\.cargo notepad $env:USERPROFILE\.cargo\config.toml添加以下内容:
[source.crates-io] replace-with = 'ustc' [source.ustc] registry = "https://mirrors.ustc.edu.cn/crates.io-index"
2. 安装 VS Code Rust 插件(推荐)
- rust-analyzer: Rust 语言服务器
- CodeLLDB: 调试器
- crates: 依赖管理
🏗️ 第 1 步:创建项目结构
1.1 创建新项目
cd C:\Users\xli23\Desktop\uv-env
cargo new iot-automation-rust
cd iot-automation-rust
1.2 项目结构
iot-automation-rust/
├── Cargo.toml # 项目配置和依赖
├── src/
│ ├── main.rs # 程序入口
│ ├── lib.rs # 库入口
│ ├── cli.rs # 命令行参数
│ ├── config/ # 配置模块
│ │ ├── mod.rs
│ │ ├── parser.rs
│ │ └── global.rs
│ ├── device/ # 设备模块
│ │ ├── mod.rs
│ │ └── adb.rs
│ ├── test/ # 测试模块
│ │ ├── mod.rs
│ │ ├── base.rs
│ │ └── runner.rs
│ └── utils/ # 工具模块
│ ├── mod.rs
│ └── logger.rs
├── tests/ # 集成测试
└── examples/ # 示例代码
📝 第 2 步:配置 Cargo.toml
创建 Cargo.toml 文件:
[package]
name = "iot-automation-rust"
version = "0.1.0"
edition = "2021"
[dependencies]
# 异步运行时
tokio = { version = "1", features = ["full"] }
async-trait = "0.1"
# 错误处理
anyhow = "1.0"
thiserror = "1.0"
# 命令行参数
clap = { version = "4", features = ["derive"] }
# 日志
log = "0.4"
env_logger = "0.11"
# 序列化
serde = { version = "1.0", features = ["derive"] }
serde_json = "1.0"
# XML 解析
quick-xml = { version = "0.31", features = ["serialize"] }
# 正则表达式
regex = "1.10"
# 时间处理
chrono = "0.4"
[dev-dependencies]
tokio-test = "0.4"
安装依赖:
cargo build
🔧 第 3 步:实现 ADB 封装
3.1 创建 device 模块
创建 src/device/mod.rs:
pub mod adb;
pub use adb::Adb;
3.2 实现 ADB 核心功能
创建 src/device/adb.rs:
use anyhow::{Context, Result};
use log::{info, error};
use std::process::{Command, Output};
use std::time::Duration;
use tokio::time::sleep;
/// ADB 设备管理器
#[derive(Debug, Clone)]
pub struct Adb {
/// 设备序列号
serial: String,
}
impl Adb {
/// 创建新的 ADB 实例
pub fn new(serial: impl Into<String>) -> Self {
Self {
serial: serial.into(),
}
}
/// 执行 ADB 命令
pub fn run_cmd(&self, args: &[&str]) -> Result<String> {
let mut cmd = Command::new("adb");
// 添加设备序列号
cmd.args(["-s", &self.serial]);
// 添加命令参数
cmd.args(args);
info!("[Send] adb -s {} {}", self.serial, args.join(" "));
let output = cmd
.output()
.context("Failed to execute adb command")?;
self.handle_output(output)
}
/// 处理命令输出
fn handle_output(&self, output: Output) -> Result<String> {
let stdout = String::from_utf8_lossy(&output.stdout).trim().to_string();
let stderr = String::from_utf8_lossy(&output.stderr).trim().to_string();
if !output.status.success() {
error!("[Error] {}", stderr);
anyhow::bail!("ADB command failed: {}", stderr);
}
info!("[Recv] {}", stdout);
Ok(stdout)
}
/// 执行 shell 命令
pub fn shell(&self, cmd: &str) -> Result<String> {
self.run_cmd(&["shell", cmd])
}
/// 推送文件到设备
pub fn push(&self, local: &str, remote: &str) -> Result<()> {
self.run_cmd(&["push", local, remote])?;
Ok(())
}
/// 从设备拉取文件
pub fn pull(&self, remote: &str, local: &str) -> Result<()> {
self.run_cmd(&["pull", remote, local])?;
Ok(())
}
/// 重启设备
pub fn reboot(&self) -> Result<()> {
info!("Rebooting device: {}", self.serial);
self.run_cmd(&["reboot"])?;
Ok(())
}
/// 等待设备就绪
pub async fn wait_for_device(&self, timeout_secs: u64) -> Result<()> {
info!("Waiting for device: {}", self.serial);
let start = std::time::Instant::now();
loop {
if start.elapsed().as_secs() > timeout_secs {
anyhow::bail!("Timeout waiting for device");
}
match self.run_cmd(&["get-state"]) {
Ok(state) if state.contains("device") => {
info!("Device is ready");
return Ok(());
}
_ => {
sleep(Duration::from_secs(2)).await;
}
}
}
}
/// 获取设备状态
pub fn get_state(&self) -> Result<String> {
self.run_cmd(&["get-state"])
}
/// 检查设备是否在线
pub fn is_online(&self) -> bool {
self.get_state()
.map(|s| s.contains("device"))
.unwrap_or(false)
}
/// Root 设备
pub fn root(&self) -> Result<()> {
self.run_cmd(&["root"])?;
Ok(())
}
/// Remount 文件系统
pub fn remount(&self) -> Result<()> {
self.run_cmd(&["remount"])?;
Ok(())
}
/// 获取设备属性
pub fn get_prop(&self, key: &str) -> Result<String> {
self.shell(&format!("getprop {}", key))
}
/// 安装 APK
pub fn install(&self, apk_path: &str) -> Result<()> {
self.run_cmd(&["install", apk_path])?;
Ok(())
}
/// 卸载应用
pub fn uninstall(&self, package: &str) -> Result<()> {
self.run_cmd(&["uninstall", package])?;
Ok(())
}
/// 启动 Activity
pub fn start_activity(&self, component: &str) -> Result<()> {
self.shell(&format!("am start -n {}", component))?;
Ok(())
}
/// 强制停止应用
pub fn force_stop(&self, package: &str) -> Result<()> {
self.shell(&format!("am force-stop {}", package))?;
Ok(())
}
/// 获取设备序列号
pub fn serial(&self) -> &str {
&self.serial
}
}
/// 获取所有连接的设备
pub fn get_devices() -> Result<Vec<String>> {
let output = Command::new("adb")
.arg("devices")
.output()
.context("Failed to execute adb devices")?;
let stdout = String::from_utf8_lossy(&output.stdout);
let devices: Vec<String> = stdout
.lines()
.skip(1) // 跳过 "List of devices attached"
.filter_map(|line| {
let parts: Vec<&str> = line.split_whitespace().collect();
if parts.len() >= 2 && parts[1] == "device" {
Some(parts[0].to_string())
} else {
None
}
})
.collect();
Ok(devices)
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_adb_creation() {
let adb = Adb::new("test_device");
assert_eq!(adb.serial(), "test_device");
}
#[tokio::test]
async fn test_get_devices() {
// 这个测试需要实际的 ADB 设备
match get_devices() {
Ok(devices) => {
println!("Found devices: {:?}", devices);
}
Err(e) => {
println!("No devices found or ADB not available: {}", e);
}
}
}
}
🧪 第 4 步:实现测试基类
4.1 创建 test 模块
创建 src/test/mod.rs:
pub mod base;
pub mod runner;
pub use base::{TestCase, TestResult};
pub use runner::TestRunner;
4.2 实现测试基类
创建 src/test/base.rs:
use anyhow::Result;
use async_trait::async_trait;
use serde::{Deserialize, Serialize};
/// 测试结果
#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
pub enum TestResult {
Pass,
Fail,
Unknown,
}
impl std::fmt::Display for TestResult {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
match self {
TestResult::Pass => write!(f, "Pass"),
TestResult::Fail => write!(f, "Fail"),
TestResult::Unknown => write!(f, "Unknown"),
}
}
}
/// 测试用例 trait
#[async_trait]
pub trait TestCase: Send + Sync {
/// 测试用例名称
fn name(&self) -> &str;
/// 测试用例描述
fn description(&self) -> &str {
""
}
/// 设置阶段
async fn setup(&mut self) -> Result<()> {
log::info!("Test setup for {}", self.name());
Ok(())
}
/// 执行阶段
async fn execute(&mut self) -> Result<TestResult>;
/// 清理阶段
async fn cleanup(&mut self) -> Result<()> {
log::info!("Test cleanup for {}", self.name());
Ok(())
}
/// 运行完整测试流程
async fn run(&mut self) -> TestResult {
log::info!("========================================");
log::info!("Running test: {}", self.name());
log::info!("========================================");
// Setup
if let Err(e) = self.setup().await {
log::error!("Setup failed: {}", e);
return TestResult::Fail;
}
// Execute
let result = match self.execute().await {
Ok(r) => {
log::info!("Test {} result: {}", self.name(), r);
r
}
Err(e) => {
log::error!("Execute failed: {}", e);
TestResult::Fail
}
};
// Cleanup
if let Err(e) = self.cleanup().await {
log::error!("Cleanup failed: {}", e);
}
log::info!("========================================");
log::info!("{} test result: {}", self.name(), result);
log::info!("========================================");
result
}
}
/// 测试用例信息
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct TestInfo {
pub name: String,
pub description: String,
pub result: TestResult,
pub duration_ms: u64,
pub log_path: String,
}
4.3 实现测试运行器
创建 src/test/runner.rs:
use super::{TestCase, TestResult, TestInfo};
use anyhow::Result;
use std::time::Instant;
/// 测试运行器
pub struct TestRunner {
tests: Vec<Box<dyn TestCase>>,
results: Vec<TestInfo>,
}
impl TestRunner {
/// 创建新的测试运行器
pub fn new() -> Self {
Self {
tests: Vec::new(),
results: Vec::new(),
}
}
/// 添加测试用例
pub fn add_test(&mut self, test: Box<dyn TestCase>) {
self.tests.push(test);
}
/// 顺序运行所有测试
pub async fn run_sequential(&mut self) -> Result<Vec<TestInfo>> {
log::info!("Running {} tests sequentially", self.tests.len());
for test in &mut self.tests {
let start = Instant::now();
let name = test.name().to_string();
let description = test.description().to_string();
let result = test.run().await;
let duration_ms = start.elapsed().as_millis() as u64;
let info = TestInfo {
name: name.clone(),
description,
result,
duration_ms,
log_path: format!("logs/{}", name),
};
self.results.push(info);
}
Ok(self.results.clone())
}
/// 并发运行所有测试
pub async fn run_concurrent(&mut self) -> Result<Vec<TestInfo>> {
use futures::future::join_all;
use std::sync::Arc;
use tokio::sync::Mutex;
log::info!("Running {} tests concurrently", self.tests.len());
let tests = std::mem::take(&mut self.tests);
let tests: Vec<_> = tests
.into_iter()
.map(|test| Arc::new(Mutex::new(test)))
.collect();
let futures: Vec<_> = tests
.iter()
.map(|test| {
let test = Arc::clone(test);
async move {
let start = Instant::now();
let mut test = test.lock().await;
let name = test.name().to_string();
let description = test.description().to_string();
let result = test.run().await;
let duration_ms = start.elapsed().as_millis() as u64;
TestInfo {
name: name.clone(),
description,
result,
duration_ms,
log_path: format!("logs/{}", name),
}
}
})
.collect();
let results = join_all(futures).await;
self.results = results;
Ok(self.results.clone())
}
/// 获取测试结果
pub fn results(&self) -> &[TestInfo] {
&self.results
}
/// 获取统计信息
pub fn statistics(&self) -> TestStatistics {
let total = self.results.len();
let passed = self.results.iter().filter(|r| r.result == TestResult::Pass).count();
let failed = self.results.iter().filter(|r| r.result == TestResult::Fail).count();
let unknown = self.results.iter().filter(|r| r.result == TestResult::Unknown).count();
let pass_rate = if total > 0 {
(passed as f64 / total as f64) * 100.0
} else {
0.0
};
TestStatistics {
total,
passed,
failed,
unknown,
pass_rate,
}
}
}
impl Default for TestRunner {
fn default() -> Self {
Self::new()
}
}
/// 测试统计信息
#[derive(Debug, Clone)]
pub struct TestStatistics {
pub total: usize,
pub passed: usize,
pub failed: usize,
pub unknown: usize,
pub pass_rate: f64,
}
impl std::fmt::Display for TestStatistics {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
writeln!(f, "Test Statistics:")?;
writeln!(f, " Total: {}", self.total)?;
writeln!(f, " Passed: {}", self.passed)?;
writeln!(f, " Failed: {}", self.failed)?;
writeln!(f, " Unknown: {}", self.unknown)?;
writeln!(f, " Pass Rate: {:.1}%", self.pass_rate)?;
Ok(())
}
}
🎯 第 5 步:实现命令行接口
创建 src/cli.rs:
use clap::Parser;
/// IoT 自动化测试框架
#[derive(Parser, Debug)]
#[command(name = "iot-test")]
#[command(version = "2.0.0")]
#[command(about = "IoT Automation Testing Framework in Rust", long_about = None)]
pub struct Cli {
/// 测试配置文件 (XML)
#[arg(short, long)]
pub config: Option<String>,
/// 设备序列号 (ADB ID 或 IP 地址)
#[arg(short, long)]
pub serial_number: Option<String>,
/// Meta 路径(固件升级)
#[arg(short, long)]
pub meta: Option<String>,
/// Apps 路径(固件升级)
#[arg(short, long)]
pub apps: Option<String>,
/// TAC 端口
#[arg(short, long)]
pub tac_port: Option<String>,
/// 设备 ID (来自 Axiom)
#[arg(short, long)]
pub device_id: Option<String>,
/// 启用 ADB over WiFi
#[arg(short = 'w', long)]
pub adb_over_wifi: bool,
/// 执行类型 (adb 或 ssh)
#[arg(short, long, default_value = "adb")]
pub exec_type: String,
/// 调试 UART 端口 (例如: COM7)
#[arg(long)]
pub debug_uart: Option<String>,
/// 日志级别
#[arg(short, long, default_value = "info")]
pub log_level: String,
/// 列出所有连接的设备
#[arg(long)]
pub list_devices: bool,
}
📚 第 6 步:实现库入口
创建 src/lib.rs:
pub mod cli;
pub mod device;
pub mod test;
pub use cli::Cli;
pub use device::Adb;
pub use test::{TestCase, TestResult, TestRunner};
🎮 第 7 步:实现主程序
创建 src/main.rs:
use anyhow::Result;
use clap::Parser;
use iot_automation_rust::{Cli, Adb};
use log::info;
#[tokio::main]
async fn main() -> Result<()> {
let cli = Cli::parse();
// 初始化日志
env_logger::Builder::from_env(
env_logger::Env::default()
.default_filter_or(&cli.log_level)
)
.format_timestamp_millis()
.init();
info!("🦀 IoT Automation Framework (Rust) v2.0.0");
info!("========================================");
// 列出设备
if cli.list_devices {
list_devices()?;
return Ok(());
}
// 获取设备序列号
let serial = match cli.serial_number {
Some(s) => s,
None => {
let devices = iot_automation_rust::device::adb::get_devices()?;
if devices.is_empty() {
anyhow::bail!("No devices found. Please connect a device or specify --serial-number");
}
info!("Using device: {}", devices[0]);
devices[0].clone()
}
};
// 创建 ADB 实例
let adb = Adb::new(serial);
// 检查设备状态
if !adb.is_online() {
anyhow::bail!("Device is not online");
}
info!("Device: {}", adb.serial());
info!("State: {}", adb.get_state()?);
// 获取设备信息
let model = adb.get_prop("ro.product.model")?;
let android_version = adb.get_prop("ro.build.version.release")?;
info!("Model: {}", model);
info!("Android Version: {}", android_version);
info!("========================================");
info!("Ready to run tests!");
Ok(())
}
fn list_devices() -> Result<()> {
let devices = iot_automation_rust::device::adb::get_devices()?;
if devices.is_empty() {
println!("No devices found");
} else {
println!("Connected devices:");
for (i, device) in devices.iter().enumerate() {
println!(" {}. {}", i + 1, device);
}
}
Ok(())
}
✅ 第 8 步:测试运行
8.1 编译项目
cargo build
8.2 运行程序
# 列出设备
cargo run -- --list-devices
# 指定设备运行
cargo run -- --serial-number 6a2ec220
# 查看帮助
cargo run -- --help
8.3 运行测试
cargo test
📊 第 9 步:创建示例测试用例
创建 examples/simple_reboot.rs:
use anyhow::Result;
use async_trait::async_trait;
use iot_automation_rust::{Adb, TestCase, TestResult, TestRunner};
use log::info;
/// 简单的 Reboot 测试
struct SimpleRebootTest {
adb: Adb,
}
impl SimpleRebootTest {
fn new(serial: String) -> Self {
Self {
adb: Adb::new(serial),
}
}
}
#[async_trait]
impl TestCase for SimpleRebootTest {
fn name(&self) -> &str {
"Simple Reboot Test"
}
fn description(&self) -> &str {
"Test device reboot functionality"
}
async fn setup(&mut self) -> Result<()> {
info!("Setting up reboot test");
Ok(())
}
async fn execute(&mut self) -> Result<TestResult> {
info!("Executing reboot test");
// 重启设备
self.adb.reboot()?;
// 等待设备就绪
self.adb.wait_for_device(60).await?;
// 检查设备状态
let uptime = self.adb.shell("uptime")?;
info!("Device uptime: {}", uptime);
Ok(TestResult::Pass)
}
async fn cleanup(&mut self) -> Result<()> {
info!("Cleaning up reboot test");
Ok(())
}
}
#[tokio::main]
async fn main() -> Result<()> {
env_logger::init();
// 获取设备
let devices = iot_automation_rust::device::adb::get_devices()?;
if devices.is_empty() {
anyhow::bail!("No devices found");
}
let serial = devices[0].clone();
info!("Using device: {}", serial);
// 创建测试运行器
let mut runner = TestRunner::new();
// 添加测试
runner.add_test(Box::new(SimpleRebootTest::new(serial)));
// 运行测试
let results = runner.run_sequential().await?;
// 输出结果
println!("\n{}", runner.statistics());
for result in results {
println!(" {} - {} ({} ms)",
result.name,
result.result,
result.duration_ms
);
}
Ok(())
}
运行示例:
cargo run --example simple_reboot
🎓 第 10 步:学习资源
Rust 学习资源
-
官方文档
- The Rust Book: https://doc.rust-lang.org/book/
- Rust by Example: https://doc.rust-lang.org/rust-by-example/
-
中文资源
- Rust 程序设计语言(中文版): https://kaisery.github.io/trpl-zh-cn/
- Rust 语言圣经: https://course.rs/
-
视频教程
- YouTube: “Rust Programming Tutorial”
- B站: “Rust 编程语言入门教程”
下一步学习
- 所有权和借用 - Rust 最重要的概念
- 错误处理 - Result 和 Option
- 异步编程 - async/await 和 tokio
- trait 和泛型 - 代码复用
- 生命周期 - 引用的作用域
📝 总结
已完成的功能
✅ 项目结构搭建 ✅ ADB 核心功能封装 ✅ 测试基类和运行器 ✅ 命令行接口 ✅ 日志系统 ✅ 示例测试用例
下一步计划
📋 Week 2-3:
- 配置文件解析 (XML/JSON)
- 更多测试用例 (Camera, Audio)
- 日志收集功能
- 报告生成
运行检查清单
# 1. 检查 Rust 安装
rustc --version
cargo --version
# 2. 创建项目
cargo new iot-automation-rust
cd iot-automation-rust
# 3. 添加依赖到 Cargo.toml
# 4. 创建模块文件
# 5. 编译
cargo build
# 6. 运行
cargo run -- --list-devices
# 7. 测试
cargo test
# 8. 运行示例
cargo run --example simple_reboot
🤝 需要帮助?
如果遇到问题,可以:
- 查看编译错误信息
- 使用
cargo check快速检查代码 - 使用
cargo clippy获取代码建议 - 查看 Rust 文档
- 问我!😊
准备好开始了吗?让我知道你的进度!🚀