有没有一种在Rust中构建测试的方法,可以在不穷尽时抛出警告?

3

是否有办法在 Rust 中构建测试,以在不详尽时发出警告? 当然,我不期望通用的解决方案,但我正在寻找一种适用于函数参数是枚举类型的解决方案。 我想检查所有组合是否被使用,就像匹配语句检查所有组合是否被覆盖一样。 例如,考虑以下代码:

// Terrible numerical type
#[derive(Debug,PartialEq)]
pub enum Num {
    Int(i32),
    Float(f32),
}

// Mathematical operation on this terrible type
pub fn myadd(x : crate::Num, y :Num) -> Num {
    match (x,y) {
        (Num::Int(x),Num::Int(y)) => Num::Int(x+y),
        (Num::Int(x),Num::Float(y)) => Num::Float((x as f32) + y),
        (Num::Float(x),Num::Int(y)) => Num::Float(x+(y as f32)),
        (Num::Float(x),Num::Float(y)) => Num::Float(x+y),
    }
}

// Add testing
#[cfg(test)]
mod test{
    use super::*;

    #[test]
    fn int_int() {
        assert_eq!(myadd(Num::Int(1),Num::Int(2)),Num::Int(3));
    }

    #[test]
    fn float_int() {
        assert_eq!(myadd(Num::Float(1.),Num::Int(2)),Num::Float(3.));
    }

    #[test]
    fn int_float() {
        assert_eq!(myadd(Num::Int(1),Num::Float(2.)),Num::Float(3.));
    }
}

在这里,我们缺少了测试float_float。我希望有一种方法来发出警告,以表示此测试已丢失。如果我们在模式匹配中忘记了Float,Float情况,将会出现错误:

error[E0004]: non-exhaustive patterns: `(Float(_), Float(_))` not covered
  --> src/lib.rs:10:11
   |
10 |     match (x,y) {
   |           ^^^^^ pattern `(Float(_), Float(_))` not covered
   |
   = help: ensure that all possible cases are being handled, possibly by adding wildcards or more match arms

我正在尝试获取类似于测试组合的东西。如果有关系的话,我不在乎是否将所有测试组合到单个函数中,而不是分成四个不同的测试。我不知道是否有一种使用模式匹配或其他机制来实现这一点的技巧。


1
知道代码是否被单元测试覆盖远不止处理所有“枚举”情况。您对更通用的解决方案(例如代码覆盖率报告)有何感想? - John Kugelman
@JohnKugelman 我同意,但覆盖所有枚举情况对我的用例非常有帮助。有没有一种方法可以涵盖枚举组合? - wyer33
你尝试过像Tarpaulin这样的工具吗? - Stargateur
@Stargateur 是一个不错的想法,我还没有用过它。话说回来,看起来Tarpaulin需要Linux x86-64,这对我来说是一个小问题。而且,实际上,我并不需要检查完美的代码覆盖率。我只需要检查一堆不同的配置是否被测试覆盖,并希望编译器在这种情况下发出警告提示。 - wyer33
1个回答

3

这绝非一种理想的解决方案,但您可以使用一个非常简短的宏来实现:

macro_rules! exhaustive_tests {
    (
        $group_name:ident = match $type:ty {
            $( $test_name:ident = $pattern:pat => $body:block )*
        }
    ) => {
        paste::item!{
            #[allow(warnings)]
            fn [< exhaustive_check_ $group_name >]() {
                let expr: $type = unreachable!();
                match expr {
                    $( $pattern => unreachable!(), )*
                }
            }
        }

        $(
            #[test] fn $test_name() { $body }
        )*
    };
}

宏将让您声明一组测试,每个测试都有一个必须完整编译的模式。
exhaustive_tests!{
    my_tests = match (Num, Num) {
        int_int = (Num::Int(_), Num::Int(_)) => {
            assert_eq!(myadd(Num::Int(1),Num::Int(2)),Num::Int(3));
        }
        float_int = (Num::Float(_), Num::Int(_)) => {
            assert_eq!(myadd(Num::Int(1),Num::Int(2)),Num::Int(3));
        }
        int_float = (Num::Int(_), Num::Float(_)) => {
            assert_eq!(myadd(Num::Int(1),Num::Float(2.)),Num::Float(3.));
        }
    }
}

除了生成每个测试(#[test] fn $test_name() { $body })之外,它还会创建一个名为 exhaustive_check_... 的单个函数,该函数包含一个 match 语句和每个测试的模式。与往常一样,match 语句必须涵盖所有模式,否则将无法编译。
// generated by macro
fn exhaustive_check_my_tests() {
    let expr: (Num, Num) = unreachable!();
    match expr {
        (Num::Int(_), Num::Int(_)) => unreachable!(),
        (Num::Float(_), Num::Int(_)) => unreachable!(),
        (Num::Int(_), Num::Float(_)) => unreachable!(),
        // error[E0004]: non-exhaustive patterns: `(Float(_), Float(_))` not covered
    }
}

在Playground中尝试


网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接