说我有两个特质
特质.rs
pub trait MessagingService {
fn new() -> Self
...
}
pub trait WebService {
fn create_client() -> Client;
fn send_request(req: RequestBuilder) -> Option<Value>;
}
还有一个模块,我在其中实现这两个不和谐的特征
服务/discord.rs
pub struct DiscordService {
client: Client,
}
impl WebService for DiscordService {
// a.k.a the transport layer
fn send_request(req: RequestBuilder) -> Option<Value> {
// contains all the code involved that involves actual HTTP requests
}
}
impl MessagingService for DiscordService {
// contains the code that
// i) build request
// ii) processing of the values returned by WebService::send_request
fn update_task_status(&self, task: &mut Task) {
let body = json!({"what":"ever"});
let _ = Self::send_request(
self.client
.post(format!(
"{BASE_URL}/channels/{}/messages",
CONF.discord_channel
))
.json(&body),
);
}
}
我想模拟传输部分,即
WebService
,这样我就可以独立测试MessagingService
的功能。
我多次读到,正确的 rustacean 方法是 为特征提供模拟,而不是模拟对象。我将其理解为在我的测试中提供了这一特性的新实现。
服务/discord.rs
...
#[cfg(test)]
mod tests {
use reqwest::blocking::{Client, RequestBuilder};
use serde_json::Value;
use super::DiscordService;
use crate::structs::WebService;
impl WebService for DiscordService {
fn send_request(req: RequestBuilder) -> Option<Value>{
// builds Values from sourced files instead
// of sending HTTP requests
}
}
#[test]
fn some_test() {
let notifier: DiscordService = DiscordService::new();
// test notifier.update_task_status
}
}
纸面上看起来不错,但我得到了
conflicting implementations of trait WebService for type discord::DiscordService
。我认为 mod tests {}
会像 encapsulation 一样工作,允许我提供 WebService
的替代实现,但事实似乎并非如此。我不想创建像 DiscordServiceMock
这样的新结构,因为它会使我的代码的某些部分无法测试。
有没有一种方法可以提供这样的实现,而不会最终导致冲突?还有别的办法吗?
不,这不是 Rust 的工作方式或应该使用的方式。正如 Rust Reference 中所述,不允许对一个类型进行多个重叠的实现:
如果孤立规则检查失败或存在重叠的实现实例,则特征实现被认为是不连贯的。
对此有多种解决方案。它们都不是 Rust 特定的。
如果您坚持模拟部分代码的想法,则可以将特征视为接口并使用某种策略模式(其中涉及注入不同的策略)。然后实施测试策略(如
DiscordServiceMock
)并在测试中使用它。