Rust测试组织结构

测试分别为两类,单元测试(unit tests)和集成测试(integration tests)。单元测试倾向于小和集中,集成测试则对于库来说是外部的,只测试公共接口。

单元测试🔗

单元测试的目的是在与其他部分隔离的环境中测试每一个单元的代码,单元测试和要测的代码放在src下相同的文件中,每个文件中创建包含测试函数的tests模块,并使用cfg(test)标注模块。

测试模块和#[cfg(test)]🔗

#[cfg(test)]告诉Rust在执行cargo test命令时才编译和运行测试代码,而在执行cargo build命令时则不编译测试代码。而集成测试由于在另外的文件夹,所以不需要#[cfg(test)]标注。

测试私有函数🔗

Rust的私有性规则允许你测试私有函数,考虑下面的例子:

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!(4, result);
    }
}

这里internal_adder函数是私有的,但是在tests模块中可以测试它。

集成测试🔗

集成测试完全是外部的,所以只能调用公有API,为了创建集成测试,需要先创建一个tests目录。

tests目录🔗

编写集成测试要在项目根目录创建一个tests目录,Cargo会将tests目录下的所有文件都当作独立的crate来编译。

创建tests/integration_test.rs文件,在其中编写集成测试:

use adder::add_two;

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

不需要在代码中标注#[cfg(test)],因为tests目录下的所有文件只有在运行cargo test命令时才会被编译。可以使用cargo test --test integration_test来运行integration_test.rs文件中的测试。

集成测试中的子模块🔗

由于tests目录下的所有文件都是独立的crate,所以tests下文件的行为与src中的不一样。

当有一些在多个集成测试文件都会用到的帮助函数,可以把它们都提取到一个通用的模块中,例如可以创建一个tests/common.rs文件,并创建一个叫setup的函数,如下:

pub fn setup() {
    // code to run before each test
}

这个时候运行cargo test,common会出现在测试结果中。

为了不让common会出现在测试结果中,我们将创建tests/common/mod.rs而不是创建tests/common.rs,这样告诉Rust不要将common当作一个集成测试文件。而这样也可以在其他的集成测试文件中调用这个模块:

use adder::add_two;

mod common;

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

二进制crate的集成测试🔗

如果项目是一个二进制crate,只包含一个src/main.rs文件而没有src/lib.rs文件,那么tests目录创建的集成测试就不能导入src/main.rs中的函数,所以许多二进制项目会使用一个简单的src/main.rs文件来调用src/lib.rs中的函数。