kdnakt blog

hello there.

Rust dojo第77回を開催した

第77回です。前回はこちら。

kdnakt.hatenablog.com

[第77回の様子]

2023/05/10に第77回を開催した。GW中はお休みだったので2週間ぶり。

内容としてはRust By Example 日本語版21. テストの「21.2. ドキュメンテーションテスト」、「21.3. インテグレーションテスト」、「21.4. 開発中の依存関係」に取り組んだ。
参加者は自分を入れて6人。初参加のメンバーも来てくれた!

[学んだこと]

  • 21.2. ドキュメンテーションテスト
  • Rustではコードのドキュメント内にコードブロックをおくことで、テストとして利用できる
  • コードブロックは暗黙的にfn main() { ... }で囲まれており、テスト対象の関数はdoccomentsクレートとして呼び出すことができる
/// ```
/// let result = doccomments::add(2, 3);
/// assert_eq!(result, 5);
/// ```
pub fn add(a: i32, b: i32) -> i32 {
    a + b
}

// コメント中のコードブロックはcargo testでdoc-testsとして実行される
$ cargo test
running 0 tests

test result: ok. 0 passed; 0 failed; 0 ignored; 0 measured; 0 filtered out

   Doc-tests doccomments

running 1 tests
test src/lib.rs - add (line 7) ... ok

test result: ok. 1 passed; 0 failed; 0 ignored; 0 measured; 0 filtered out
  • パニックする結果のテストの場合はコードブロックにshould_panicをつけるとテストが通る
/// ```rust,should_panic
/// doccomments::div(10, 0);
/// ```
pub fn div(a: i32, b: i32) -> i32 {
    if b == 0 {
        panic!("Divide-by-zero error");
    }

    a / b
}
  • ドキュメンテーションテストではコードブロックの周囲にfn main() { ... }があるので、戻り値がユニット()でなければならない
  • 以下のように#でドキュメントからコードを隠すことで、テスト中に?演算子を利用できる
/// ```
/// # // 次の行はコンパイル時にのみ利用される
/// # fn try_main() -> Result<(), String> {
/// let res = doccomments::try_div(10, 2)?;
/// # Ok(())
/// # }
/// # fn main() { // 明示的にmain関数を配置
/// #    try_main().unwrap();
/// # }
/// ```
pub fn try_div(a: i32, b: i32) -> Result<i32, String> {
    if b == 0 {
        Err(String::from("Divide-by-zero"))
    } else {
        Ok(a / b)
    }
}
// src/lib.rs
// adderクレートの関数
pub fn add(a: i32, b: i32) -> i32 {
  a + b
}

// tests/integration_test.rs
// テスト対象のクレートをexternで宣言
extern crate adder;
#[test]
fn test_add() {
  assert_eq!(adder::add(3, 2), 5);
}

// cargo testでインテグレーションテストを実行できる
$ cargo test
running 0 tests

test result: ok. 0 passed; 0 failed; 0 ignored; 0 measured; 0 filtered out

     Running target/debug/deps/integration_test-bcd60824f5fbfe19

running 1 test
test test_add ... ok

test result: ok. 1 passed; 0 failed; 0 ignored; 0 measured; 0 filtered out

   Doc-tests adder

running 0 tests

test result: ok. 0 passed; 0 failed; 0 ignored; 0 measured; 0 filtered out
  • インテグレーションテスト間でコードを共有するには共通のモジュールに関数を定義する
// tests/common.rs
pub fn setup() {
    // テストに必要なファイル・ディレクトリの作成やサーバの起動といった準備を行うコードを記述する。
}

// tests/integration_test.rs
// テスト対象のクレートをexternで宣言
extern crate adder;

// 共通のモジュールをインポート
mod common;

#[test]
fn test_add() {
    common::setup();
    assert_eq!(adder::add(3, 2), 5);
}
// Cargo.toml
# 関係のない行は省略
# 1はクレートのバージョン
[dev-dependencies]
pretty_assertions = "1"
  • テストコードで利用する場合は次のようにする
// テストにのみ使うクレートをexternで宣言
#[cfg(test)]
#[macro_use]
extern crate pretty_assertions;

// テスト対象の関数
pub fn add(a: i32, b: i32) -> i32 {
    a + b
}

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

    #[test]
    fn test_add() {
        assert_eq!(add(2, 3), 5);
    }
}

[まとめ]

モブプログラミングスタイルでRust dojoを開催した。
インテグレーションテストとユニットテストの違いとか使い分けとか、テストの基本的な部分がやっぱり難しい気がする...。もう少しこの辺勉強しないとな。
今週のプルリクエストはこちら。

github.com