「TAURI」と「Rust」の「テスト」機能を試してみよう

2024年3月12日(火)
大西 武 (オオニシ タケシ)
第11回の今回は「Rust」で値が正しいか「テスト」する「assert_eq!」マクロなどを解説します。また「TAURI」でもプログラムが正しく動作するかの「テスト」についても解説します。

はじめに

今回は「Rust」の「テスト」機能を解説します。テストとはプログラムが正常に動作しているか検証するための機能のことです。テストプログラム実行時の計算結果の値が正しいかどうかを「assert_eq!」「assert_ne!」「assert!」「panic!」の各マクロを使って検証します。

また、本連載は「TAURI」がテーマでもあるので、TAURIを使う際のRustについてもテストします。TAURIでのテストの際はフロントエンドの「HTML5+JavaScript+CSS」はほとんど関係しません。

Rustでテストをするためのプロジェクト

まず、TAURIではなくRustのみのプロジェクトを作成します。追加で必要なクレートはありません。

ライブラリ用のプロジェクトの作成

第1回のRustの設定が済んでいる前提で話を進めていきます。適当なフォルダをカレントディレクトリにして、次のコマンドを実行して「rust_test」などという名前でプロジェクトを新規作成します。「--lib」オプションはライブラリ用のプロジェクトを作成します。それから作成した「rust_test」フォルダをカレントディレクトリにします。

・ライブラリ用のRustプロジェクトの作成コマンド
$ cargo new rust_test --lib

テンプレートプロジェクトのテスト

まずは作成されたプロジェクトを次のコマンドを実行してテストしてみます。今までのように「cargo」コマンドで「run」や「build」を実行して実行ファイルを作るのではなく、「test」を実行してテストします。

テンプレートのソースコードの内容は「add」関数で2つの引数の値「2」と「2」を加算した結果を返し、その戻り値が「4」と等しいか調べるだけの単純なものです。

・Rustのテストを実行
$ cargo test

すると、図1のような文がターミナルに表示されます。各文の意味は下記の通りです。

  • test result: ok.
    →テストの結果はOKでした
  • 1 passed;
    →1個テストを通過しました
  • 0 failed;
    →0個テストが失敗しました
  • 0 ignored;
    →0個テストが無視されました
  • 0 measured;
    →0個テストでパフォーマンスを調べました(nightly版のRustのみの機能)
  • 0 filtered out;
    →0個テストでフィルターがかけられました

図1:テスト成功の画面

テストを実行してみる

テストにはassert_eq!、assert_ne!、assert!、panic!の4つのマクロのいずれか、または全部を使います。ここでのサンプルコードでは、分かりやすいように最低限の例だけコーディングしています。ここで、簡単に各マクロについて解説しておきます。

  • assert_eq!マクロ
    「assert」とは「主張する」という意味で「eq」は「equal」の略と思われます。文字通り主張が「イコール(等しい)」かを調べるテストをします。
  • assert_ne!マクロ
    「ne」は「not equal」の略と思われます。文字通り主張が「ノット イコール(等しくない)」かを調べるテストをします。
  • assert!マクロ
    主張の真偽をテストします。「true」ならテストに成功し、「false」ならテストに失敗します。
  • panic!マクロ
    文字通り「パニック(慌てる)」することで、常にテストに失敗します。オリジナルのテストを作るときなど、失敗を表す場合に使います。

assert_eq! マクロ

デフォルトのプロジェクトは2つの引数の値が等しいか調べるassert_eq!マクロが成功した例でした。今度は次のサンプルコードのようにassert_eq!マクロが失敗します。

次のテストを実行すると「tests::it_works」が「test result: FAILED. 0 passed; 1 failed; 0 ignored; 0 measured; 0 filtered out; finished in 0.00s」と表示されます。「test result: FAILED.」でテスト結果が失敗を表しています(図2)。

「#[cfg(test)]」の次行からのモジュールがテスト対象になります。「#[test]」の次行からの関数を呼び出さなくてもテストされます。ここでは最も単純な等不等を調べているだけですが、皆さんはご自身のコードで複雑な計算をテストしてみてください。

図2:テスト失敗の例

・「src」→「main.rs」ファイルの「assert_eq!」マクロのサンプルコード
//testsモジュールをテスト対象に
#[cfg(test)]
mod tests {
  //it_works関数をテスト
  #[test]
  fn it_works() {
    //等しいとテスト成功
    assert_eq!(1+1, 3);
  }
}

【サンプルコードの解説】
#[cfg(test)]の「cfg」には環境に応じたコンパイルを指定し、「()」内が「アトリビュート(属性)」です。ここでは「test」属性でコンパイルを指定します。
「mod」文はモジュール宣言です。
#[test]の次行からが「test」関数であることを表しています。
testのit_works関数はテストを実行する際に呼ばれ、assert_eq!マクロで1つ目の引数と2つ目の引数が等しければテスト成功です。

assert_ne! マクロ

