简体   繁体   English

如何将特征对象的存根插入Rust中的类型并对其进行引用?

[英]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: 我可以想象多种解决方案:

  1. Use Rc instead of Box inside the tracker for shared ownership of the time service 在跟踪器中使用Rc代替Box来共享时间服务的所有权
  2. Make the Tracker generic and add a type argument for the used TimeTracker implementation 使Tracker通用,并为使用的TimeTracker实现添加类型参数
  3. Add lifetimes to the 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.

相关问题 如何对函数存根并为引用类型参数定义相等比较器 - How to stub a function and define equality comparer for a Reference Type parameter 如何使外部板条箱(或周围的方法)生锈 - How to stub an external crate (or ways around it) in rust 如何在struct / class中存根Swift“Trait / Mixin”方法进行测试 - How to stub Swift “Trait/Mixin” method in struct/class for testing 扩展特征的单元测试类-如何在特征中模拟和存根方法? - Unit testing class that extends a trait - how do I mock and stub methods in the trait? 如何使用Sinon存根对象? - How to stub an object using Sinon? 如何存根对具有引用参数的方法的调用? - How to stub a call to a method with a reference argument? 如何使用AutoFixture生成编译时未知的任意类型的存根对象 - How to generate a stub object of an arbitrary Type not known at compile time using AutoFixture 生成并引用存根对象进行测试,而无需存根将扩展的类 - Make and reference stub object for testing, without needing the class that the stub would extend 如何存根模块中导出的函数返回的对象 - How to stub an object returned by function exported in the module 如何存根/模拟XMLBeans对象以进行测试? - How to stub/mock XMLBeans object for testing?
 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM