跳转至

测试策略和详细说明

本项目遵循 Rust 官方测试最佳实践,分为单元测试集成测试

目录


测试组织规范

单元测试 (Unit Tests)

  • 位置: 保留在 src/ 目录中,与被测试代码在同一文件
  • 标记: 使用 #[cfg(test)] 模块
  • 权限: 可以测试私有函数和内部实现
  • 目的: 快速、隔离地测试单个功能单元

示例结构:

// src/common/auth.rs
pub struct CookieAuth { /* ... */ }

#[cfg(test)]
mod tests {
    use super::*;

    #[test]
    fn test_cookie_auth_parse() {
        // 单元测试代码
    }
}

集成测试 (Integration Tests)

  • 位置: 项目根目录的 tests/ 目录
  • 编译: 每个文件被编译为独立的 crate
  • 权限: 只能访问公共 API
  • 目的: 测试多个模块的协作和真实使用场景

目录结构:

项目根目录/
├── src/
│   ├── lib.rs              # 可保留简单的烟雾测试
│   ├── common/
│   │   └── auth.rs         # ✅ 保留单元测试
│   └── dm/
│       └── client.rs       # ✅ 保留单元测试
└── tests/
    ├── integration_dm.rs   # DM 功能集成测试
    ├── integration_auth.rs # 认证流程集成测试
    └── common/
        └── mod.rs          # 测试辅助函数(不会被当作测试文件)


单元测试

运行单元测试

# 运行所有单元测试
cargo test

# 运行特定模块的测试
cargo test upload::types::tests
cargo test upload::client::tests

# 带日志输出的测试
RUST_LOG=debug cargo test -- --nocapture

单元测试覆盖

src/upload/types.rs - 9 个测试

测试类型定义、序列化、Debug 格式化:

  • test_media_category_as_str: 测试 MediaCategory 枚举转字符串
  • test_media_category_clone: 测试 Clone trait
  • test_upload_result_success: 测试成功上传结果
  • test_upload_result_failure: 测试失败上传结果
  • test_upload_result_clone: 测试 Clone trait
  • test_upload_init_response_deserialization: 测试 JSON 反序列化
  • test_upload_init_response_serialization: 测试 JSON 序列化
  • test_upload_result_debug_format: 测试 Debug 格式化
  • test_media_category_all_variants: 测试所有枚举变体

src/upload/client.rs - 4 个测试

测试 MD5 计算、数据处理:

  • test_md5_known_values: 测试已知 MD5 值
  • test_md5_format_length: 测试 MD5 输出格式
  • test_image_size_calculation: 测试图片大小计算
  • test_md5_binary_data: 测试二进制数据 MD5

其他模块

  • src/common/auth.rs: Cookie 解析和认证测试
  • src/dm/client.rs: 私信发送逻辑测试

集成测试

配置集成测试环境

集成测试需要真实的 Twitter cookies。

步骤 1:复制模板文件

cp tests/.twitter_cookies.example.txt tests/.twitter_cookies.txt

步骤 2:编辑文件,填入真实的 cookies

格式: ct0=xxx; auth_token=yyy; twid=u%3D123

步骤 3:运行集成测试

# 运行所有集成测试(需要真实 cookies)
cargo test -- --ignored

# 运行特定的集成测试
cargo test test_upload_tweet_image -- --ignored
cargo test test_upload_dm_image -- --ignored
cargo test test_upload_banner_image -- --ignored

集成测试覆盖

tests/integration_upload.rs - 图片上传完整流程测试

  • test_upload_tweet_image: 推文图片上传(需要真实 cookies)
  • test_upload_dm_image: 私信图片上传(需要真实 cookies)
  • test_upload_banner_image: 横幅图片上传(需要真实 cookies)
  • test_upload_multiple_images: 批量上传测试(需要真实 cookies)
  • test_cookies_validation: Cookies 验证(不需要真实 cookies)
  • test_media_category_enum: 媒体类别枚举测试(不需要真实 cookies)

