rustdoc におけるサンプルコードの扱いについて。

基本

サンプルは Markdown のコードブロック (``` から ``` まで) で記載される。

このようにして書かれたサンプルは単体テストと同じように自動テストの対象になる。

非表示行の指定

サンプル内のハッシュ ('#') とスペース (' ') で始まる行はドキュメント化時に隠される。

これにより例えば、use 文などのテストの準備段階のコードを見せないようにできる。

補足: 後述の TIPS ではこれをややトリッキーな形で活用している。

サンプル


/// Returns factorial of `n`.
/// 
/// # Examples
/// 
/// ```
/// # use rust_test::*;
/// assert_eq!(factorial(1), 1);
/// assert_eq!(factorial(2), 2);
/// assert_eq!(factorial(3), 6);
/// ```
pub fn factorial(n: u32) -> u32 {
    match n {
        0 | 1 => 1,
        _ => factorial(n - 1) * n
    }
}

言語

デフォルトの言語は rust である。この場合、言語名は省略できる。

rust 以外の言語では文書化テストは実行されない。

サンプル


/// Read csv format string.
///
/// # CSV Examples
/// 
/// ```csv
/// v11,v12,v13
/// v21,v22,v23
/// ```
/// 
/// # Examples
/// 
/// ```rust
/// # use rust_test::*;
/// let csv = read_csv("v11,v12,v13\nv21,v22,v23,");
/// assert_eq!(csv[1][2], "v23");
/// ```
pub fn read_csv(text: &str) -> Vec<Vec<&str>> {
    let mut ret = Vec::new();
    for line in text.split('\n') {
        let mut record = Vec::new();
        for value in line.split(',') {
            record.push(value);
        }
        ret.push(record);
    }

    ret
}

属性

属性をつけると文書化テストの挙動が変わる。

ここでは代表的なものをいくつか紹介する (他は公式資料を参照)。

補足: 言語に明示的に rust と記述した場合、言語と属性はカンマで区切る。

ignore

コードの無視

コンパイルも実行もされなくなる。ただし、Rust のコードとしては認識される。そのため、文書化テスト内でも構文強調が効く環境では、Rust 用の強調スタイルが使われる。

これは未完成のコードテンプレートの記述などに役立つ。


/// Executes function with log.
///
/// # Examples
///
/// ```ignore
/// # use rust_test::*;
/// exec_with_log("test", /* Your function */);
/// ```
pub fn exec_with_log<F: FnMut()>(name: &str, mut f: F) {
    println!("start `{name}`.");
    f();
    println!("end `{name}`.");
}

should_panic

パニック発生の確認

パニックの発生をもってテスト成功と判定されるようになる。

これはパニックが発生すべき状況の説明などに役立つ。

サンプル


/// Executes function with log.
///
/// # Examples
///
/// ```should_panic
/// # use rust_test::*;
/// divide(3, 0);
/// ```
pub fn divide(ntr: i32, dtr: i32) -> i32 {
    ntr / dtr
}

no_run

コンパイル成功のみ確認

コンパイルの成功のみが確認されるようになる。コードは実行されない。

これはテストを簡単に実行できない場合、例えば外部入出力を使う場合などに役立つ。

サンプル


use std::fs::File;
use std::io::{self, BufRead, BufReader};

/// Reads first line of `file`.
/// 
/// # Examples
/// 
/// ```no_run
/// # use rust_test::*;
/// # use std::fs::File;
/// #
/// # let file = File::open("example.txt").unwrap();
/// #
/// let ret = read_first_line(&file).unwrap();
/// assert_eq!(ret, Some("test".to_string()));
/// ```
pub fn read_first_line(file: &File) -> io::Result<Option<String>> {
    let mut reader = BufReader::new(file);
    let mut line = String::new();
    let cnt = reader.read_line(&mut line)?;
    let ret = if cnt == 0 { None } else { Some(line) };
    Ok(ret)
}

compile_fail

コンパイル失敗を確認

コンパイルの失敗をもってテスト成功と判定されるようになる。

これは代表的な誤ったコーディング例を示す場合などに役立つ。

サンプル


use std::ops::RangeInclusive;

/// Returns minimum and maximum element of the collection.
///
/// # Examples
///
/// ```
/// # use rust_test::*;
/// let ret = min_max([1, 2, 3].iter());
/// assert_eq!(*ret.as_ref().unwrap().start(), 1);
/// assert_eq!(*ret.as_ref().unwrap().end(), 3);
/// ```
///
/// # Illegal Examples
///
/// ```compile_fail
/// # use rust_test::*;
/// let ret = min_max([1, 2, 3]);
/// assert_eq!(*ret.as_ref().unwrap().start(), 1);
/// assert_eq!(*ret.as_ref().unwrap().end(), 3);
/// ```
pub fn min_max<'a, I>(values: I) -> Option<RangeInclusive<i32>>
where
    I: Iterator<Item = &'a i32>,
{
    let mut ret = None as Option<RangeInclusive<i32>>;
    for value in values {
        ret = ret
            .map(|x| *x.start().min(value)..=*x.end().max(value))
            .or(Some(*value..=*value));
    }

    ret
}

TIPS

? 演算子の適用

ダミー関数と非表示行を活用して、テストコードのルート階層で ? 演算子が使えるようにできる。なお、『? 演算子の使いどころ』で紹介するように、本来なら ? 演算子は戻り値の型が OptionResult の関数内でしか使えない。

サンプル


use std::collections::HashMap;

/// Returns key of the largest counter entry from map.
/// 
/// # Examples
/// 
/// ```
/// # use rust_test::*;
/// # use std::collections::HashMap;
/// # fn _ret_opt() -> Option<&'static str> {
/// let map = HashMap::from_iter([("a", 2), ("b", 4), ("c", 1)]);
/// let ret = max_count_key(&map)?;
/// assert_eq!(ret, "b");
/// # None
/// # }
/// ```
pub fn max_count_key<'a>(map: &HashMap<&'a str, i32>) -> Option<&'a str> {
    map.iter().max_by_key(|x| x.1).map(|x| *x.0)
}

ビルド確認の簡単化

ダミー関数と非表示行を活用して、ビルド確認のみのコードを no_run を使うよりも簡単化できる。この方法を使うと、テスト対象に渡すアイテムを揃えるコードが不要になる。

サンプル


use std::fs::File;
use std::io::{self, BufRead, BufReader};

/// Reads first line of `file`.
/// 
/// # Examples
/// 
/// ```
/// # use rust_test::*;
/// # use std::fs::File;
/// # fn _no_run(file: File) {
/// let ret = read_first_line(&file).unwrap();
/// assert_eq!(ret, Some("test".to_string()));
/// # }
/// ```
pub fn read_first_line(file: &File) -> io::Result<Option<String>> {
    let mut reader = BufReader::new(file);
    let mut line = String::new();
    let cnt = reader.read_line(&mut line)?;
    let ret = if cnt == 0 { None } else { Some(line) };
    Ok(ret)
}