Closed10

The Rust Programming Language - Chapter 11 メモ

Tatsushi KiryuTatsushi Kiryu

https://doc.rust-jp.rs/book-ja/ch11-00-testing.html

自動テストを書く

https://doc.rust-jp.rs/book-ja/ch11-01-writing-tests.html

テストの記述法

  1. 必要なデータや状態をセットアップする。
  2. テスト対象のコードを走らせる。
  3. 結果が想定通りであることを断定(以下、アサーションという)する。

テスト関数の構成

  • 最も単純には、Rustにおけるテストはtest属性で注釈された関数
    • 属性:Rustコードの部品に関するメタデータ
      • 例)derive属性
    • 関数に #[test] を付与すればテスト関数になる
    • cargo test でテストを実行できる
Rust
#[cfg(test)]
mod tests {
    #[test] // これがあるとテスト関数になる
    fn it_works() {
        assert_eq!(2 + 2, 4); // assert_eq!マクロ
    }
    #[test]
    fn another() {
        //このテストを失敗させる
        panic!("Make this test fail");
    }
}
Tatsushi KiryuTatsushi Kiryu

assert!マクロで結果を確認する

Rust
#[cfg(test)]
mod tests {
    // 外部モジュール内のテスト配下にあるコードを内部モジュールのスコープに持っていく必要がある
    // 外部モジュール内のテスト配下にあるコード = Rectangle
    use super::*;

    #[test]
    fn larger_can_hold_smaller() {
        let larger = Rectangle {
            width: 8,
            height: 7,
        };
        let smaller = Rectangle {
            width: 5,
            height: 1,
        };

        assert!(larger.can_hold(&smaller));
    }

    #[test]
    fn smaller_cannot_hold_larger() {
        let larger = Rectangle {
            width: 8,
            height: 7,
        };
        let smaller = Rectangle {
            width: 5,
            height: 1,
        };

        assert!(!smaller.can_hold(&larger));
    }
}
Tatsushi KiryuTatsushi Kiryu

assert_eq!とassert_ne!マクロで等値性をテストする

Rust
pub fn add_two(a: i32) -> i32 {
    a + 2
}

#[cfg(test)]
mod tests {
    use super::*;

    #[test]
    fn it_adds_two() {
        assert_eq!(4, add_two(2));
        assert_ne!(3, add_two(2));
    }
}

Tatsushi KiryuTatsushi Kiryu

カスタムの失敗メッセージを追加する

Rust
pub fn greeting(name: &str) -> String {
    format!("Hello {}!", name)
}

#[cfg(test)]
mod tests {
    use super::*;

    #[test]
    fn greeting_contains_name() {
        let result = greeting("Carol");
        assert!(
            result.contains("Carol"),
            "Greeting did not contain name, value was `{}`", // 第二引数で失敗メッセージを指定する
            result
        );
    }
}
Tatsushi KiryuTatsushi Kiryu

should_panicでパニックを確認する

Rust
pub struct Guess {
    value: i32,
}

impl Guess {
    pub fn new(value: i32) -> Guess {
        Guess { value }
    }
}

#[cfg(test)]
mod tests {
    use super::*;

    #[test]
    // #[should_panic] // should_panic属性を追加する
    #[should_panic(expected = "Guess value must be less than or equal to 100")]
    fn greater_than_100() {
        Guess::new(200);
    }
}
Tatsushi KiryuTatsushi Kiryu

Result<T, E>をテストで使う

Rust
#[cfg(test)]
mod tests {
    #[test]
     // #[should_panic] は使えない
    fn it_works() -> Result<(), String> {
        if 2 + 2 == 4 {
            Ok(())
        } else {
            // テスト失敗させる場合は Err を返す
            Err(String::from("two plus two does not equal four"))
        }
    }
}
Tatsushi KiryuTatsushi Kiryu

https://doc.rust-jp.rs/book-ja/ch11-02-running-tests.html

テストの実行のされ方を制御する

  • cargo run
    • コードをコンパイルし、出来上がったバイナリを実行する
  • cargo test
    • コードをテストモードでコンパイルし、出来上がったテストバイナリを実行する