次は、2つの引数の値が等しくないことを調べるassert_ne!マクロの例です。次のサンプルコードのようにassert_ne!マクロの2つの引数が等しくない場合に成功します。

次のテストを実行すると「tests::it_works」が「test result: ok. 1 passed; 0 failed; 0 ignored; 0 measured; 0 filtered out; finished in 0.00s」と表示されます。「test result: ok.」でテスト結果がOKでした。

・「src」→「main.rs」ファイルの「assert_ne!」マクロのサンプルコード
#[cfg(test)]
mod tests {

  #[test]
  fn it_works() {
    //等しくないとテスト成功
    assert_ne!(1+1, 3);
  }
}
-----

【サンプルコードの解説】
testのit_works関数はテストを実行する際に呼ばれ、assert_ne!マクロで1つ目の引数と2つ目の引数が等しくなければテスト成功です。

assert! マクロ

続いて引数の値がtrueかfalseか調べるassert!マクロの例です。次のサンプルコードのようにassert!マクロの引数がtrueの場合に成功します。次のテストを実行すると「tests::it_works」が「test result: ok. 1 passed; 0 failed; 0 ignored; 0 measured; 0 filtered out; finished in 0.00s」と表示されます。「test result: ok.」でテスト結果がOKでした。

・「src」→「main.rs」ファイルの「assert!」マクロのサンプルコード
#[cfg(test)]
mod tests {

  #[test]
  fn it_works() {
    //trueなら成功、falseなら失敗
    assert!(1+1 == 2);
  }
}

【サンプルコードの解説】
testのit_works関数はテストを実行する際に呼ばれ、assert!マクロで1つ目の引数が「true」ならテスト成功です。

panic! マクロ

最後は常にテストが失敗するpanic!マクロの例です。次のサンプルコードのようにpanic!マクロの引数の「動物名がありません!」がターミナルにエラー内容として表示されます。新たに「use super::*;」が出てきますが、これによりテストするモジュール外で定義したものも「tests」モジュール内で全て使えるようにします。

次のテストを実行すると「tests::no_name」が「test result: FAILED. 0 passed; 1 failed; 0 ignored; 0 measured; 0 filtered out; finished in 0.00s」と表示されます。「test result: FAILED.」でテスト結果が失敗でした。

・「src」→「main.rs」ファイルで「panic!」マクロのサンプルコード
//動物の構造体
struct Animal {
  name: String,
}
//動物の構造体のメソッド
impl Animal {
  fn new(name: String) -> Self {
    Animal {
      name: name,
    }
  }
}

#[cfg(test)]
mod tests {
  //モジュール外の定義を使えるように
  use super::*;
  //動物名があるかをテスト
  #[test]
  fn no_name() {
    let instance = Animal::new(String::from(""));
    if instance.name == "" {
      panic!("動物名がありません!");
    }
  }
}

【サンプルコードの解説】
「name」プロパティを持った「Animal」構造体を宣言します。
Animal構造体のコンストラクタを「new」メソッドで宣言します。
testsモジュールをtest属性にします。
「use」文で全てのモジュール外の定義を使えるようにします。
テスト関数として宣言した「no_name」関数はテストを実行する際に呼ばれ、Animal構造体のインスタンスのname文字列が""(空)ならpanic!マクロで1つ目の引数の"動物名がありません!"文字列がエラーとして表示されます。

著者
大西 武 (オオニシ タケシ)
1975年香川県生まれ。大阪大学経済学部経営学科中退。プログラミング入門書など30冊以上を商業出版。Microsoftで大賞やNTTドコモでグランプリなど20回以上全国区のコンテストに入賞。オリジナルの間違い探し「3Dクイズ」が全国放送のTVで約10回出題。

連載バックナンバー

開発言語技術解説
第12回

「TAURI」でExcelのデータを読み書きしてWebページに表示してみよう

2024/4/2
第12回の今回は「TAURI」で「Rust」の「umya-spreadsheet」クレートを使って「Excel」の「xlsx」ファイルを読み書きし、Webページに表示するところまでを解説します。
開発言語技術解説
第11回

「TAURI」と「Rust」の「テスト」機能を試してみよう

2024/3/12
第11回の今回は「Rust」で値が正しいか「テスト」する「assert_eq!」マクロなどを解説します。また「TAURI」でもプログラムが正しく動作するかの「テスト」についても解説します。
開発言語技術解説
第10回

「TAURI」と「Rust」の「Option型」と「Result型」を使いこなそう

2024/2/27
第10回の今回は「TAURI」や「Rust」で値なしを場合分けする「Option型」と、例外処理の代わりをする「Result型」について解説します。

Think ITメルマガ会員登録受付中

Think ITでは、技術情報が詰まったメールマガジン「Think IT Weekly」の配信サービスを提供しています。メルマガ会員登録を済ませれば、メルマガだけでなく、さまざまな限定特典を入手できるようになります。

Think ITメルマガ会員のサービス内容を見る

他にもこの記事が読まれています