首页 / Rust 入门教程 / 测试组织与运行控制

Rust 入门教程

测试组织与运行控制

本教程共 78 篇 · 第 54 篇 · 更新于 2026-08-08 · 约 10 分钟阅读

RustRust 入门教程测试控制cargo testtest-threadsignoredev-dependencies

本节目标:掌握 cargo test 的各种运行控制手段——区分参数位置、切换并行与串行、查看被吞掉的打印、按需过滤测试和忽略慢测试,并学会引入只在测试时用的依赖。

测试少的时候 cargo test 一把梭就行。等项目变大、测试成百上千,每次全跑不现实,你也受不了。这章讲怎么精细地控制测试的运行。

1-1 两类命令行参数:— 是分界

cargo test 背后做的事和 cargo run 类似:先把测试编译成一个可执行文件,再运行它,跑完删掉。因为它本质是”运行一个程序”,所以能接收两类参数,中间用 -- 隔开:

  • -- 前面的参数,交给 cargo test 命令本身。
  • -- 后面的参数,交给编译出来的那个测试二进制。

查看帮助就能看出来:

cargo test --help        # 看 cargo test 自己的参数
cargo test -- --help     # 看测试二进制支持的参数

初学容易把这两类参数混在一起写,结果要么报错要么参数没生效。记住:-- 之后才是控制”测试怎么跑”的。

1-2 并行还是串行

默认情况下,每个测试函数都开一个独立线程并行跑,主线程等它们全部结束收结果。并行最大的好处是快——几百个测试一起跑,总时间约等于最慢那个,而不是逐个相加。

但并行有副作用:如果多个测试共享同一份外部状态(比如都往同一个文件写数据、都连同一个数据库),谁先谁后就不可控了,A 刚写进去的数据可能被 B 覆盖,导致 A 读到错误内容而失败。这种失败不是代码错,是测试互相踩踏,最让人头疼。

Warning

如果你的测试依赖共享的可变状态(文件、网络端口、全局变量),要么让每个测试用自己独占的资源,要么强制串行。否则你会遇到”单独跑就过、一起跑就挂”的灵异现象。

强制串行,把线程数设成 1:

cargo test -- --test-threads=1

参数在 -- 之后,交给测试二进制,所以是 --test-threads=1。当然也能设成 4、8,但想纯粹串行就必须是 1。

1-3 看被吞掉的打印

测试里写 println! 调试很常见。但默认规则是:测试通过了,它的标准输出不显示。只有失败的测试,它的输出才会被打出来,帮你排查。

看这段:

fn prints_and_returns_10(a: i32) -> i32 {
    println!("收到的值 {}", a);
    10
}

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

    #[test]
    fn this_test_will_pass() {
        let value = prints_and_returns_10(4);
        assert_eq!(10, value);
    }

    #[test]
    fn this_test_will_fail() {
        let value = prints_and_returns_10(8);
        assert_eq!(5, value);
    }
}

运行后,this_test_will_pass 过了,它打印的”收到的值 4”不会出现;只有失败的 this_test_will_fail 的输出会被展示。

想不管过不过都把输出全打印出来,加 --show-output

cargo test -- --show-output
Tip

调试阶段用 --show-output 很香。但提交代码前最好别依赖满屏打印,断言信息本身才该是测试失败时的主要线索。

1-4 按名称过滤测试

只跑一部分测试,最直接的是把函数名当参数传:

cargo test one_hundred

这样只有名字匹配 one_hundred 的测试会跑,其余被过滤掉(输出里用 filtered out 标出数量)。注意不能一次传多个名字,cargo test a b 不会同时跑 a 和 b,它把整串当成一个过滤词。

想按”包含某段字符”过滤,直接传片段即可:

cargo test add        # 跑所有名字里带 add 的
cargo test and        # 跑所有名字里带 and 的

甚至连模块名都能用来过滤:

cargo test tests       # 跑 tests 模块下的全部测试

这个特性在大型项目里很实用:你只改了某个模块,就只跑那个模块的测试,省时省力。

1-5 用 #[ignore] 跳过慢测试

有些测试特别耗时(比如压测、下载大文件),日常不想每次都跑。用 #[ignore] 标注它:

#[test]
fn it_works() {
    assert_eq!(2 + 2, 4);
}

#[test]
#[ignore]
fn expensive_test() {
    // 这里要跑几十秒甚至几分钟
}

cargo test 默认跳过它,输出里显示 ignored。等你确实想验证它,单独跑被忽略的:

cargo test -- --ignored

还能组合使用,比如只跑模块里被忽略的、名字带 run 的:

cargo test run -- --ignored
Note

#[ignore] 是”默认不跑但保留随时能跑”的好办法。和”直接删掉测试”完全不同——删了就没人知道那段逻辑曾被保护过。

1-6 只在测试时用的依赖

和 Node 的 devDependencies 类似,Rust 也能引入只在测试时需要的依赖。比如 pretty_assertions,它能让 assert_eq! 的失败对比更直观(带颜色、标出差异位置)。

Cargo.toml 里这样加:

[dev-dependencies]
pretty_assertions = "1"

然后在测试模块里覆盖引入:

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

    #[test]
    fn test_add() {
        assert_eq!(add(2, 3), 5);
    }
}

use pretty_assertions::assert_eq; 只在 tests 模块里生效。如果在正常业务代码里这么写,编译器会报”这个包不在正常依赖里”。这正好保证了测试专用依赖不会溜进发布版本。

1-7 生成可分享的测试二进制

cargo test 其实会在 target/debug/deps/ 下生成一个测试可执行文件。你可以直接运行它,效果等同于 cargo test

target/debug/deps/adder-92948b65e88960b4

只想编译出这个文件、不想看它立刻跑,用:

cargo test --no-run

这在你想把测试二进制打包分发给别人、或丢到 CI 机器上单独跑时很有用。

Tip

真要上手持续集成(GitHub Actions 自动跑测试),把 cargo test 丢进流水线就行。测试控制这块吃得透,CI 配置写起来毫无压力。

1-8 运行控制的使用心法

测试一旦变多,你会发现”只跑相关测试”是日常最高频的操作。一个实用技巧:给测试函数起有规律的名字,比如 test_add_positivetest_add_overflow,然后用 cargo test add 一次筛出所有和 add 相关的,定位问题快得多。

还有个排查卡死测试的小招:如果 cargo test 一直不结束,可能是某个测试里死循环,或者并行测试互相死锁。这时加 -- --test-threads=1 串行跑,往往能更快暴露是哪个测试卡住。配合 -- --show-output,还能看到卡住前它打印了什么。

Tip

CI 里常见两段式:先 cargo test --workspace 跑全部,再单独 cargo test -- --ignored 跑慢测试。日常本地你几乎只跑过滤后的小部分,省下的时间都是自己的。

最后提醒:测试二进制本身也受 profile 影响。cargo test 用的是 test profile(继承自 dev,优化低、含溢出检查),所以测试跑得比 release 慢但更安全——溢出之类的问题在测试里更容易被抓到。别为了测试快就去改 test profile 的 overflow-checks,那等于主动关掉一道保护。

测试这块到这就讲完了。从下一章起,我们转向 Cargo 的进阶用法。