《Rust编程入门》16.2 编写与运行单元测试

在 Rust 中,编写和运行单元测试是确保代码质量的重要步骤。单元测试用于验证程序中各个功能单元(函数或方法)是否按照预期执行。Rust 提供了非常简洁且强大的测试框架,可以帮助开发者高效地编写和运行测试。

16.2 编写与运行单元测试

在 Rust 中,编写和运行单元测试是确保代码质量的重要步骤。单元测试用于验证程序中各个功能单元(函数或方法)是否按照预期执行。Rust 提供了非常简洁且强大的测试框架,可以帮助开发者高效地编写和运行测试。

1. 什么是单元测试?

单元测试是指对程序中最小的功能单元进行独立验证的过程。在 Rust 中,单元测试通常是通过对函数进行测试,验证该函数的输出是否符合预期。每个测试函数都是独立的,它们可以互相隔离,不会共享状态。

2. 如何编写单元测试

2.1 使用 #[test] 注解

Rust 提供了一个 #[test] 注解,用于标记单元测试函数。当你使用 cargo test 命令时,Rust 会自动发现并执行这些测试函数。

下面是一个简单的单元测试示例:

// lib.rs
pub fn add(a: i32, b: i32) -> i32 {
    a + b
}

#[cfg(test)]
mod tests {
    use super::*;  // 引入外部的函数

    // 这是一个单元测试
    #[test]
    fn test_add() {
        assert_eq!(add(2, 3), 5);  // 验证 add(2, 3) 是否返回 5
    }

    #[test]
    fn test_add_negative() {
        assert_eq!(add(-2, -3), -5);  // 验证 add(-2, -3) 是否返回 -5
    }
}
  • #[test] 注解表示 test_addtest_add_negative 是测试函数。
  • assert_eq! 宏用于验证函数返回值与期望值是否相等。如果不相等,测试将失败。

2.2 测试函数的命名

测试函数的命名通常采用描述性命名方式,表明它们验证的行为或功能。例如:

  • test_add_positive_numbers:验证正数加法
  • test_add_negative_numbers:验证负数加法

这种命名约定帮助开发者快速理解测试的目的。

2.3 使用 assert! 和其他断言宏

Rust 提供了一些断言宏来验证测试结果。除了 assert_eq! 之外,常用的断言宏还包括:

  • assert!:检查某个条件是否为 true
  • assert_ne!:验证两个表达式不相等。
  • assert!(result.is_ok()):验证 Result 类型的值是 Ok
  • assert!(result.is_err()):验证 Result 类型的值是 Err
#[test]
fn test_add_with_assert() {
    let result = add(2, 3);
    assert!(result == 5);  // 验证 result 是否为 5
}

#[test]
fn test_add_with_assert_ne() {
    assert_ne!(add(2, 3), 6);  // 验证 add(2, 3) 的返回值不等于 6
}

3. 运行单元测试

3.1 使用 cargo test 命令

在 Rust 中,运行单元测试的最简单方法是使用 cargo test 命令。此命令会自动编译你的项目并运行所有被 #[test] 注解标记的测试函数。

cargo test

此命令会输出测试的执行结果,例如:

running 2 tests
test tests::test_add ... ok
test tests::test_add_negative ... ok

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

3.2 查看详细的输出

如果你想查看更详细的测试输出信息,可以使用 cargo test -- --nocapture。这会显示测试中 println! 或任何标准输出的内容。

cargo test -- --nocapture

3.3 跳过或只运行特定测试

有时我们只想运行某个特定的测试,可以使用 cargo test 命令后跟测试函数的名称,Rust 会只执行该测试。

cargo test test_add

此命令将只运行名为 test_add 的测试。

此外,你还可以使用 #[ignore] 属性来标记某些测试为“被忽略”。这样,默认情况下它们不会运行,除非你显式指定:

#[test]
#[ignore]
fn expensive_test() {
    // 这个测试不会默认运行
}

运行被忽略的测试:

cargo test -- --ignored

4. 错误处理和 Result 测试

在 Rust 中,错误处理通常使用 Result 类型(即 Result<T, E>)来传递和捕获错误。你可以使用 assert!(result.is_ok())assert!(result.is_err()) 来验证 Result 类型的返回值。

例如,假设你有一个可能返回错误的函数:

pub fn divide(a: i32, b: i32) -> Result<i32, String> {
    if b == 0 {
        Err(String::from("Cannot divide by zero"))
    } else {
        Ok(a / b)
    }
}

编写单元测试来验证 Result 类型的行为:

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

    #[test]
    fn test_divide_by_zero() {
        let result = divide(2, 0);
        assert!(result.is_err());  // 预期结果是错误
        assert_eq!(result.unwrap_err(), "Cannot divide by zero");
    }

    #[test]
    fn test_divide_success() {
        let result = divide(6, 2);
        assert!(result.is_ok());  // 预期结果是成功
        assert_eq!(result.unwrap(), 3);
    }
}

5. 总结

编写单元测试是 Rust 开发流程的重要一环,它能帮助开发者及时发现和解决潜在的错误和问题。Rust 提供的测试框架非常简洁,允许开发者通过 #[test] 注解编写独立的测试函数,并通过 cargo test 轻松运行测试。

通过合理的使用断言宏、测试命名和测试结构,开发者可以编写高效、清晰的单元测试,进一步提高代码的质量和可靠性。在下一节中,我们将探讨如何编写和运行集成测试,以便更全面地验证代码的功能。

继续阅读

探索更多技术文章

浏览归档,发现更多关于系统设计、工具链和工程实践的内容。

全部文章 返回首页