競技プログラミング用テスト


モチベーション

ある日AtCoderにて人のコードを眺めていたところテストを搭載したスニペットを使っている人がいました1

「かっこいい❤️」

自分でも作ってみました。

実装

fn main() {
    // 入力
    let n = input::<usize>();
    let a = input::<usize>();

    // 何らかの処理

    // 出力
    println!("{}", max_ave);
    println!("{}", cnt);
}

#[cfg(test)]
mod tests {
    #[test]
    fn tester() {
        // テストケースをここに書く
        // (入力, 期待する出力) 
        let inputs = [
("
5 2 2
1 2 3 4 5
", "
4.500000
1
"),

("
4 2 3
10 20 10 10
", "
15.000000
3
"),
       ];

        for (input, expected) in inputs.iter() {
            call(input, expected.to_string());
        }
    }

    fn call<T: AsRef<str>>(input: T, expected: String) {
        use std::process::{Command, Stdio};
        use std::io::{Read, Write};

        let mut cmd_echo = Command::new("echo")
            .arg(input.as_ref())
            .stdout(Stdio::piped())
            .spawn()
            .unwrap();
        let mut cmd_run = Command::new("cargo")
            .arg("run")
            .stdin(Stdio::piped())
            .stdout(Stdio::piped())
            .spawn()
            .unwrap();

        // 実行と標準出力の受け取り
        if let Some(ref mut stdout) = cmd_echo.stdout {
            if let Some(ref mut stdin) = cmd_run.stdin {
                let mut buf: Vec<u8> = Vec::new();
                stdout.read_to_end(&mut buf).unwrap();
                stdin.write_all(&buf).unwrap();
            }
        }

        let res = cmd_run.wait_with_output().unwrap().stdout;
        let res_string = String::from_utf8(res.to_vec()).unwrap();

        // convert '\n' into space
        let actual = res_string
            .replace("\n", " ")
            .trim_end_matches(' ')
            .trim_start_matches(' ')
            .to_owned();
        let expected = expected
            .replace("\n", " ")
            .trim_end_matches(' ')
            .trim_start_matches(' ')
            .to_owned();

        // split into a vector
        let actual = actual
            .split(' ')
            .map(|s| s.parse::<f64>().unwrap())
            .collect::<Vec<_>>();
        let expected = expected
            .split(' ')
            .map(|s| s.parse::<f64>().unwrap())
            .collect::<Vec<_>>();

        assert_eq!(actual, expected);
    }
}

実行

$ cargo test

工夫した点

先述したサイト1の実装はsolve関数に回答のための主要な処理を書き、main関数から呼び出していました。テストでは
solve関数を呼び出し、入力は全rustaceanがいいねしたであろうtanakhさんのinputマクロ2に手を加えたものを使用していました。

これに対し自作のテストはmain関数をテストから完全に独立させて自由に書けるようにしました。

コードについて

TopCoder や AtCoder は改行とスペースを区別しないらしいので改行コードを全てスペースに直しています。

テストケース

テストケースはinputs変数 Vec<()>に書いている
のですが、先述したサイト1のFireFox拡張を使えば
よりスッキリ書くことができると思います今度試してみます。

感想

シェルのパイプ処理がクレートなしで書けたのがよかったです
だけど少し複雑で大変だったです。

その他

こうした方がいいよなど思う所がありましたら是非コメントください❤️

参考