やってみる

アウトプットすべく己を導くためのブログ。その試行錯誤すらたれ流す。

Rustでテスト駆動開発

 こんな感じでやる。

成果物

参考

前回からの続き

  1. Rustでコマンドライン引数を受け取る
  2. Rustのファイル読込
  3. Rustでリファクタリング(モジュール性とエラー処理の向上)

テスト駆動(TDD)

  1. 失敗するテストを書き、想定通りの理由で失敗することを確かめる
  2. 十分な量のコードを書くか変更して新しいテストを通過するようにする
  3. 追加または変更したばかりのコードをリファクタリングし、テストが通り続けることを確認する
  4. 手順1から繰り返す

1. 失敗するテストを書く

src/lib.rs

pub fn search<'a>(query: &str, contents: &'a str) -> Vec<&'a str> {
    vec![]
}
#[cfg(test)]
mod test {
    use super::*;
    #[test]
    fn one_result() {
        let query = "duct";
        let contents = "\
Rust:
safe, fast, productive.
Pick three.";
        assert_eq!(
            vec!["safe, fast, productive."],
            search(query, contents)
        );
    }
}
$ cargo test
...
---- test::one_result stdout ----
thread 'test::one_result' panicked at 'assertion failed: `(left == right)`
  left: `["safe, fast, productive."]`,
 right: `[]`', src/lib.rs:39:9

2. 成功するコードを書く

pub fn search<'a>(query: &str, contents: &'a str) -> Vec<&'a str> {
    for line in contents.lines() {
        // 行に対して何かする
    }
}
  • str.lines()で改行ごとに分解する

各行を検索する

pub fn search<'a>(query: &str, contents: &'a str) -> Vec<&'a str> {
    for line in contents.lines() {
        if line.contains(query) {
            // 行に対して何かする
        }
    }
}
  • str.containts()で指定した文字列が含まれているか判定する

合致した行を保存する

pub fn search<'a>(query: &str, contents: &'a str) -> Vec<&'a str> {
    let mut results = Vec::new();
    for line in contents.lines() {
        if line.contains(query) {
            results.push(line);
        }
    }
    results
}

 contentsqueryが含まれているなら、その行テキストをVectorに追加する。

 戻り値について。Vec自体は所有権をムーブしている。Vec中身のstrは引数contentsの一部である。それは参照であるためライフタイムの指定が必要。ライフタイム省略規則だけでは特定できないため、ライフタイム指定子により指定する必要がある。Vec中身のstrは引数contentsの一部なので、ライフタイムも同じである。それをライフタイム指定子によって指定する。すなわち'a

テスト実行

 成功。

$ cargo test
   Compiling minigrep v0.1.0 (/tmp/work/Rust.Minigrep.TDD.20190701120916/src/1/minigrep)
    Finished dev [unoptimized + debuginfo] target(s) in 5.68s
     Running /tmp/work/Rust.Minigrep.TDD.20190701120916/src/1/minigrep/target/debug/deps/minigrep-59cba27a4e3f1289

running 1 test
test test::one_result ... ok

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

     Running /tmp/work/Rust.Minigrep.TDD.20190701120916/src/1/minigrep/target/debug/deps/minigrep-f69c65fea4d1414e

running 0 tests

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

   Doc-tests minigrep

running 0 tests

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

run()内でsearch()を呼ぶ

pub fn run(config: Config) -> Result<(), Box<Error>> {
    let mut f = File::open(config.filename)?;
    let mut contents = String::new();
    f.read_to_string(&mut contents)?;
    for line in search(&config.query, &contents) { println!("{}", line); }
    Ok(())
}

 1件も合致しないとき。

$ cargo run AAA poem.txt   Compiling minigrep v0.1.0 (/tmp/work/Rust.Minigrep.TDD.20190701120916/src/2/minigrep)
...
query: AAA
filename: poem.txt

 いくつか合致するとき。

$ cargo run the poem.txt
...
query: the
filename: poem.txt
Then there's a pair of us - don't tell!
To tell your name the livelong day
$ cargo run body poem.txt
...
query: body
filename: poem.txt
I'm nobody! Who are you?
Are you nobody, too?
How dreary to be somebody!

 成功! これで指定した文字列に部分一致する行を抜き出すことができた。

対象環境

$ uname -a
Linux raspberrypi 4.19.42-v7+ #1219 SMP Tue May 14 21:20:58 BST 2019 armv7l GNU/Linux

前回まで