Rust语言从入门到精通系列 - 单元测试

2 分钟阅读

单元测试是软件开发过程中的重要环节,用于测试代码的小部分是否正常工作。Rust 语言拥有一个丰富的测试框架,可以轻松编写并运行测试用例。本教程将介绍如何在 Rust 项目中编写单元测试,并提供示例代码。

单元测试

单元测试是软件测试的一个重要部分,它的主要目的是验证代码单元(如函数、模块或类)是否按照预期工作。与手动测试相比,自动化单元测试能够快速、可靠地检测代码问题,避免了大量的手动测试工作。

在 Rust 中,单元测试是通过test属性来实现的。每个符合规范的测试函数都必须使用特殊的宏assert!来验证其结果是否正确。

编写单元测试

以下是一个简单的 Rust 代码示例:

1
2
3
4
5
6
7
fn add(a: i32, b: i32) -> i32 {
    a + b
}

fn main() {
    println!("{}", add(1, 2));
}

这个程序定义了一个名为add的函数,该函数接受两个i32类型的参数并返回它们的和。我们可以使用单元测试来验证这个函数是否正确。

首先,在add函数的上面添加一个#[test]属性,告诉编译器这是一个测试函数:

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

在这个测试函数中,我们使用assert_eq!宏来检查add函数是否返回了我们期望的结果。如果运行测试失败,则会输出错误消息,否则测试通过。

运行单元测试

在 Rust 中,我们使用命令cargo test来运行所有的测试。当我们运行这个命令时,编译器会自动查找所有带有#[test]属性的函数,并且运行他们。

下面是一个运行测试的示例,我们将上述代码保存在src/main.rs文件中:

$ cargo test
   Compiling rust-unit-test v0.1.0 (/path/to/rust-unit-test)
    Finished test [unoptimized + debuginfo] target(s) in 0.29s
     Running target/debug/deps/rust_unit_test-xxxxxxxxxxxxxxxx

running 1 test
test test_add ... ok

test result: ok. 1 passed; 0 failed; 0 ignored; 0 measured; 0 filtered out

从输出结果可以看出,我们的测试已经通过了。

单元测试进阶

除了使用assert!宏来验证测试结果之外,Rust 还提供了一些其他的工具来帮助我们编写更完整的测试。

使用assert_eq!

assert_eq!宏可以用来比较两个值是否相等。它还可以比较各种类型的值,包括字符串和浮点数,而不只是数字类型。

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

使用assert_ne!

assert_ne!宏可以用来比较两个值是否不相等。

1
2
3
4
5
#[test]
fn test_add() {
    assert_ne!(add(1, 2), 4);
    assert_ne!(add(-1, -2), -4);
}

使用panic!断言

有时候,我们需要在测试中引发一个错误(如输入错误的参数等),以测试程序是否能够正确处理这个错误。这时,我们可以使用panic!宏来抛出一个错误。

1
2
3
4
5
6
7
8
9
10
11
12
#[test]
#[should_panic(expected = "attempt to divide by zero")]
fn test_divide_by_zero() {
    divide(10, 0);
}

fn divide(a: i32, b: i32) -> i32 {
    if b == 0 {
        panic!("attempt to divide by zero");
    }
    a / b
}

在这个例子中,我们测试了一个除以零的函数。我们使用should_panic属性指示编译器检测panic!宏是否被正确执行,并且预期的错误信息是否匹配。

使用assert_approx_eq!

当我们需要比较浮点数时,由于舍入误差等原因,直接使用assert_eq!宏并不能得到正确的结果。这时,我们可以使用assert_approx_eq!宏来比较两个浮点数是否近似相等。

1
2
3
4
#[test]
fn test_approx_pi() {
    assert_approx_eq!(22.0 / 7.0, std::f64::consts::PI, 0.01);
}

使用assert!(result.is_ok())验证结果

对于返回值是Result类型的函数,我们可以使用assert!(result.is_ok())宏来验证结果是否成功。

1
2
3
4
5
6
#[test]
fn test_parse_int() {
      let result = "123".parse::<i32>();
    assert!(result.is_ok());
    assert_eq!(result.unwrap(), 123);
}

进阶示例

下面是一个稍微复杂一点的例子,它演示了如何测试一个可能会出错的函数,以及如何使用上面提到的所有宏进行测试。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
#[test]
#[should_panic(expected = "attempt to divide by zero")]
fn test_divide_by_zero() {
    divide(10, 0);
}

#[test]
fn test_add() {
    assert_eq!(add(1, 2), 3);
    assert_eq!(add(-1, -2), -3);
    assert_ne!(add(1, 2), 4);
    assert_ne!(add(-1, -2), -4);
}

#[test]
fn test_approx_pi() {
    assert_approx_eq!(22.0 / 7.0, std::f64::consts::PI, 0.01);
}

#[test]
fn test_parse_int() {
    let result = "123".parse::<i32>();
    assert!(result.is_ok());
    assert_eq!(result.unwrap(), 123);
}

fn add(a: i32, b: i32) -> i32 {
    a + b
}

fn divide(a: i32, b: i32) -> i32 {
    if b == 0 {
        panic!("attempt to divide by zero");
    }
    a / b
}

测试私有函数

Rust 允许在测试模块中测试私有函数。为了做到这一点,我们将测试模块放在包含私有函数的模块之内,并使用 #[cfg(test)] 和 #[test] 属性将测试函数标记为测试。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
mod foo {
    #[cfg(test)]
    mod tests {
        use super::private_function;

        #[test]
        fn test_private_function() {
            assert_eq!(private_function(), 3);
        }
    }

    fn private_function() -> i32 {
        3
    }
}

在上面的示例中,我们定义了一个名为 foo 的模块,并在其中添加了一个私有函数 private_function。在模块内部,我们定义了一个测试模块,并添加了一个测试函数,该函数使用 assert_eq! 断言来验证 private_function 的结果是否正确。

结论

Rust 提供了非常好用的单元测试框架,使得我们能够轻松地编写、运行和调试测试代码。本教程介绍了一些基本的测试宏和示例代码,希望能够对学习和使用 Rust 单元测试有所帮助。

知识共享许可协议

本作品采用 知识共享署名-非商业性使用-相同方式共享 4.0 国际许可协议 进行许可。欢迎转载、使用、重新发布,但务必保留文章署名 TinyZ Zzh (包含链接: https://tinyzzh.github.io ),不得用于商业目的,基于本文修改后的作品务必以相同的许可发布。 如有任何疑问,请 与我联系 (tinyzzh815@gmail.com)

TinyZ Zzh

TinyZ Zzh

专注于高并发服务器、网络游戏相关(Java、PHP、Unity3D、Unreal Engine等)技术,热爱游戏事业, 正在努力实现自我价值当中。

评论

  点击开始评论...