ys memos

Blog

Rust入門 Matrixクラス 2 Unit Test


rust

2022/09/24


Rust の勉強を目的とし,Matrix クラスを作る.

それを通して得た知見を残す.

コード全体はこちら



Struct に実装したメソッドに対する Unit Test


推奨されている方法というわけではないが,src/matrix/matrix.rsが肥大化するのを防ぐ目的で,別ファイルに UnitTest をまとめてみた.

src/matrix/matrix_test.rs
#[cfg(test)]
mod tests {
    mod new {
        use std::{
            fmt::{Debug, Display},
            ops::{AddAssign, SubAssign},
        };

        use crate::matrix::Matrix;

        fn unit<T>(input: Vec<Vec<T>>, width: u8, height: u8, result: Vec<T>)
        where
            T: PartialEq + Debug + Copy + AddAssign + SubAssign + Display,
        {
            let mat = Matrix::<T>::new(input);
            assert_eq!(mat.width, width);
            assert_eq!(mat.height, height);
            let zipped = mat.data.iter().zip(result.iter());
            for (mv, rv) in zipped.into_iter() {
                assert_eq!(mv, rv);
            }
        }

        #[test]
        fn i32_1x1() {
            unit(vec![vec![0]], 1, 1, vec![0]);
        }
        #[test]
        fn i32_1x2() {
            unit(vec![vec![0], vec![1]], 1, 2, vec![0, 1]);
        }
        #[test]
        fn i32_2x1() {
            unit(vec![vec![0, 1]], 2, 1, vec![0, 1]);
        }
        #[test]
        fn i32_2x2() {
            unit(vec![vec![0, 1], vec![2, 3]], 2, 2, vec![0, 1, 2, 3]);
        }
        #[test]
        fn i32_3x3() {
            let input = vec![vec![0, 1, 2], vec![3, 4, 5], vec![6, 7, 8]];
            let result = vec![0, 1, 2, 3, 4, 5, 6, 7, 8];
            unit(input, 3, 3, result);
        }

        #[test]
        fn f32_1x1() {
            unit(vec![vec![0.0_f32]], 1, 1, vec![0.0_f32]);
        }

        #[test]
        fn f64_1x1() {
            unit(vec![vec![0.0_f64]], 1, 1, vec![0.0_f64]);
        }
    }

    #[test]
    fn at() {
        let mat = matrix![[0, 1], [2, 3]];
        assert_eq!(*mat.at(0, 0), 0);
        assert_eq!(*mat.at(0, 1), 1);
        assert_eq!(*mat.at(1, 0), 2);
        assert_eq!(*mat.at(1, 1), 3);
    }

    #[test]
    fn at_mut() {
        let mut mat = matrix![[0, 1], [2, 3]];
        *mat.at_mut(0, 0) = 1;
        assert_eq!(*mat.at(0, 0), 1);
    }

    #[test]
    fn flatten() {
        let mat = matrix![[0, 1], [2, 3]];
        assert_eq!(mat.flatten(), vec![0, 1, 2, 3]);
    }

    #[test]
    fn add() {
        let mut mat = matrix![[0, 1], [2, 3]];
        let res = mat.add(&matrix![[3, 2], [1, 0]]);
        assert!(res.is_ok());
        assert_eq!(mat, matrix![[3, 3], [3, 3]]);
    }

    #[test]
    fn diff() {
        let mut mat = matrix![[3, 3], [3, 3]];
        let res = mat.diff(&matrix![[3, 2], [1, 0]]);
        assert!(res.is_ok());
        assert_eq!(mat, matrix![[0, 1], [2, 3]]);
    }
}


cfg(test)により,module を TestModule にすることが出来る.

cargo buildではビルドされず,cargo test実行時にのみビルドされる.

Test のグループ化のような役割も果たしているし,Module 内での Test 用で汎用的な処理を関数化することが出来る.

