实现代码
- 定义模块和函数:
// src/lib.rs
pub fn fetch_data() -> String {
// 实际这里会调用外部API,现在简单返回一个字符串
"real data from api".to_string()
}
pub fn process_data() -> String {
let data = fetch_data();
// 简单处理数据,比如将字符串转大写
data.to_uppercase()
}
- 使用Mock技术进行单元测试:
// src/lib.rs
// 引入依赖
#[cfg(test)]
mod tests {
use super::*;
use mockall::mock;
mock! {
DataFetcher {
fn fetch_data(&self) -> String;
}
}
#[test]
fn test_process_data() {
let mut mock_fetcher = MockDataFetcher::new();
mock_fetcher.expect_fetch_data().returning(|| "mocked data".to_string());
let result = {
let old_fetch_data = std::mem::replace(
&mut super::fetch_data,
move || mock_fetcher.fetch_data(),
);
let res = process_data();
std::mem::replace(&mut super::fetch_data, old_fetch_data);
res
};
assert_eq!(result, "MOCKED DATA");
}
}
Mock和Stub的区别以及适用场景
- 区别:
- Stub:Stub是为了提供固定的响应,通常是简单地返回预设的数据,而不关心函数被调用的次数、参数等细节。Stub主要是为了让被测代码能够顺利运行,提供必要的输入数据。例如,在上述代码中,如果使用Stub,可能只是简单地替换
fetch_data
函数返回一个固定字符串,而不验证调用情况。
- Mock:Mock不仅返回预设的数据,还可以验证函数是否被调用、调用次数、调用时的参数等。Mock更侧重于验证被测代码与依赖之间的交互是否正确。在上述测试代码中,
MockDataFetcher
不仅返回了预设字符串,还可以通过expect_fetch_data
等方法验证fetch_data
是否按预期被调用。
- 适用场景:
- Stub适用场景:当我们只关心被测函数的输出是否正确,而不关心它与依赖的交互细节时,使用Stub。例如,一个函数只是简单地读取某个数据源的数据并进行计算,我们只需要提供固定数据来测试计算逻辑,这时Stub就足够了。
- Mock适用场景:当我们需要验证被测函数与依赖之间的交互行为是否符合预期时,使用Mock。比如,一个函数需要根据不同条件调用不同的外部API,我们需要验证在不同条件下API是否被正确调用,此时Mock更合适。