【问题标题】:How to mock specific methods but not all of them in Rust?如何在 Rust 中模拟特定方法但不是所有方法?
【发布时间】:2019-12-07 12:38:18
【问题描述】:

我在为目标结构的方法确定单元测试时遇到了麻烦。

我有一个方法random_number,它根据结构的属性返回一个随机值,还有另一个方法plus_one,它采用第一种方法的结果并对其进行处理:

pub struct RngTest {
    pub attr: u64,
}

impl RngTest {
    pub fn random_number(&self) -> u64 {
        let random = 42; // lets pretend it is random
        return random * self.attr;
    }

    pub fn plus_one(&self) -> u64 {
        return self.random_number() + 1;
    }
}

对第一种方法进行单元测试,测试另一种方法的策略是什么?我想为plus_one() 的单元测试模拟self.random_number() 输出,以便在单元测试中拥有健全的代码。有一篇很好的帖子比较了different mocking libraries 并得出结论(很遗憾),它们中没有一个能真正脱颖而出。

我在阅读这些库的说明时学到的唯一一件事是,我可以模拟方法的唯一方法是将它们移动到一个 trait。我在这些库中没有看到任何示例(我查看了其中的 4 或 5 个),它们测试了与此类似的案例。

在将这些方法移动到特征(即使它们是)之后,我如何模拟 random_number 以对 RngTest::plus_one 的输出进行单元测试?

pub trait SomeRng {
    fn random_number(&self) -> u64 {
        let random = 42; // lets pretend it is random
        return random * self.attr;
    }

    fn plus_one(&self) -> u64 {
        return self.random_number() + 1;
    }
}

impl SomeRng for RngTest {}

【问题讨论】:

  • 在 Rust 中没有意义?

标签: unit-testing testing rust mocking


【解决方案1】:

感谢@Shepmaster,我找到了这个解决方法。我添加了实际的Rng 以获得更多上下文。

use rand::{thread_rng, Rng}; // 0.6.5

struct RngTest(Vec<u64>);

impl RngTest {
    fn random_number(&self) -> u64 {
        let random_value = thread_rng().choose(&self.0);
        *random_value.unwrap()
    }

    fn plus_one(&self) -> u64 {
        self.random_number() + 1
    }
}

#[test]
fn plus_one_works() {
    let rng = RngTest(vec![1]);
    assert_eq!(rng.plus_one(), 2);
}

我可以在对象中设置一个合适的值并且不需要使用特征。虽然有一个缺点 - 这迫使我为这个特定的测试有一个我的类型的特殊实例,我想避免这种情况,因为我的实际类型有 很多 字段,我想定义它的创建使用speculate 进行所有测试。

【讨论】:

    【解决方案2】:

    如何在 Rust 中模拟特定方法而不是所有方法?

    正如您已经了解到的,您不能替换类型上的方法。您唯一能做的就是将方法移至特征,然后提供该特征的生产和测试特定的实现。您如何构建特征决定了您能够测试的粒度。

    具有默认实现的特征

    根据您的用例,您也许可以使用默认实现:

    trait SomeRng {
        fn random_number(&self) -> u64;
    
        fn plus_one(&self) -> u64 {
            self.random_number() + 1
        }
    }
    
    struct RngTest(u64);
    impl SomeRng for RngTest {
        fn random_number(&self) -> u64 {
            self.0
        }
    }
    
    #[test]
    fn plus_one_works() {
        let rng = RngTest(41);
        assert_eq!(rng.plus_one(), 42);
    }
    

    这里,random_number 是必需的方法,但plus_one 有一个默认实现。实现random_number 默认为您提供plus_one。如果您可以更有效地执行此操作,您也可以选择实施plus_one

    真正的 rand crate 有什么作用?

    真正的rand crate 使用两个特征:

    • Rng

      pub trait Rng: RngCore { /* ... */ }
      

      RngCore 上自动实现的扩展特征,为采样值和其他便利方法提供高级通用方法。

    • RngCore

      pub trait RngCore { /* ... */ }
      

      随机数生成器的核心。

    这将实现的核心有趣部分与辅助方法分开。然后您可以控制核心并测试助手:

    trait SomeRngCore {
        fn random_number(&self) -> u64;
    }
    
    trait SomeRng: SomeRngCore {
        fn plus_one(&self) -> u64 {
            self.random_number() + 1
        }
    }
    
    impl<R: SomeRngCore> SomeRng for R {}
    
    struct RngTest(u64);
    impl SomeRngCore for RngTest {
        fn random_number(&self) -> u64 {
            self.0
        }
    }
    
    #[test]
    fn plus_one_works() {
        let rng = RngTest(41);
        assert_eq!(rng.plus_one(), 42);
    }
    

    【讨论】:

      猜你喜欢
      • 2017-08-07
      • 2012-05-04
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 2014-10-15
      • 1970-01-01
      • 1970-01-01
      相关资源
      最近更新 更多