【Rust】ユニットテストであそぼう!
はじめに
Rustのユニットテストに関する投稿です。
初学者向けの記事となります。
ユニットテストとは?
テストは、テスト以外のコードが想定通りに動いているかを確かめるRustの関数です。一般にテスト関数は、準備をしてからテストしたいコードを実行し、そしてその結果が期待したものであるか確認します。
。。。
ざっくり言うと、自分の作ったプログラムの部品が、問題なく動いているかチェックすることです。そのチェックの手段がテストコードとなり、テストコードを書いてそれを実行しましょう!ということです。
実際に書いていきます。
テストコードを書いてみる
まずは前回のソースコードを使います。
このコードにメソッドを2つ追加します。ボーカロイドが大人かチェックするメソッドと、こんにちは!と言うメソッドを追加します。
#[derive(Debug)]
struct Vocaloid {
name: String,
age: u8,
songs: Vec<String>,
}
impl Vocaloid {
fn new(name: String, age: u8, songs: Vec<String>) -> Self {
Self { name, age, songs }
}
fn info(&self) {
println!("vocaloid info: {:#?}", &self);
}
+ fn is_adult(&self) -> bool {
+ self.age >= 18
+ }
+ fn say_hello(&self) -> String {
+ format!("Hello {}!", self.name)
+ }
}
pub fn run() {
let songs = vec![
String::from("The Vampire"),
String::from("Cinderella"),
String::from("Ruma"),
];
let miku = Vocaloid::new(String::from("Hatsune Miku"), 16, songs);
}
この追加したコードに対し、テストコードを記述します。テストアフター開発
まずは同一ファイル内にサブモジュールを追加します。
mod test {
}
そして、アトリビュートを追加します。
+ #[cfg(test)]
mod test {
}
親クラスのすべての関数を対象にします。
#[cfg(test)]
mod test {
+ use super::*;
}
大人かどうかをチェックするメソッドに対し、テストコードを追加します。
#[cfg(test)]
mod test {
use super::*;
+ #[test]
+ fn test_is_not_adult() {
+ let miku = Vocaloid::new(String::from("Hatsune Miku"), 16, vec![String::from("Ruma")]);
+ assert!(miku.is_adult());
+ }
}
assert!
は期待どおりに返ってきているかをチェックするマクロです。
ここで注意しないといけないのは、ミクさんは16歳のため大人ではないということです。
つまり、実行すると false となります。
以下のコマンドでテストコードを実行します。
$ cargo test
結果、レッドで返ってきました。
これをレッドからグリーンにします。(ちなみに、テスト駆動開発では先にテストコードを書くため、対象のコードはありません。最初はレッドになります。レッドからグリーンです。レガシーコードからの脱却より)
大人ではないことを確認するためのテストコードなので、!
をつけて false を期待値にします。
- assert!(miku.is_adult());
+ assert!(!miku.is_adult());
再度テストコードを実行します。結果、グリーンで返ってきました。
続いて、大人である場合のテストコードを追加します。
#[test]
fn test_is_adult() {
let kaito = Vocaloid::new(String::from("KAITO"), 26, vec![String::from("Ohed")]);
assert!(kaito.is_adult());
}
テストコードを実行します。結果、2件グリーンで返ってきます。
次に、こんにちは!と言うメソッドに対し、戻り値に名前が含まれているかをテストします。以下のコードを追加します。(モックが冗長になっていますが)
#[test]
fn test_say_hello_contains_name() {
let miku = Vocaloid::new(String::from("Hatsune Miku"), 16, vec![String::from("Ruma")]);
assert!(miku.say_hello().contains("Hatsune Miku"));
}
#[test]
fn test_say_hello_does_not_contain_different_name() {
let miku = Vocaloid::new(String::from("Hatsune Miku"), 16, vec![String::from("Ruma")]);
assert!(!miku.say_hello().contains("KAITO"));
}
テストを実行します。
無事、グリーンが4件返ってきました。
さいごに
今回はユニットテストについての説明でしたが、登場したコードの中に冗長的なコードがありました。
once_cell あたりを使うと問題を回避できそうです。
Discussion
重い処理を一回しか実行したくないとか、グローバルで状態を管理する必要があるとかなら
once_cell
の出番ですが、このくらいのテストのセットアップでわざわざグローバルな状態を作り出さない方がいいのではないでしょうか。コードの冗長性を減らしたいならみたいな共通化をするだけでいいと思います。
白山さんコメントありがとうございます。
おっしゃるとおり、今回の例だとただ関数化するだけでよいですね。