到達点は、保存を含む振る舞いを繰り返し検証し、実行物を渡せることです。前提は第17・18回です。
実際の保存境界を通す
次をsrc/main.rsの末尾へ追加します。既存のmainを置き換えず、テスト用モジュールを足してください。
#[cfg(test)]
mod tests {
use super::*;
fn args(path: &Path, command: &str, value: &str) -> Vec<String> {
vec![path.to_string_lossy().into_owned(), command.to_owned(), value.to_owned()]
}
#[test]
fn rejects_invalid_model() {
assert!(Task::new(0, "復習").is_err());
assert!(Task::new(1, " ").is_err());
assert!(Task::new(1, "a\nb").is_err());
}
#[test]
fn persists_and_preserves_failed_updates() -> Result<(), Box<dyn Error>> {
let dir = tempfile::tempdir()?;
let path = dir.path().join("tasks.json");
run(&args(&path, "add", "復習"))?;
run(&args(&path, "done", "1"))?;
run(&args(&path, "done", "1"))?;
assert!(load(&path)?[0].done);
let before = std::fs::read(&path)?;
assert!(run(&args(&path, "done", "999")).is_err());
assert_eq!(std::fs::read(&path)?, before);
std::fs::write(&path, "broken")?;
assert!(run(&args(&path, "add", "新規")).is_err());
assert_eq!(std::fs::read_to_string(&path)?, "broken");
Ok(())
}
}
cargo testで二つのテストが成功します。tempdirでテストごとに別の領域を作り、終了時に片付けます。通常の利用者のtasks.jsonをテストで書き換えません。
配布時にも前提を明記する
cargo build --release で最適化された実行ファイルを作ります。通常はtarget/releaseに置かれ、Windowsでは拡張子exeが付きます。同じOSやCPU向けにビルドした実行物が、すべての環境でそのまま動くとは限りません。
READMEには必要なツールチェーン、コマンド、保存形式、終了コード、同時更新非対応、保存上限を記録します。Cargo.tomlとCargo.lockを追跡し、生成物と個人の保存データをGitへ入れないようにします。CIではfmt、clippy、testを基本にし、必要なら対象OSごとに検証します。
性能を調べるときはdebugビルドとreleaseビルドを混ぜて比較しないようにします。ただし最適化でテストを省略してよいわけではありません。桁あふれなどの意味はchecked系操作で明示しておくことが再現性にも役立ちます。
練習と解答
練習:ID重複、未知の保存バージョン、ID最大値を持つファイルのテストを追加します。
解答の要点:重複と未知の版はloadで失敗、最大値の後のaddは発番で失敗し、いずれも元のバイト列を維持することを確認します。エラーになった事実だけでなく、データを壊していないことが重要です。
公式資料
Cargoのプロファイルとテストを参照できます。
Rust全20回の目次 | 前の回 | 次の回