【Rust】チュートリアルが終わったのでFizzBuzzを試しに書いてみた。

# はじめに

様々な方々の記事を拝見していてRustに興味が湧いた僕。
Rustの[チュートリアル](https://doc.rust-jp.rs/book/second-edition/)を読み進め、ようやく一通り読み終わることができました。
せっかくなので復習がてら何か作成してみよう！ということで、よくある題材かとは思いますがFizzBuzzを作成してみることにします。
[最終的な作成物はこちらになります。](https://github.com/h-ueno2/rust_fizzbuzz)

# FizzBuzzとは
念のため、FizzBuzzについて簡単に説明します。といってもWikipediaから貼るだけですが...( ¯•ω•¯ )

[Fizz Buzz](https://ja.wikipedia.org/wiki/Fizz_Buzz)


> 最初のプレイヤーは「1」と数字を発言する。次のプレイヤーは直前のプレイヤーの次の数字を発言していく。ただし、3で割り切れる場合は「Fizz」（Bizz Buzzの場合は「Bizz」）、5で割り切れる場合は「Buzz」、両者で割り切れる場合（すなわち15で割り切れる場合）は「Fizz Buzz」（Bizz Buzzの場合は「Bizz Buzz」）を数の代わりに発言しなければならない。発言を間違えた者や、ためらった者は脱落となる。  


  
 ~~Wikipediaに頼った方が逆に難解なんじゃね...？~~ 端的にいうと、以下な出力がされるイメージです。

* 3で割り切れる数なら`Fizz`
* 5で割り切れる数なら`Buzz`
* 3と5の両方で割り切れる数なら`FizzBuzz`
* 上記以外であれば数字

```
1
2
Fizz
4
Buzz
Fizz
7
8
Fizz
Buzz
11
Fizz
13
14
FizzBuzz
```

# Rustで書いてみる
そんなわけでRustでFizzBuzzを書いていきます。
適当なフォルダにプロジェクトを作成します。

```shell
cargo new --bin fizz_buzz
```

## まずは普通に書いてみる
とりあえず普通？というかざっくり書いてみます。
せっかくのRustなので`match`式とガードを使用してみました。

```rust:src/main.rs
fn main() {
    for i in 1..101 {
        let answer = match i {
            i if i % 15 == 0 => String::from("FizzBuzz"),
            i if i % 3 == 0 => String::from("Fizz"),
            i if i % 5 == 0 => String::from("Buzz"),
            _ => i.to_string(),
        };
        println!("{}",answer);
    }
}
```

とりあえずこれで完成ではありますが、少々あっさりして物足りない感が否めない。。。
せっかくなので、これをベースとして更に手を加えてみます。

## モジュールを分けてみる
ロジック部分を別モジュールに分けてみます。

```rust:src/lib.rs
/// `Manager`は`FizzBuzz`ゲームを管理します。
pub struct Manager {
    fizz_num: u32,
    buzz_num: u32,
    fizz_buzz_num: u32,
    max: u32,
}

impl Manager {
    /// 新しい`Manager`を作成します。引数に応じてルールを設定します。
    ///
    /// # Argments
    ///
    /// * `fizz_num` - 値を`Fizz`とするための基準値
    /// * `buzz_num` - 値を`Buzz`とするための基準値
    /// * `max` - FizzBuzzゲームを繰り返し行う最大値
    pub fn new(fizz_num: u32, buzz_num: u32, max: u32) -> Manager {
        let fizz_buzz_num = fizz_num * buzz_num;
        Manager {
            fizz_num,
            buzz_num,
            fizz_buzz_num,
            max,
        }
    }

    /// 与えられた数値から回答を作成して返却します。
    ///
    /// # Arguments
    ///
    /// * `num` - 対象の数値
    ///
    /// # Return value
    ///
    /// 返却値は`num`によって以下の値をとります。
    ///
    /// * `fizz_num`で割り切れる数字であった場合は`Fizz`を返却します。
    /// * `buzz_num`で割り切れる数字であった場合は`Buzz`を返却します。
    /// * `fizz_num`及び`buzz_num`の両方で割り切れる値であった場合は`FizzBuzz`を返却します。
    /// * 上記以外の値で割り切れる値であった場合は`num`を返却します。
    pub fn make_answer(&self, num: u32) -> String {
        match num {
            num if num % self.fizz_buzz_num == 0 => String::from("FizzBuzz"),
            num if num % self.fizz_num == 0 => String::from("Fizz"),
            num if num % self.buzz_num == 0 => String::from("Buzz"),
            _ => num.to_string(),
        }
    }

    /// 1からmaxまでの値を対象にFizzBuzzを実行します。
    pub fn run(&self) {
        for i in 1..=self.max {
            println!("{}", self.make_answer(i));
        }
    }
}
```
FizzBuzzのゲーム部分を司ってもらおうということで`Manager`構造体を作成しました。

* Fizzとなる数字`fizz_num`、Buzzとなる数字`buzz_num`、繰り返す最大数`max`を設定できるようにコンストラクタを作成してみました。
* `make_answer()`メソッドにて数字がFizzなのかBuzzなのかFizzBuzzなのか判定します。
* `run()`メソッドで`1 〜 max`までの値を`make_answer`に渡し、判定結果を出力します。

なお、このManager構造体を使用するmain関数は以下のような形となります。

```rust:main.rs
extern crate fizz_buzz;

use fizz_buzz::Manager;

fn main() {
    Manager::new(3, 5, 100).run();
}
```

さてさてさて、これで`main.rs`から処理を外出しできました。
せっかくなので単体テストも作ってしまいます。

## 単体テストを作る

[チュートリアル](https://doc.rust-jp.rs/book/second-edition/ch11-03-test-organization.html)や[色々](https://teratail.com/questions/208572)調べてみたところ、Rustでは単体テストのテストコードはテスト対象のモジュールと同一ファイル内に記載することがお作法らしい？
`tests`ディレクトリにテストコードファイルを置く（例：`src/sample.rs`に対するテストコードを`tests/sample.rs`に配置など）のはモジュール間の結合テストコードである場合との事です。
上記に従って、`make_answer()`メソッドのテストコードを`src/lib.rs`に作成します！以下となります！ドン！

```rust:src/lib.rs
#[cfg(test)]
mod make_answer {
    use super::*;

    #[test]
    fn return_fizz() {
        let manager = Manager::new(3, 5, 10);
        let expected = String::from("Fizz");
        assert_eq!(manager.make_answer(3), expected);
        assert_eq!(manager.make_answer(6), expected);
    }

    #[test]
    fn return_buzz() {
        let manager = Manager::new(3, 5, 10);
        let expected = String::from("Buzz");
        assert_eq!(manager.make_answer(5), expected);
        assert_eq!(manager.make_answer(10), expected);
    }

    #[test]
    fn return_fizz_buzz() {
        let manager = Manager::new(3, 5, 10);
        let expected = String::from("FizzBuzz");
        assert_eq!(manager.make_answer(15), expected);
        assert_eq!(manager.make_answer(30), expected);
    }

    #[test]
    fn return_number() {
        let manager = Manager::new(3, 5, 10);
        assert_eq!(manager.make_answer(2), String::from("2"));
        assert_eq!(manager.make_answer(4), String::from("4"));
    }
}
```

# ここまでやってみて

* 本当は`run()`メソッドの実行についてもテストした方が良いのだろうなぁ…と思いつつも、返り値がなく標準出力するだけメソッドとして作ってしまったのでテストしづらい…これに関しては処理の仕方を変えた方が良さそう。
* 他の方々がRustで作成された`FizzBuzz`を拝見すると、機能を色々駆使されていて非常に面白いし勉強になる。
 + [FizzBuzzで学ぶenumとDisplay, Fromトレイト](https://qiita.com/gyu-don/items/aa61c5bbc1017bd3171c) - FizzBuzzの判定結果にenumを使用するだけでなく、そのenumにDisplay, Fromトレイトを実装されていてCoolである…取り入れてみよう！
 + [Re:FizzBuzzから始めるRust生活](https://qiita.com/hinastory/items/543ae9749c8bccb9afbc) - `１日にひとつのFizzBuzzを紹介するというスタイル`との通り、1日毎にRustの様々な機能が実例として紹介されているので非常に勉強になります（~~本記事執筆中に拝見して、投稿するの恥ずかしくなったのは内緒~~）。5日目時点で`FizzBuzz`の判定をタプルを使用してシンプルでエレガントになったmatch式に衝撃。

簡単なFizzBuzzをベースとして、色々弄っていくのは面白いし、新しく学んだ言語の文法や機能の復習として非常に良いですね。他の方々のFizzBuzzを参考にどんどん魔改造していき、この記事に追記していきたいなぁと思います。

## 追記：FizzBuzzの実行結果をテストできるようにしてみた
FizzBuzzを実行する`run()`メソッドですが、標準出力するだけのメソッドとして作ってしまったので、単純にテストがしづらい状態となっていました。
実行結果の文字列を返却するメソッドに修正し、自動テストできるようにしてしまいたいと思います。

```rust:src/lib.rs
impl Manager {
    // ~~ 省略 ~~

    /// 1からmaxまでの値を対象にFizzBuzzを実行します。
    /// 
    /// # Return value
    /// 
    /// `max`までのFizzBuzzの実行結果を返却します。
    /// * 値ごとに改行されます。
    /// 
    pub fn run(&self) -> String {
        let mut result = String::from("");
        for i in 1..=self.max {
            let answer = self.make_answer(i);
            result = match i {
                1 => answer,
                _ => format!("{}\n{}", result, answer),
            }
        }
        result
    }
}
// ~~ 省略 ~~

#[cfg(test)]
mod run {
    use super::*;

    #[test]
    fn result() {
        let manager = Manager::new(3,5,10);
        let expected = "1
2
Fizz
4
Buzz
Fizz
7
8
Fizz
Buzz";
        assert_eq!(manager.run(), expected);
    }
}
```