程序代码的测试是开发产业中最古老和最重要的过程之一。然而,在许多语言中,测试库是分开提供的,测试与主代码的集成可能不够透明或不方便。在Rust的早期版本中,该语言就是针对内置明确的模块测试进行设计的。
在许多传统语言中,测试是单独放置或需要配置特定的框架和构建工具。这使得协作开发变得复杂,增加了主逻辑与测试之间不同步的风险,也使得进行集成和模块测试变得更加困难。
Rust在语言层面集成了测试系统:模块#[cfg(test)]、指令#[test]和工具cargo test允许在源代码中创建、运行和控制测试。这确保了代码与验证其的测试之间的紧密关系,保证了测试的简单启动和CI/CD过程的自动化。
代码示例:
// 测试由`cargo test`自动编译和运行 #[cfg(test)] mod tests { use super::*; #[test] fn it_adds_two() { assert_eq!(2 + 2, 4); } }
主要特点:
cargo test)可以运行所有发现的测试。#[cfg(test)]可以防止测试包含在发行构建中。可以在测试中使用不稳定或私有函数吗?如何做到?
可以。在声明为#[cfg(test)]的测试模块内部,测试可以访问当前模块的私有API。这意味着你可以直接测试实现细节。然而,从其他模块只能通过公共接口访问。
示例:
fn private_internal(x: i32) -> i32 { x + 1 } #[cfg(test)] mod tests { use super::*; #[test] fn test_private() { assert_eq!(private_internal(41), 42); } }
不同模块中的同名测试会有冲突吗?
不会,测试名称只在其模块内可见。不同模块的测试可以同名,因为编译器通过完整路径(命名空间)来区分它们。
示例:
mod a { #[cfg(test)] mod tests { #[test] fn basic() {} } } mod b { #[cfg(test)] mod tests { #[test] fn basic() {} } }
可以运行单个测试或测试集的某一部分吗?
可以,使用测试名称过滤:cargo test 测试名称。这对于调试大型测试集非常方便。
示例:
cargo test only_this_test
开发者将测试放在一个没有权限访问私有函数并且目录结构不同的单独项目中。由于主库的更改,测试很快就落后并失去时效性。
优点: 可以隔离测试,避免意外将测试模块包含在发行构建中。
缺点: 失去同步,无法测试内部细节,维护负担重且测试存在失效风险。
测试直接写在与主逻辑相同的模块中,小的原子测试用例测试私有与公共API。新手通过测试迅速理解模块的工作。
优点: 最大化保持同步,透明性,快速的本地测试,适用于测试驱动开发。
缺点: 源文件中的代码行数增加,潜在的复杂性增长在大量测试时。