changed 5 years ago
Linked with GitHub

Rust勉強会 第12回

12章 入出力プロジェクト 後半

2020/10/23 岡本拓海


本日のメニュー

  1. テスト駆動開発でのライブラリの機能を開発する
  2. 環境変数を取り扱う
  3. 標準出力ではなく、標準エラーにエラーメッセージを書き込む

プレゼン20分くらい+議論10分位で18:30前後終了を目指します。


テスト駆動開発でライブラリの機能を開発する

前回

minigrepクレートを作成してちょっとしたRustのプロジェクトを作りました。

use std::error::Error;
use std::fs::File;
use std::io::prelude::*;

pub struct Config {
    pub query: String,
    pub filename: String,
}

impl Config {
    pub fn new(args: &[String]) -> Result<Config, &'static str> {
        // --snip--
    }
}

pub fn run(config: Config) -> Result<(), Box<Error>> {
    // --snip--
}

テストを書いてみよう!!

  • #[cfg(test)]でテストモジュールの定義
  • #[test] でユニットテストを記述できます
#[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)
        );
    }
}

search関数の最低限の実装

pub fn search<'a>(query: &str, contents: &'a str) -> Vec<&'a str> { vec![] }

テストを走らせてみよう

$ cargo test
   Compiling minigrep v0.1.0 (file:///projects/minigrep)
--warnings--
    Finished dev [unoptimized + debuginfo] target(s) in 0.43 secs
     Running target/debug/deps/minigrep-abcabcabc

running 1 test
test test::one_result ... FAILED

failures:

---- test::one_result stdout ----
        thread 'test::one_result' panicked at 'assertion failed: `(left ==
right)`
left: `["safe, fast, productive."]`,
right: `[]`)', src/lib.rs:48:8
 note: Run with `RUST_BACKTRACE=1` for a backtrace.


failures:
    test::one_result

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

error: test failed, to rerun pass '--li

想定どおりテストは失敗しましたね!!


テストを通過させるコードを書く

実装する操作は以下の通り

  • 中身を各行ごとに繰り返す。
  • 行にクエリ文字列が含まれるか確認する。
  • するなら、それを返却する値のリストに追加する。
  • しないなら、何もしない。
  • 一致する結果のリストを返す。

テストを通過するコード

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
}

再度テスト

$ cargo test
--snip--
running 1 test
test test::one_result ... ok

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

成功しました!!


環境変数を取り扱う

環境変数を取り扱うのにはstd::envを使用します

let case_sensitive = env::var("CASE_INSENSITIVE").is_err();

環境変数を使用した場合のテスト

mod test {
    use super::*;

// ---snip---

    #[test]
    fn case_insensitive() {
        let query = "rUsT";
        let contents = "\
Rust:
safe, fast, productive.
Pick three.
Trust me.";

        assert_eq!(
            vec!["Rust:", "Trust me."],
            search_case_insensitive(query, contents)
        );
    }
}

search_case_insensitive関数を実装する

pub fn search_case_insensitive<'a>(query: &str, contents: &'a str) -> Vec<&'a str> {
    let query = query.to_lowercase();
    let mut results = Vec::new();

    for line in contents.lines() {
        if line.to_lowercase().contains(&query) {
            results.push(line);
        }
    }

    results
}

実行する

$ CASE_INSENSITIVE=1 cargo run to poem.txt
    Finished dev [unoptimized + debuginfo] target(s) in 0.0 secs
     Running `target/debug/minigrep to poem.txt`
Are you nobody, too?
How dreary to be somebody!
To tell your name the livelong day
To an admiring bog!

標準出力ではなく、標準エラー出力にエラーメッセージを書き込む

プログラムの正常な出力以外のエラーなどが表示されることを避けたい場合があります。

例えば、リダイレクトで結果をファイルに保存した場合等

現行のプログラムではpanicした内容も出力されてしまうのでエラー出力に出力しましょう。


eprintln!マクロで標準エラー出力に書き込む

eprintln!マクロを使うことで標準エラー出力に書き込むことができます。

eprintln!("This is some error!! caused by {}", err);
fn main() { let args: Vec<String> = env::args().collect(); let config = Config::new(&args).unwrap_or_else(|err| { eprintln!("Problem parsing arguments: {}", err); process::exit(1); }); if let Err(e) = minigrep::run(config) { eprintln!("Application error: {}", e); process::exit(1); } }

参考資料

https://doc.rust-jp.rs/book-ja/ch12-00-an-io-project.html


ご清聴ありがとうございました

Select a repo