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