[英]How to inject a stub for a trait object into a type in Rust and keep a reference to it?
I'm trying to develop some kind of time tracking CLI tool in Rust. 我正在尝试在Rust中开发某种时间跟踪CLI工具。 I try to cover most of the code with unit tests and got stuck about how to use stub objects with Rusts ownership restrictions.
我尝试使用单元测试覆盖大多数代码,并陷入如何使用具有Rusts所有权限制的存根对象的困境。
I have a type Tracker
which has functions to start/stop tracking time. 我有一个
Tracker
类型,它具有启动/停止跟踪时间的功能。 These functions query the current system time. 这些函数查询当前系统时间。
To make this design testable I introduced the trait TimeService
which provides the current timestamp. 为了使该设计可测试,我引入了特征
TimeService
,它提供了当前时间戳。 I have the "real" implementation SystemTimeService
which returns the current system time and a fake implementation FakeTimeService
for tests where the time can be set from the outside. 我有“真实的”实现
SystemTimeService
,它返回当前系统时间,还有一个虚假的实现FakeTimeService
用于测试,可以从外部设置时间。
This is the current implementation: 这是当前的实现:
pub struct Tracker {
// ...
time_service: Box<TimeService>,
}
trait TimeService {
fn time(&self) -> u64;
}
Here is the test I'd like to implement: 这是我要实施的测试:
#[test]
fn tracker_end_when_called_tracks_total_time() {
let (tracker, time_service) = init_tracker();
time_service.set_seconds(0);
tracker.start("feature1");
time_service.set_seconds(10);
tracker.end();
assert_eq!(tracker.total_time, 10);
}
fn init_tracker() -> (Tracker, &FakeTimeService) {
let time_service = Box::new(FakeTimeService::new());
let tracker = Tracker::with_time_service(time_service);
// does not compile, as time service's ownership has been
// moved to the tracker.
(tracker, &*time_service)
}
The problem is that I don't know how to access the fake time service from inside the unit test as it's ownership is taken by the tracker. 问题是我不知道如何从单元测试内部访问伪造的时间服务,因为它的所有权是由跟踪器获取的。
I can imagine multiple solutions: 我可以想象多种解决方案:
Rc
instead of Box
inside the tracker for shared ownership of the time service Rc
代替Box
来共享时间服务的所有权 Tracker
generic and add a type argument for the used TimeTracker
implementation Tracker
通用,并为使用的TimeTracker
实现添加类型参数 init_tracker
function init_tracker
函数 I do not like using solution 1 as this would not express the idea that the service is part of the tracker (seems to violate encapsulation). 我不喜欢使用解决方案1,因为这不会表达服务是跟踪器一部分的想法(似乎违反了封装)。
Solution 2 is probably viable but in that case I'd need to make the TimeService
trait and the used implementations public which I'd also like to avoid. 解决方案2可能是可行的,但在那种情况下,我需要公开
TimeService
特性和使用的实现,这也是我想避免的。
So the most promising solution without changing the design seems to be lifetimes. 因此,不更改设计的最有希望的解决方案似乎是使用寿命。 Is it possible to add lifetimes to the variables in such a way that the
init_tracker
function can return the tracker and the fake time service? 是否可以通过
init_tracker
函数可以返回跟踪器和假时间服务的方式来添加变量的生存期?
Are there any other solutions/best practices? 还有其他解决方案/最佳做法吗?
You can make the FakeTimeService
type cloneable, making its state shared across multiple cloned instances: 您可以使
FakeTimeService
类型可克隆,使其状态在多个克隆实例之间共享:
use std::rc::Rc;
#[derive(Clone)]
struct FakeTimeService {
state: Rc<FakeTimeServiceState>,
}
impl TimeService for FakeTimeService { ... }
impl FakeTimeService {
fn state(&self) -> &FakeTimeServiceState {
*self.state
}
}
fn init_tracker() -> (Tracker, FakeTimeService) {
let time_service = FakeTimeService::new();
let tracker = Tracker::with_time_service(Box::new(time_service.clone()));
(tracker, time_service)
}
Now you'll be able to independently modify the fake time service state, while keeping the interface and implementation of the Tracker
the same as before. 现在,您可以独立修改假时服务状态,同时保持
Tracker
的界面和实现与以前相同。
Most likely you'll want to have some mutable state inside FakeTimeService
in order to set up your test preconditions. 您很可能希望在
FakeTimeService
中具有一些可变状态,以便设置测试先决条件。 Therefore, you will probably need to use some kind of internal mutability for the FakeTimeService
state: 因此,您可能需要为
FakeTimeService
状态使用某种内部可变性:
use std::rc::Rc;
use std::cell::{RefCell, RefMut};
#[derive(Clone)]
struct FakeTimeService {
state: Rc<RefCell<FakeTimeServiceState>>,
}
impl FakeTimeService {
fn state_mut(&self) -> RefMut<FakeTimeServiceState> {
self.state.borrow_mut()
}
}
struct FakeTimeServiceState {
init_value: i32,
}
impl FakeTimeServiceState {
fn set_initial_value(&mut self, x: i32) {
self.init_value = x;
}
}
let (tracker, time_service) = init_tracker();
time_service.state_mut().set_initial_value(123);
声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.