测试
测试
测试组织
.NET 里通常使用独立测试项目承载测试代码(xUnit/NUnit/MSTest 等均如此), 因此测试代码会位于与被测代码不同的程序集。
Rust 更常见的约定是:单元测试放在被测模块同文件内的 tests 子模块中:
- 被测代码与测试并排放置,阅读与维护更直接
- 测试作为子模块可访问内部实现,无需类似 .NET
[InternalsVisibleTo]的配置
测试子模块一般使用 #[cfg(test)] 标注,只在 cargo test 时参与编译与执行。
具体测试函数使用 #[test] 标注。
集成测试通常放在与 src 同级的 tests 目录中。cargo test 会将该目录下每个文件
视为独立 crate 并执行其中所有 #[test]。因此该目录内模块通常不必再写 #[cfg(test)]。
另见:
运行测试
dotnet test 在 Rust 中最接近的是 cargo test。
cargo test 默认并行执行测试。如需串行执行:
cargo test -- --test-threads=1
更多说明见 “Running Tests in Parallel or Consecutively”。
测试输出
复杂集成测试或端到端测试常需要日志输出。
在 Rust 中可直接使用 println!,行为类似 NUnit 的 Console.WriteLine。
默认情况下 cargo test 会捕获输出;如需显示,可加 --show-output:
cargo test --show-output
更多说明见 “Showing Function Output”。
断言
.NET 的断言 API 随框架不同而变化。以 xUnit 为例:
[Fact]
public void Something_Is_The_Right_Length()
{
var value = "something";
Assert.Equal(9, value.Length);
}Rust 标准库已内置常用断言宏,大多数场景无需额外框架:
示例:
#[test]
fn something_is_the_right_length() {
let value = "something";
assert_eq!(9, value.len());
}标准库本身不提供 xUnit [Theory] 这类数据驱动测试机制。
Mock(模拟)
.NET 常借助 Moq、NSubstitute 等框架模拟依赖。Rust 也有对应 crate,如
mockall。
此外,Rust 还能利用 条件编译 + cfg attribute
做轻量 mock,而不依赖额外框架。
#[cfg(test)] 表示仅在 cargo test 时编译该代码(底层相当于 rustc --test)。
对应地,#[cfg(not(test))] 表示仅在非测试构建中包含该代码。
下面示例演示如何 mock 标准库函数 var_os,让 get_env 在测试构建与普通构建使用不同实现:
// Copyright (c) Microsoft Corporation. All rights reserved.
// Licensed under the MIT license.
/// 读取环境变量并返回其值(若存在)。
/// 若值不是合法 Unicode,则 panic。
pub fn get_env(key: &str) -> Option<String> {
#[cfg(not(test))] // 常规构建
use std::env::var_os; // 使用标准库实现
#[cfg(test)] // 测试构建
use tests::var_os_mock as var_os; // 使用 mock 实现
let val = var_os(key);
val.map(|s| s.to_str()
.unwrap()
.to_owned())
}
#[cfg(test)]
mod tests {
use std::ffi::*;
use super::*;
pub(crate) fn var_os_mock(key: &str) -> Option<OsString> {
match key {
"FOO" => Some("BAR".into()),
_ => None
}
}
#[test]
fn get_env_when_var_undefined_returns_none() {
assert_eq!(None, get_env("???"));
}
#[test]
fn get_env_when_var_defined_returns_some_value() {
assert_eq!(Some("BAR".to_owned()), get_env("FOO"));
}
}代码覆盖率
.NET 在覆盖率分析上有较成熟工具链(Visual Studio 内置、VS Code 插件、coverlet 等)。
Rust 也提供了 内建覆盖率能力。
在编辑器可视化方面,可结合 VS Code 的 Coverage Gutters
与 Tarpaulin(或其他可生成 LCOV 的工具)。
示例命令:
cargo tarpaulin --ignore-tests --out Lcov命令会生成 LCOV 文件。开启 Coverage Gutters: Watch 后,可在编辑器里看到行级覆盖率标记。
注意:LCOV 文件位置很关键。若工程是多包 workspace(见项目结构)并在根目录生成了
--workspace覆盖率文件,插件通常会优先读取它。实际排查单包时,建议在目标包目录单独生成。