#[cfg(test)]
mod tests {

new()には数パターンのテストを行いたかったため,new Module としてまとめた.

また,生成されたインスタンスの整合性確認のための関数を,ここではunit()という名前で準備した.

assert_eq!マクロは,2つの引数がイコールであることをチェックする.assert には,trueである確認のassert!や,2つの引数がイコール出ないことを確認するassert_ne!などもある.

    mod new {
        use std::{
            fmt::{Debug, Display},
            ops::{AddAssign, SubAssign},
        };

        use crate::matrix::Matrix;

        fn unit<T>(input: Vec<Vec<T>>, width: u8, height: u8, result: Vec<T>)
        where
            T: PartialEq + Debug + Copy + AddAssign + SubAssign + Display,
        {
            let mat = Matrix::<T>::new(input);
            assert_eq!(mat.width, width);
            assert_eq!(mat.height, height);
            let zipped = mat.data.iter().zip(result.iter());
            for (mv, rv) in zipped.into_iter() {
                assert_eq!(mv, rv);
            }
        }

test属性を付けることで,cargo testでこれが実行される.

1x11x2など関数名で,「何のテストか」が分かるようにしている.

        #[test]
        fn i32_1x1() {
            unit(vec![vec![0]], 1, 1, vec![0]);
        }
        #[test]
        fn i32_1x2() {
            unit(vec![vec![0], vec![1]], 1, 2, vec![0, 1]);
        }
        #[test]
        fn i32_2x1() {
            unit(vec![vec![0, 1]], 2, 1, vec![0, 1]);
        }
        #[test]
        fn i32_2x2() {
            unit(vec![vec![0, 1], vec![2, 3]], 2, 2, vec![0, 1, 2, 3]);
        }
        #[test]
        fn i32_3x3() {
            let input = vec![vec![0, 1, 2], vec![3, 4, 5], vec![6, 7, 8]];
            let result = vec![0, 1, 2, 3, 4, 5, 6, 7, 8];
            unit(input, 3, 3, result);
        }

        #[test]
        fn f32_1x1() {
            unit(vec![vec![0.0_f32]], 1, 1, vec![0.0_f32]);
        }

        #[test]
        fn f64_1x1() {
            unit(vec![vec![0.0_f64]], 1, 1, vec![0.0_f64]);
        }
    }

本来は多くのパターンに対応できるようにテストを書いたほうが良いが,ここでは各メソッドあたり1パターンのテストとした.

assert!は,引数がbool型のtrueである事を確認する.

    #[test]
    fn at() {
        let mat = matrix![[0, 1], [2, 3]];
        assert_eq!(*mat.at(0, 0), 0);
        assert_eq!(*mat.at(0, 1), 1);
        assert_eq!(*mat.at(1, 0), 2);
        assert_eq!(*mat.at(1, 1), 3);
    }

    #[test]
    fn at_mut() {
        let mut mat = matrix![[0, 1], [2, 3]];
        *mat.at_mut(0, 0) = 1;
        assert_eq!(*mat.at(0, 0), 1);
    }

    #[test]
    fn flatten() {
        let mat = matrix![[0, 1], [2, 3]];
        assert_eq!(mat.flatten(), vec![0, 1, 2, 3]);
    }

    #[test]
    fn add() {
        let mut mat = matrix![[0, 1], [2, 3]];
        let res = mat.add(&matrix![[3, 2], [1, 0]]);
        assert!(res.is_ok());
        assert_eq!(mat, matrix![[3, 3], [3, 3]]);
    }

    #[test]
    fn diff() {
        let mut mat = matrix![[3, 3], [3, 3]];
        let res = mat.diff(&matrix![[3, 2], [1, 0]]);
        assert!(res.is_ok());
        assert_eq!(mat, matrix![[0, 1], [2, 3]]);
    }
}

C++だと GoogleTest など,外部のユニットテストツールを用いる必要があるので,言語標準でユニットテスト機能を持っているのは素晴らしいですねぇ!


関連タグを探す