rust 单元测试接受Fn闭包作为回调的服务

aurhwmvo  于 2023-01-30  发布在  其他
关注(0)|答案(1)|浏览(106)

我有下面的服务,它注册回调函数,以便在某个时期执行,由i64标识。该服务具有回调向量(受Send + Fn() -> () traits约束)。每个回调可以执行多次(因此使用Fn而不是FnOnceFnMut)。需要Send特征,因为回调将由其他线程注册,并且此服务将在后台运行。
目前为止一切顺利,但我想测试回调是否以应有的方式执行(即i64历元在某个方向上滴答作响,这可能(也可能不会)导致回调被执行)。问题是我似乎想不出实现这一点的方法。我“我来自Golang,在Golang中注入一个模拟回调并Assert它是否被调用是非常容易的,因为编译器没有施加这样的限制,但是当我在Rust中使用相同的方法时,我最终得到了一个FnMut,而不是Fn

use std::sync::{Arc, Mutex};
use std::collections::HashMap;

struct Service<T: Send + Fn() -> ()> {
    triggers: Arc<Mutex<HashMap<i64, Vec<Box<T>>>>>,
}

impl<T: Send + Fn() -> ()> Service<T> {
    pub fn build() -> Self {
        Service {
            triggers: Arc::new(Mutex::new(HashMap::new())),
        }
    }

    pub fn poll(&'static self) {
        let hs = Arc::clone(&self.triggers);
        tokio::spawn(async move {
            loop {
                // do some stuff and get `val`
                if let Some(v) = hs.lock().unwrap().get(&val) {
                    for cb in v.iter() {
                        cb();
                    }
                }
            }
        });
        ()
    }

    pub fn register_callback(&self, val: i64, cb: Box<T>) -> () {
        self.triggers
            .lock()
            .unwrap()
            .entry(val)
            .or_insert(Vec::new())
            .push(cb);
    }
}

#[cfg(test)]
mod tests {

    use super::*;

    #[test]
    fn test_poll() {
        let c = Service::build();
        let mut called = false;
        let cb = || called = true;
        let h: i64 = 10;
        c.register_callback(h, Box::new(cb));
        assert_eq!(called, false);
    }
}

关于如何在Rust中测试这种行为,有什么想法吗?我唯一能想到的可能是一些channel,它将向测试传递一个本地值,并放弃对它的所有权?

bvjveswy

bvjveswy1#

最好的方法可能是使您的界面尽可能通用:

// type bounds on structs are generally unnecessary so I removed it here.
struct Service<T> {
    triggers: Arc<Mutex<HashMap<i64, Vec<Box<T>>>>>,
}

impl<T: Send + FnMut() -> ()> Service<T> {
    pub fn build() -> Self {
        Service {
            triggers: Arc::new(Mutex::new(HashMap::new())),
        }
    }

    pub fn poll(&'static self, val: i64) {
        let hs = Arc::clone(&self.triggers);
        tokio::spawn(async move {
            loop {
                // do some stuff and get `val`
                if let Some(v) = hs.lock().unwrap().get_mut(&val) {
                    for cb in v.iter_mut() {
                        cb();
                    }
                }
            }
        });
        ()
    }

    pub fn register_callback(&self, val: i64, cb: Box<T>) -> () {
        self.triggers
            .lock()
            .unwrap()
            .entry(val)
            .or_insert(Vec::new())
            .push(cb);
    }
}

但是如果你不能泛化接口,你可以使用AtomicBool,如下所示:

#[cfg(test)]
mod tests {
    use super::*;
    use std::sync::atomic::{Ordering, AtomicBool};

    #[test]
    fn test_poll() {
        let c = Service::build();
        let mut called = AtomicBool::new(false);
        let cb = || called.store(true, Ordering::Relaxed);
        let h: i64 = 10;
        c.register_callback(h, Box::new(cb));
        assert!(!called.load(Ordering::Relaxed));
    }
}

相关问题