tests/integration_dm_with_media.rs - 带图片私信完整流程测试

  • test_send_dm_with_image: 上传图片 + 发送带图片私信(需要真实 cookies)
  • test_send_dm_without_image: 发送纯文本私信,验证向后兼容(需要真实 cookies)
  • test_batch_send_dm_with_mixed_media: 批量发送混合私信(部分带图片)(需要真实 cookies)
  • test_batch_send_dm_all_with_images: 批量发送全部带图片(需要真实 cookies)
  • test_media_ids_validation: 参数验证测试(不需要真实 cookies)

测试辅助模块

tests/common/mod.rs - 提供测试辅助函数

// 创建最小的测试 JPEG 图片
pub fn create_test_jpeg() -> Vec<u8>

// 创建测试用 cookies
pub fn create_test_cookies() -> String

// 验证上传成功
pub fn assert_upload_success(result: &UploadResult)

// 验证上传失败
pub fn assert_upload_failure(result: &UploadResult)

测试输出规范

强制要求

  • 禁止使用 println!, print!, eprintln!, eprint! 进行测试输出
  • 必须使用 tracing crate 的日志宏: debug!, info!, warn!, error!

测试日志初始化

单元测试

#[cfg(test)]
mod tests {
    use super::*;

    fn init_test_logging() {
        let _ = tracing_subscriber::fmt()
            .with_test_writer()
            .with_max_level(tracing::Level::DEBUG)
            .try_init();
    }

    #[test]
    fn test_example() {
        init_test_logging();
        info!("开始测试");
        debug!("详细信息: {:?}", data);
    }
}

集成测试

// tests/common/mod.rs
pub fn init_test_logging() {
    let _ = tracing_subscriber::fmt()
        .with_test_writer()
        .with_max_level(tracing::Level::DEBUG)
        .try_init();
}

// tests/integration_upload.rs
#[tokio::test]
async fn test_upload() {
    common::init_test_logging();
    info!("测试开始");
}

测试输出示例

// ✅ 正确
#[test]
fn test_batch_upload() {
    init_test_logging();
    info!("📸 测试图片大小: {} bytes", image_bytes.len());
    info!("🚀 开始批量上传 {} 次...", count);
    info!("✅ 批量上传完成!");
    debug!("详细结果: {:?}", result);
}

// ❌ 错误
#[test]
fn test_batch_upload() {
    println!("📸 测试图片大小: {} bytes", image_bytes.len());  // 禁止
    println!("🚀 开始批量上传 {} 次...", count);              // 禁止
}

代码提交前检查清单

在提交代码前,确保:

测试组织

  • [ ] 单元测试保留在 src/ 对应文件中
  • [ ] 集成测试按模块组织在 tests/<module>/ 目录
  • [ ] 测试辅助函数放在 tests/common/mod.rs

测试输出

  • [ ] 测试中无 println!print!
  • [ ] 所有测试输出使用 tracing 日志宏
  • [ ] 测试日志已正确初始化

测试覆盖

  • [ ] 新功能添加了单元测试
  • [ ] 复杂场景添加了集成测试
  • [ ] 测试覆盖了边界条件和错误处理

测试命名规范

  • 单元测试: test_功能描述 (如 test_cookie_auth_parse)
  • 集成测试: integration_模块名 (如 integration_dm.rs)
  • 使用描述性名称,说明测试意图而非实现细节

测试编写原则

  • 单元测试: 测试边界条件、错误处理、私有实现细节
  • 集成测试: 测试端到端流程、公共 API 使用、模块协作
  • 测试隔离: 每个测试独立运行,互不依赖
  • 快速执行: 单元测试应快速完成,集成测试可选择性运行

禁止的做法

  • ❌ 在 lib.rs 中放置复杂的测试逻辑
  • ❌ 在单元测试中测试跨模块的协作
  • ❌ 在集成测试中依赖私有 API

注意事项

  • 🚫 默认跳过集成测试:集成测试标记为 #[ignore],需要真实 cookies 才能运行
  • 🔐 Cookies 配置:通过 tests/.twitter_cookies.txt 文件传递认证信息
  • 独立运行:单元测试可以独立运行,不依赖外部服务
  • 📊 测试覆盖:单元测试 18 个,集成测试 11 个
  • 🔒 安全性.gitignore 已配置排除 *cookies*.txt,防止意外提交敏感信息

相关文档