如何在Rust中使用mockall库Mock libc的shm_open函数?
在Rust中使用mockall拦截libc函数实现灵活Mock
问题背景
在Rust代码中调用了libc的多个函数(以shm_open为例),希望在测试用例中拦截这些调用并使用自定义Mock函数,实现不同测试用例返回不同结果。目前已实现返回固定值的stub函数,但尝试用mockall库时出现报错(无法直接导入dummy_libc的shm_open),需要正确的实现方式。
原stub实现代码:
use libc::{c_int, c_char, size_t, mode_t}; use libc::{O_RDWR, S_IROTH, S_IRUSR, S_IWOTH, S_IWUSR}; use std::mem; #[cfg(not(test))] use libc::shm_open; #[cfg(test)] fn shm_open( name: *const c_char, oflag: i32, mode: u32 ) -> c_int{ 42 as c_int } const SHM_NAME: *const c_char = b"/ptp\0".as_ptr() as *const c_char; const SHM_SIZE: size_t = mem::size_of::<u32>(); struct Consumer { s_value: c_int, s_data: u32, } trait GuestConsumer{ fn new() -> Self; } impl GuestConsumer for Consumer { fn new() -> Self { let shm_fd = unsafe { shm_open( SHM_NAME, O_RDWR, (S_IRUSR | S_IROTH | S_IWUSR | S_IWOTH) as mode_t, ) }; Self { s_value: shm_fd, s_data: 0 } } } #[cfg(test)] mod tests{ use super::*; #[test] fn test_new(){ let value = Consumer::new(); println!("s_value: {:?}", value.s_value); } } fn main() { println!("Hello, world!"); }
尝试的错误代码(报错点:无法直接导入dummy_libc的shm_open):
use mockall::*; use mockall::predicate::*; #[cfg(not(test))] use libc::shm_open; #[cfg(test)] use dummy_libc::shm_open; // error: not directly importable #[allow(dead_code, non_camel_case_types)] #[cfg(test)] #[automock] pub trait dummy_libc{ fn shm_open( name: *const c_char, oflag: c_int, mode: mode_t )-> c_int; }
正确实现方案
核心思路是通过依赖注入和trait抽象解耦代码与libc函数,让测试时可以替换为mock实例:
- 定义抽象trait封装需要mock的libc函数
- 为生产环境实现该trait,绑定真实的libc调用
- 修改Consumer,使其依赖该trait而非直接调用libc函数
- 在测试中使用mockall生成的Mock对象,设置不同测试用例的返回值
完整修正代码
use libc::{c_int, c_char, mode_t, O_RDWR, S_IROTH, S_IRUSR, S_IWOTH, S_IWUSR}; use std::mem; // 1. 定义抽象trait,封装需要mock的libc函数 #[cfg_attr(test, automock)] pub trait ShmOps { unsafe fn shm_open( name: *const c_char, oflag: c_int, mode: mode_t, ) -> c_int; } // 2. 生产环境实现:调用真实的libc::shm_open #[cfg(not(test))] pub struct RealShmOps; #[cfg(not(test))] impl ShmOps for RealShmOps { unsafe fn shm_open( name: *const c_char, oflag: c_int, mode: mode_t, ) -> c_int { libc::shm_open(name, oflag, mode) } } const SHM_NAME: *const c_char = b"/ptp\0".as_ptr() as *const c_char; const SHM_SIZE: usize = mem::size_of::<u32>(); struct Consumer { s_value: c_int, s_data: u32, } impl Consumer { // 3. 修改构造函数,接收ShmOps实例作为参数(依赖注入) pub fn new<T: ShmOps>(shm_ops: T) -> Self { let shm_fd = unsafe { shm_ops.shm_open( SHM_NAME, O_RDWR, (S_IRUSR | S_IROTH | S_IWUSR | S_IWOTH) as mode_t, ) }; Self { s_value: shm_fd, s_data: 0, } } } // 生产环境的便捷构造函数 #[cfg(not(test))] impl Consumer { pub fn default() -> Self { Self::new(RealShmOps) } } #[cfg(test)] mod tests { use super::*; use mockall::predicate::*; #[test] fn test_new_returns_fd_42() { // 创建Mock实例并设置预期返回值 let mut mock = MockShmOps::new(); mock.expect_shm_open() .with(eq(SHM_NAME), eq(O_RDWR), eq((S_IRUSR | S_IROTH | S_IWUSR | S_IWOTH) as mode_t)) .returning(|_, _, _| 42); let consumer = Consumer::new(mock); assert_eq!(consumer.s_value, 42); } #[test] fn test_new_returns_error() { // 另一个测试用例,返回错误值-1 let mut mock = MockShmOps::new(); mock.expect_shm_open() .returning(|_, _, _| -1); let consumer = Consumer::new(mock); assert_eq!(consumer.s_value, -1); } } fn main() { #[cfg(not(test))] { let _consumer = Consumer::default(); println!("Hello, world!"); } }
关键改动说明
- 抽象trait:
ShmOps封装了shm_open函数,#[cfg_attr(test, automock)]让mockall在测试环境自动生成MockShmOps结构体。 - 依赖注入:
Consumer::new接收ShmOps实例,不再直接硬编码调用libc::shm_open,实现了代码解耦。 - 生产环境绑定:
RealShmOps实现ShmOps,调用真实的libc函数,同时提供default构造函数方便生产代码使用。 - 测试灵活Mock:每个测试用例可以创建独立的
MockShmOps实例,通过expect_shm_open设置不同的参数匹配规则和返回值,满足不同测试场景需求。
内容的提问来源于stack exchange,提问作者Gagan
相关产品推荐
相关产品推荐

