--- title: 第10回 11章テスト tags: Rust description: Rust勉強会第10回のスライド slideOptions: theme: white slideNumber: 'c/t' center: false transition: 'none' keyboard: true --- <style> /* basic design */ .reveal h1, .reveal h2, .reveal h3, .reveal h4, .reveal h5, .reveal h6, .reveal section, .reveal table, .reveal li, .reveal blockquote, .reveal th, .reveal td, .reveal p { font-family: 'Meiryo UI', 'Source Sans Pro', Helvetica, sans-serif, 'Helvetica Neue', 'Helvetica', 'Arial', 'Hiragino Sans', 'ヒラギノ角ゴシック', YuGothic, 'Yu Gothic'; text-align: left; line-height: 1.6; letter-spacing: normal; text-shadow: none; word-wrap: break-word; color: #444; } .reveal h1, .reveal h2, .reveal h3, .reveal h4, .reveal h5, .reveal h6 {font-weight: bold;} .reveal h1, .reveal h2, .reveal h3 {color: #2980b9;} .reveal th {background: #DDD;} .reveal section img {background:none; border:none; box-shadow:none; max-width: 95%; max-height: 95%;} .reveal blockquote {width: 90%; padding: 0.5vw 3.0vw;} .reveal table {margin: 1.0vw auto;} .reveal code {line-height: 1.2;} .reveal p, .reveal li {padding: 0vw; margin: 0vw;} .reveal .box {margin: -0.5vw 1.5vw 2.0vw -1.5vw; padding: 0.5vw 1.5vw 0.5vw 1.5vw; background: #EEE; border-radius: 1.5vw;} /* table design */ .reveal table {background: #f5f5f5;} .reveal th {background: #444; color: #fff;} .reveal td {position: relative; transition: all 300ms;} .reveal tbody:hover td { color: transparent; text-shadow: 0 0 3px #aaa;} .reveal tbody:hover tr:hover td {color: #444; text-shadow: 0 1px 0 #fff;} /* blockquote design */ .reveal blockquote { width: 90%; padding: 0.5vw 0 0.5vw 6.0vw; font-style: italic; background: #f5f5f5; } .reveal blockquote:before{ position: absolute; top: 0.1vw; left: 1vw; content: "\f10d"; font-family: FontAwesome; color: #2980b9; font-size: 3.0vw; } /* font size */ .reveal h1 {font-size: 5.0vw;} .reveal h2 {font-size: 4.0vw;} .reveal h3 {font-size: 2.8vw;} .reveal h4 {font-size: 2.6vw;} .reveal h5 {font-size: 2.4vw;} .reveal h6 {font-size: 2.2vw;} .reveal section, .reveal table, .reveal li, .reveal blockquote, .reveal th, .reveal td, .reveal p {font-size: 2.2vw;} .reveal code {font-size: 1.6vw;} /* new color */ .red {color: #EE6557;} .blue {color: #16A6B6;} /* split slide */ #right {left: -18.33%; text-align: left; float: left; width: 50%; z-index: -10;} #left {left: 31.25%; text-align: left; float: left; width: 50%; z-index: -10;} </style> <style> /* specific design */ .reveal h2 { padding: 0 1.5vw; margin: 0.0vw 0 2.0vw -2.0vw; border-left: solid 1.2vw #2980b9; border-bottom: solid 0.8vw #d7d7d7; } </style> <!-- --------------------------------------------------------------------------------------- --> # Rust勉強会 第10回 ## 11章 テスト ### 2020/10/09 堀川由人 スライド: https://hackmd.io/@hyrodium/HJ-iV8oIw --- ## 本日のメニュー 1. テストを書く 1. テストを走らせる 1. テストの体系化 --- ## テストを書く * 最初のテスト * テスト用のマクロ`assert`, `assert_eq`, .. * カスタムメッセージ * エラーの検証`should_panic` ---- ### はじめに ```shell $ cargo new adder --lib ``` 以下が生成されてる. これは`cargo new hello`のようにサンプルコードをつくってくれる. `--lib`は`src/lib.rs`を作ることを意味する. ```rust #[cfg(test)] mod tests { #[test] fn it_works() { assert_eq!(2 + 2, 4); } } ``` 簡単なテストが最初から入っている `#[cfg(test)]`は謎だが, 以下のが詳しいらしい(?) https://doc.rust-jp.rs/book/second-edition/ch11-03-test-organization.html#a%E3%83%86%E3%82%B9%E3%83%88%E3%83%A2%E3%82%B8%E3%83%A5%E3%83%BC%E3%83%AB%E3%81%A8cfgtest `#[test]`でテスト時に実行することを明示する. ---- ### 最初のテスト実行 テスト結果: `it_works`関数でのテストが成功してることが確認できる ![](https://i.imgur.com/xW0WrOT.png) ---- ### 他のテストの追加 ```rust #[cfg(test)] mod tests { #[test] fn it_works2() { assert_eq!(2 + 2, 4); assert_eq!(2 * 3, 6); } #[test] fn it_works3() { assert_ne!(0, 0 * 2); assert_ne!(1, 1 * 2); assert_ne!(2, 2 * 2); assert_ne!(3, 3 * 2); } } ``` `it_works2`で成功, `it_works3`で失敗. (どの行数でコケたかまでは教えてくれない.) ![](https://i.imgur.com/YaQPH9J.png) ---- ### 他のテスト用マクロ: `assert!` :heavy_check_mark: ```rust #[derive(Debug)] pub struct Rectangle { length: u32, width: u32, } impl Rectangle { pub fn can_hold(&self, other: &Rectangle) -> bool { self.length > other.length && self.width > other.width } } #[cfg(test)] mod tests { use super::*; #[test] fn larger_can_hold_smaller() { let larger = Rectangle { length: 8, width: 7, }; let smaller = Rectangle { length: 5, width: 1, }; assert!(larger.can_hold(&smaller)); } #[test] fn smaller_cannot_hold_larger() { let larger = Rectangle { length: 8, width: 7, }; let smaller = Rectangle { length: 5, width: 1, }; assert!(!smaller.can_hold(&larger)); } } ``` ![](https://i.imgur.com/Pa5V574.png) `assert!`は真偽値(`bool`)に対するテスト ---- ### 他のテスト用マクロ: `assert_eq!` :thinking_face: ```rust pub fn add_two(a: i32) -> i32 { a + 2 } #[cfg(test)] mod tests { use super::*; #[test] fn it_adds_two() { assert!(5 == add_two(2)); } } ``` ![](https://i.imgur.com/c3gX43Q.png) ---- ### 他のテスト用マクロ: `assert_eq!` :+1: ```rust pub fn add_two(a: i32) -> i32 { a + 2 } #[cfg(test)] mod tests { use super::*; #[test] fn it_adds_two() { assert_eq!(5, add_two(2)); } } ``` ![](https://i.imgur.com/TA3YZgO.png) `assert_ne!`は等しくない評価 ---- ### カスタムメッセージ :heavy_check_mark: ```rust pub fn greeting(name: &str) -> String { format!("Hello {}!", name) } #[cfg(test)] mod tests { use super::*; #[test] fn greeting_contains_name() { let result = greeting("Carol"); assert!(result.contains("Carol")); } } ``` ![](https://i.imgur.com/1ZjmPLC.png) ---- ### カスタムメッセージ :thinking_face: ```rust pub fn greeting(name: &str) -> String { format!("Hello {}!", name) } #[cfg(test)] mod tests { use super::*; #[test] fn greeting_contains_name() { let result = greeting("Carl"); assert!(result.contains("Carol")); } } ``` ![](https://i.imgur.com/TC4kj8B.png) ---- ### カスタムメッセージ :+1: ```rust pub fn greeting(name: &str) -> String { format!("Hello {}!", name) } #[test] fn greeting_contains_name() { let result = greeting("Carl"); assert!( result.contains("Carol"), "Greeting did not contain name, value was `{}`", result ); } ``` ![](https://i.imgur.com/QL1T9YY.png) ---- ### 必ずエラー`should_panic` :heavy_check_mark: ```rust #[derive(Debug)] pub struct Guess { value: u32, } impl Guess { pub fn new(value: u32) -> Guess { if value < 1 || value > 100 { //予想値は1から100の間でなければなりません panic!("Guess value must be between 1 and 100, got {}.", value); } Guess { value } } } #[cfg(test)] mod tests { use super::*; #[test] #[should_panic] fn greater_than_100() { Guess::new(200); } } ``` ![](https://i.imgur.com/ui84XT3.png) ---- ### 必ずエラー`should_panic` :heavy_check_mark: :heavy_check_mark: ```rust #[derive(Debug)] pub struct Guess { value: u32, } impl Guess { pub fn new(value: u32) -> Guess { if value < 1 { panic!( "Guess value must be greater than or equal to 1, got {}.", value ); } else if value > 100 { panic!( "Guess value must be less than or equal to 100, got {}.", value ); } Guess { value } } } #[cfg(test)] mod tests { use super::*; #[test] #[should_panic] fn greater_than_100() { Guess::new(200); } } ``` ![](https://i.imgur.com/yxTsLZT.png) ---- ### 必ずエラー`should_panic` :smile: ```rust #[derive(Debug)] pub struct Guess { value: u32, } impl Guess { pub fn new(value: u32) -> Guess { if value < 1 { panic!( "Guess value must be greater than or equal to 1, got {}.", value ); } else if value > 100 { panic!( "Guess value must be less than or equal to 100, got {}.", value ); } Guess { value } } } #[cfg(test)] mod tests { use super::*; #[test] #[should_panic] fn greater_than_100() { Guess::new(50); } } ``` ![](https://i.imgur.com/zbaBjMQ.png) ---- ### 必ずエラー`should_panic` :thinking_face: `greater_than_100`ではないが.. ```rust #[derive(Debug)] pub struct Guess { value: u32, } impl Guess { pub fn new(value: u32) -> Guess { if value < 1 { panic!( "Guess value must be greater than or equal to 1, got {}.", value ); } else if value > 100 { panic!( "Guess value must be less than or equal to 100, got {}.", value ); } Guess { value } } } #[cfg(test)] mod tests { use super::*; #[test] #[should_panic] fn greater_than_100() { Guess::new(0); } } ``` ![](https://i.imgur.com/7wkRhPA.png) ---- ### 必ずエラー`should_panic` :+1: ```rust #[derive(Debug)] pub struct Guess { value: u32, } impl Guess { pub fn new(value: u32) -> Guess { if value < 1 { panic!( "Guess value must be greater than or equal to 1, got {}.", value ); } else if value > 100 { panic!( "Guess value must be less than or equal to 100, got {}.", value ); } Guess { value } } } #[cfg(test)] mod tests { use super::*; #[test] #[should_panic(expected = "Guess value must be less than or equal to 100")] fn greater_than_100() { Guess::new(0); } } ``` ![](https://i.imgur.com/6i0ddFj.png) --- ## テストを走らせる * スレッド数の指定 * 関数の標準出力のキャプチャ設定 * テストの一部を実行 * 通常は無視するテスト ---- ### スレッド数の指定 ```shell $ cargo test -- --test-threads=1 ``` 標準では勝手に並列して処理してくれる 複数のテストで同一のtmpファイルを操作する場合に有効 ---- ### 関数の出力の制御 :thinking_face: ```rust #[allow(dead_code)] fn prints_and_returns_10(a: i32) -> i32 { println!("I got the value {}", a); 10 } #[cfg(test)] mod tests { use super::*; #[test] fn this_test_will_pass() { let value = prints_and_returns_10(4); assert_eq!(10, value); } #[test] fn this_test_will_fail() { let value = prints_and_returns_10(8); assert_eq!(5, value); } } ``` ![](https://i.imgur.com/BmfdhCe.png) ---- ### 関数の出力の制御 :+1: ```shell $ cargo test -- --nocapture ``` ![](https://i.imgur.com/AjjQTPQ.png) ---- ### テストの一部を実行(1/2) ```rust pub fn add_two(a: i32) -> i32 { a + 2 } #[cfg(test)] mod tests { use super::*; #[test] fn add_two_and_two() { assert_eq!(4, add_two(2)); } #[test] fn add_three_and_two() { assert_eq!(5, add_two(3)); } #[test] fn one_hundred() { assert_eq!(102, add_two(100)); } } ``` ![](https://i.imgur.com/NrDDCkb.png) ---- ### テストの一部を実行(2/2) `one_hundred`関数のみテスト ```shell $ cargo test one_hundred ``` 名前に`add`を関数のみテスト ```shell $ cargo test add ``` ![](https://i.imgur.com/zQaHAIr.png) ---- ### 通常は無視したいテスト ```rust #[test] fn it_works() { assert_eq!(2 + 2, 4); } #[test] #[ignore] fn expensive_test() { // code that takes an hour to run } ``` 詳細にテスト ```shell $ cargo test -- --ignored ``` ![](https://i.imgur.com/i19KFIW.png) --- ## テストの体系化 * 単体テスト * 結合テスト ---- ### 単体テスト >testsモジュールの#[cfg(test)]という注釈は、コンパイラにcargo buildを走らせた時ではなく、cargo testを走らせた時にだけ、 テストコードをコンパイルし走らせるよう指示します。 私の環境では`cargo build`でテストが走らないようでした(謎) ```rust #[cfg(test)] mod tests { #[test] fn it_works() { assert_eq!(2 + 2, 4); } } ``` ---- ### 非公開関数をテストする ```rust pub fn add_two(a: i32) -> i32 { internal_adder(a, 2) } fn internal_adder(a: i32, b: i32) -> i32 { a + b } #[cfg(test)] mod tests { use super::*; #[test] fn internal() { assert_eq!(4, internal_adder(2, 2)); } } ``` ![](https://i.imgur.com/ZIVwgOn.png) ---- ### 結合テスト(1/2) ``` ├── Cargo.lock ├── Cargo.toml ├── src │   └── lib.rs ├── tests │   └── integration_test.rs ... ``` `integration_test.rs` ```rust extern crate adder; #[test] fn it_adds_two() { assert_eq!(4, adder::add_two(2)); } ``` ---- ### 結合テスト(2/2) ```shell $ cargo test ``` ![](https://i.imgur.com/FNl8ic7.png) それぞれ単体テスト、結合テスト、ドックテスト ---- ### 結合テスト内のサブモジュール (テストファイルが複数ある場合など) ### バイナリクレート用の結合テスト (ちょっとよく分かりませんでした) --- ## まとめ * Rustはテストのための道具が充実してる * ちゃんとテスト書こう!