我正在测试一个结构体,它看起来像这样
struct CANProxy {
socket: CANSocket
// other stuff .......
}
impl CANProxy {
pub fn new(can_device: &str) -> Self {
let socket = CANSocket::open(can_device).unwrap();
// other stuff .......
Self { socket }
}
}
我想测试的是通过套接字发送的消息是否正确,但我不想在运行测试时实际初始化新的can设备。我想做一个虚拟的CANSocket(来自cansocket crate),它使用相同的函数和诸如此类的东西。
我试着创建一个trait并扩展socketcan::CANSocket
,但它非常繁琐和冗余。我已经看过mockall
机箱,但我不确定这是否会在这种情况下有所帮助。有没有一种优雅的方式来实现我想要的?
trait CANInterface {
fn open(name: &str) -> Result<Self, SomeError>;
// ... all the functions that are a part of the socketcan::CANSocket
// which is a lot of repetition
}
///////////// Proxy code
struct<T: CANInterface> CANProxy<T> {
socket: T
// other stuff .......
}
impl<T: CANInterface> CANProxy<T> {
pub fn open(can_device: &str) -> Result<Self, SomeError> {
let socket = T::open(can_device).unwrap();
// other stuff .......
Ok(Self { socket })
}
}
////////////// Stubbed CANInterfaces
struct FakeCANSocket;
impl CANInterface for FakeCANSocket {
// ..... implementing the trait here
}
// extension trait over here
impl CANInterface for socketcan::CANSocket {
// this is a lot of repetition and is kind of silly
// because I'm just calling the same things
fn open(can_device: &str) -> Self {
CANSocket::open(can_device)
}
/// ..............
/// ..............
/// ..............
}
2条答案
按热度按时间gopyfrb31#
因此,首先,确实有一些针对mock的辅助工具和crate(如
::mockall
)来帮助处理这些模式,* 但前提是您已经有了基于trait的API*。如果你不这样做,那部分可能会很乏味。值得一提的是,还有其他的helper crates来帮助编写样板式的y和冗余委派的trait impls,比如你的
open -> open
情况。一个这样的例子可能是::delegate
机箱。使用测试目标Cargo功能进行模拟
话虽如此,我个人对你的特殊情况的看法-目标是用一个mock来覆盖一个真正的impl,但只是为了测试目的-将放弃泛型和traits的结构化但重量级的方法,而是拥抱“鸭子类型”的API,* 就像在不同平台上实现时经常做的那样。换句话说,下面的建议,从概念上讲,可以解释为您的测试环境是一个这样的特殊“平台”。
然后,您可以使用
#[cfg(…)]
-feature-gate来使用真实的的impl,即在一种情况下使用CANSocket
类型,并使用#[cfg(not(…))]
-feature gate来模拟您自己的CANSocket
类型的定义,前提是您设法复制/模拟了您自己可能正在使用的所有真正的类型API。mock-socket
Cargo功能添加到您的项目中:cfg(test)
而不是cfg(feature = "…")
,但这种方法只适用于 unit(带有#[cfg(test)] mod tests
、cargo test --lib
调用的src/…
文件)测试,它不用于集成测试(tests/….rs
文件,cargo test --tests
调用)或doctests(cargo test --doc
调用),因为库本身然后在没有 *cfg(test)
的情况下编译。my_own_mock_socket
模块(* 例如 *,在一个my_own_mock_socket.rs
文件中使用mod my_own_mock_socket;
声明),前提是你不要忘记对它本身进行功能门控,这样编译器在不使用模拟的CANSocket
时就不会浪费时间和精力编译它(这会产生dead_code
警告等):cargo test
cargo test --features mock-socket
在运行测试时选择所需的实现
features
.toml
属性来启用该dev-dependency
的功能:奖励:而不必为模拟代码定义额外的模块。
当所讨论的mock impls足够短时,将其定义和
impl
块内联可能更有吸引力。问题是,对于每个这样定义的项目,它必须携带#[cfg…]
属性,这很烦人。这时,像https://docs.rs/cfg-if这样的辅助宏就很有用了,尽管为这样一个简单的宏添加一个依赖似乎有点大材小用(而且,非常个人,我发现cfg_if!
的语法太符号化了)。相反,你可以用不到12行代码自己重新实现它:
使用它,您可以将上面的步骤
2.
和3.
重写为:gfttwv5a2#
您可以通过使用宏创建 Package 器trait并为基本结构实现它来避免很多样板。简化示例:
Playground
这将需要扩展以处理引用(至少
&self
参数),但您可以理解。您可以参考The Little Book of Rust Macros以获得有关编写宏的更多信息。然后,您可以使用像
mockall
这样的crate来创建TestTrait
的模拟实现,或者自己创建。