---
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はテストのための道具が充実してる
* ちゃんとテスト書こう!