Keyboard shortcuts

Press or to navigate between chapters

Press S or / to search in the book

Press ? to show this help

Press Esc to hide this help

11.10 集成测试

11.10.1. 什么是集成测试

在Rust里,集成测试完全位于被测试库的外部。集成测试调用库的方式和其他代码一样,这也意味着它们只能调用公共API。

集成测试的目的是验证库的多个部分能否正确地一起工作。 这一点有别于单元测试;单元测试比较小也比较专注。单元测试单独测试一个模块,还可以测试私有接口。

有时单独运行没问题的代码,合在一起使用时仍可能出问题。集成测试正是为了尽早发现并解决这类问题而存在的。因此,集成测试的覆盖率很重要

11.10.2. tests目录

要创建集成测试,首先创建tests目录。

这个目录与src并列,cargo会自动在那里寻找集成测试文件。你可以在这个目录下创建任意数量的集成测试文件。编译时,cargo会把每个测试文件当作一个单独的包,也就是一个单独的crate

下面演示如何创建集成测试文件:

1. 创建tests目录

src旁边创建一个名为tests的文件夹: 文件资源管理器高亮显示与 src 并列的顶层 tests 文件夹

2. 创建测试文件

tests下创建一个.rs测试文件,并给它取名。这里我用的是integration_test.rs文件资源管理器显示选中的 tests/integration_test.rs

3. 把测试代码移到测试文件里

11.9. 单元测试 的代码为例(lib.rs):

#![allow(unused)]
fn main() {
pub fn add_two(a: usize) -> usize {
    internal_adder(a, 2)
}

fn internal_adder(left: usize, right: usize) -> usize {
    left + right
}

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

    #[test]
    fn internal() {
        let result = internal_adder(2, 2);
        assert_eq!(result, 4);
    }
}
}

因为每个集成测试文件都是一个单独的crate,所以这个文件(integration_test.rs)如果想测试那个crate,就必须先把lib.rs的内容导入作用域。

在这个例子中,由于我把项目命名为RustStudy,所以包名也是RustStudy。如果不清楚,可以到Cargo.toml里看name字段。在这个例子中,可以写use RustStudy;来导入;如果想导入某个具体函数也可以。

导入之后可以直接写测试函数。不需要写#[cfg(test)],因为tests目录下的代码只有在执行cargo test时才会运行。只需要给测试函数标注#[test]即可。

完整代码如下(integration_test.rs):

#![allow(unused)]
fn main() {
use RustStudy;

#[test]
fn it_adds_two() {
    let result = RustStudy::add_two(2);
    assert_eq!(result, 4);
}
}

输出:

$ cargo test
   Compiling RustStudy v0.1.0 (file:///projects/RustStudy)
    Finished `test` profile [unoptimized + debuginfo] target(s) in 0.15s
     Running unittests src/lib.rs (target/debug/deps/RustStudy-48a2c23cb22e1ddc)

running 1 test
test tests::internal ... ok

test result: ok. 1 passed; 0 failed; 0 ignored; 0 measured; 0 filtered out; finished in 0.00s

     Running tests/integration_test.rs (target/debug/deps/integration_test-e60608d740742c0c)

running 1 test
test it_adds_two ... ok

test result: ok. 1 passed; 0 failed; 0 ignored; 0 measured; 0 filtered out; finished in 0.00s

   Doc-tests RustStudy

running 0 tests

test result: ok. 0 passed; 0 failed; 0 ignored; 0 measured; 0 filtered out; finished in 0.00s

可以看到,这个输出显示运行了两个测试:一个来自lib.rs(单元测试),一个来自integration_test.rs(集成测试)。

11.10.3. 运行指定的集成测试

要运行某个特定的集成测试函数,使用cargo test <test_name>。要运行某个测试文件中的所有测试函数,使用cargo test --test <file_name>

看个例子: 文件资源管理器显示 tests 下的两个测试文件:another_test.rs 与 integration_test.rs

现在tests下有两个文件。如果我只想运行integration_test.rs里的测试函数,可以运行:

cargo test --test integration_test

11.10.4. 集成测试中的子模块

因为tests下的每个文件都被编译成单独的crate,所以这些文件彼此不共享行为,这与src下的文件不同。

那么,如果想把测试函数中重复的逻辑提取到一个helper函数里以避免重复,该怎么写呢?

例如,我在tests下创建了common.rs来存放helper函数: IDE 中打开 tests/common.rs,内含公共 setup 辅助函数

试着运行测试:

$ cargo test
   Compiling RustStudy v0.1.0 (file:///projects/RustStudy)
    Finished `test` profile [unoptimized + debuginfo] target(s) in 0.13s
     Running unittests src/lib.rs (target/debug/deps/RustStudy-48a2c23cb22e1ddc)

running 1 test
test tests::internal ... ok

test result: ok. 1 passed; 0 failed; 0 ignored; 0 measured; 0 filtered out; finished in 0.00s

     Running tests/common.rs (target/debug/deps/common-5306c3915df25199)

running 0 tests

test result: ok. 0 passed; 0 failed; 0 ignored; 0 measured; 0 filtered out; finished in 0.00s

     Running tests/integration_test.rs (target/debug/deps/integration_test-e60608d740742c0c)

running 1 test
test it_adds_two ... ok

test result: ok. 1 passed; 0 failed; 0 ignored; 0 measured; 0 filtered out; finished in 0.00s

   Doc-tests RustStudy

running 0 tests

test result: ok. 0 passed; 0 failed; 0 ignored; 0 measured; 0 filtered out; finished in 0.00s

可以看到,common.rs出现在了测试输出中。但因为common.rs只是用来存放helper函数的,它本身不需要被测试。这种写法是错误的。

正确做法是在tests下创建common目录,在里面放一个mod.rs文件,并把helper函数移过去,然后删除原来的common.rsIDE 中打开 tests/common/mod.rs,展示共享的 setup 辅助代码

这是Rust能理解的另一种命名约定。Rust不会把common模块当作集成测试文件,测试输出中也不会再出现common,因为tests下的子目录不会被编译成单独的crate。

如果要在集成测试文件中使用那里的内容,只需在文件开头写mod <folder_name>;。在这个例子中就是mod common;。使用时写common::your_function。在这个例子中就是common::setup()

11.10.5. 针对二进制crate的集成测试

如果项目是二进制crate,也就是只有src/main.rs而没有src/lib.rs,就不能在tests下创建集成测试;即使创建了,也无法把main.rs里的函数导入作用域。因为只有库crate(也就是有lib.rs的)才能把函数暴露给其他crate使用。

二进制crate意味着独立运行。因此,Rust的二进制项目通常会把这些逻辑放在lib.rs里,而在main.rs里只保留简单调用。这样项目就会被视为库crate,就可以用集成测试来检查代码。