2022/09/24
はじめに
Rust の勉強を目的とし,Matrix クラスを作る.
それを通して得た知見を残す.
コード全体はこちら.
入門コース目次
- 入門 0 概要
- 入門 1 Struct とメソッド
- 入門 2 Unit Test
- 入門 3 演算子オーバーロードによるインデックスアクセス
- 入門 4 インスタンス生成用の自作マクロ
- 入門 5 module
- 入門 6 自作 Module を利用する
このページで作るもの
Struct に実装したメソッドに対する Unit Test
コード全体
推奨されている方法というわけではないが,src/matrix/matrix.rs
が肥大化するのを防ぐ目的で,別ファイルに UnitTest をまとめてみた.
#[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)
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);
}
}
new()のテスト
test
属性を付けることで,cargo test
でこれが実行される.
1x1
や1x2
など関数名で,「何のテストか」が分かるようにしている.
#[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 など,外部のユニットテストツールを用いる必要があるので,言語標準でユニットテスト機能を持っているのは素晴らしいですねぇ!