📋 前置准备

1. 安装 Rust

Windows 安装步骤:

  1. 下载 Rust 安装器

    • 访问:https://rustup.rs/
    • 或直接下载:https://win.rustup.rs/x86_64
  2. 运行安装器

    # 下载后运行 rustup-init.exe
    # 选择默认安装选项(按 1 然后回车)
    
  3. 验证安装

    # 重新打开 PowerShell
    rustc --version
    cargo --version
    
  4. 配置国内镜像(可选,加速下载)

    # 创建配置文件
    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 学习资源

  1. 官方文档

  2. 中文资源

  3. 视频教程

    • YouTube: “Rust Programming Tutorial”
    • B站: “Rust 编程语言入门教程”

下一步学习

  1. 所有权和借用 - Rust 最重要的概念
  2. 错误处理 - Result 和 Option
  3. 异步编程 - async/await 和 tokio
  4. trait 和泛型 - 代码复用
  5. 生命周期 - 引用的作用域

📝 总结

已完成的功能

✅ 项目结构搭建 ✅ 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

🤝 需要帮助?

如果遇到问题,可以:

  1. 查看编译错误信息
  2. 使用 cargo check 快速检查代码
  3. 使用 cargo clippy 获取代码建议
  4. 查看 Rust 文档
  5. 问我!😊

准备好开始了吗?让我知道你的进度!🚀