テストを並行または連続して実行する

  • デフォルトでは、複数のテストは平行に実行される。

    • テストが相互や共有された環境を含む状態に依存しているとダメ
      • 作業対象ディレクトリや環境変数など
  • テストを1つずつ実行するには、スレッド数を制限する

    • テスト実行時のスレッド数の制御には --test-threads フラグを使う
cargo test -- --test-threads=1
Tatsushi KiryuTatsushi Kiryu

関数の出力を表示する

Rust
fn prints_and_returns_10(a: i32) -> i32 {
    //{}という値を得た
    println!("I got the value {}", a);
    10
}

#[cfg(test)]
mod tests {
    use super::*;

    #[test]
    fn this_test_will_pass() {
        let value = prints_and_returns_10(4);
        assert_eq!(10, value);
    }

    #[test]
    fn this_test_will_fail() {
        let value = prints_and_returns_10(8);
        assert_eq!(5, value);
    }
}

デフォルトでは I got the value 4 などが出力されない。
出力したければ --nocapture フラグを使う

cargo test -- --nocapture
Tatsushi KiryuTatsushi Kiryu

名前でテストの一部を実行する

Rust
pub fn add_two(a: i32) -> i32 {
    a + 2
}

#[cfg(test)]
mod tests {
    use super::*;

    #[test]
    fn add_two_and_two() {
        assert_eq!(4, add_two(2));
    }

    #[test]
    fn add_three_and_two() {
        assert_eq!(5, add_two(3));
    }

    #[test]
    fn one_hundred() {
        assert_eq!(102, add_two(100));
    }
}

どれか1つのテストだけを実行したければ cargo test につづけてテスト関数を渡す

cargo test one_hundred

複数のテストを実行するようフィルターをかける

以下のようにコマンドをうつと add からはじまる複数のテストを実行する

cargo test add

特に要望のない限りテストを無視する

#[ignore]属性を付与する


#![allow(unused)]
fn main() {
#[test]
fn it_works() {
    assert_eq!(2 + 2, 4);
}

#[test]
#[ignore]
fn expensive_test() {
    // 実行に1時間かかるコード
    // code that takes an hour to run
}

ignore されているテストのみを実行したい場合 --ignored フラグを付与する

cargo test -- --ignored
Tatsushi KiryuTatsushi Kiryu

https://doc.rust-jp.rs/book-ja/ch11-03-test-organization.html

テストの体系化

単体テスト

  • 単体テストは、テスト対象となるコードと共に、srcディレクトリの各ファイルに置く
  • 慣習:各ファイルにtestsという名前のモジュールを作り、テスト関数を含ませ、 そのモジュールをcfg(test)で注釈する
  • #[cfg(test)] の注釈は、cargo build を走らせたときではなく、cargo test を走らせたときにだけ、テストコードをコンパイルし実行するよう指示するもの。
  • 単体テストは、コードと同じファイルにテストコードを置くため #[cfg(test)] で注釈する必要がある。
  • Rustの公開性規則により、非公開関数もテストすることが可能
Rust
pub fn add_two(a: i32) -> i32 {
    internal_adder(a, 2)
}

fn internal_adder(a: i32, b: i32) -> i32 {
    a + b
}

#[cfg(test)]
mod tests {
    use super::*;

    #[test]
    fn internal() {
        // pub で公開はしていないがテスト可能
        assert_eq!(4, internal_adder(2, 2));
    }
}

結合テスト

  • tests ディレクトリ
    • src と同じ階層に作成すると、Cargo はこのディレクトリに結合テストのファイルがあると認識する。
    • Cargo はそれぞれのファイルを個別のクレートとしてコンパイルする。
Rust
// testsディレクトリのテストはそれぞれ個別のクレートであるため、 各々ライブラリをインポートする必要があるため
extern crate adder;

#[test]
fn it_adds_two() {
    assert_eq!(4, adder::add_two(2));
}
このスクラップは2021/07/17にクローズされました