書籍「テスト駆動開発」をRustで書く #2 （chapter4...6）

# はじめに
これは[Rustその2 Advent Calendar 2017](https://qiita.com/advent-calendar/2017/rust-lang)の9日目の記事です。

[@Khigashiguchi](https://twitter.com/Khigashiguchi)です。
著書「テスト駆動開発」をrustで書いていきます。
「[テスト駆動開発](https://www.amazon.co.jp/%E3%83%86%E3%82%B9%E3%83%88%E9%A7%86%E5%8B%95%E9%96%8B%E7%99%BA-Kent-Beck/dp/4274217884)」は、Kent Beck著、和田 卓人さん翻訳の書籍です。この著書では、第1章から第17章にかけて、「多国通貨」というテーマでテスト駆動開発のプロセスを写経しながら体感することができる構成になっています。
本記事は、[書籍「テスト駆動開発」をRustで書く #1 （chapter1...3）](https://qiita.com/Khigashiguchi/items/dfc382d9d2f988522721)の続編になります。

# 目次

- 概要
- 前回のおさらい
- 第4章 意図を語るテスト
- 第5章 原則をあえて破るとき
- 第6章 テスト不足に気づいたら
- 最後に

# 概要
本ページでは、第4章から第6章までを進めていきます。第7章以降は、続編にて更新していきます。

rustのユニットテスト自体の書き方については、[RustでUnit Testを書く方法](https://qiita.com/Khigashiguchi/items/32c03c6886a4a81605d5)に簡単に書かせていただいています。

# 前回のおさらい
## 前回時点のToDoList
前回終了時点で、実装タスクは以下のステータスになっています。

- [ ]$5 + 10CHF = $10
- [x]$5 * 2 = $10
- [ ]amountをprivateにする
- [x]Dollarの副作用どうする
- [ ]Moneyの丸め処理どうする
- [x]equals()の実装
- [ ]hashCode()の実装

## 前回時点でのコード
https://github.com/Khigashiguchi/rust_sutras_tdd/blob/chapter/3/src/lib.rs

# 第4章 意図を語るテスト
前回書いたコードにて、times()はDollarを返すことになった。テストコードでもその仕様に明示的にわかるようにしたい。

```rust
    fn test_multiplication() {
        let five = Dollar::new(5);
        let mut product = five.times(2);
        assert_eq!(10, product.amount);
        product = five.times(3);
        assert_eq!(15, product.amount);
    }
```

## テストを修正する
Dollar同士を比較するequal()を実装したのでそのメソッドを利用します。

```rust
-        assert_eq!(Dollar::new(10).amount, product.amount);
+        assert!(Dollar::new(10).equals(product));
```
[差分Commit](https://github.com/Khigashiguchi/rust_sutras_tdd/commit/4daf840c0f9f74db3b3570a3004ff84ee5435ca0)

上記の修正の結果、テストコードに不要な変数ができましたのでリファクタリングします。

```rust
-        let mut product = five.times(2);
-        assert!(Dollar::new(10).equals(product));
-        product = five.times(3);
-        assert!(Dollar::new(15).equals(product));
+        assert!(Dollar::new(10).equals(five.times(2)));
+        assert!(Dollar::new(15).equals(five.times(3)));
```
[差分Commit](https://github.com/Khigashiguchi/rust_sutras_tdd/commit/09062b9ed51f70a14e672ef7fb5144be1e210045)

テストが通ればOKです！

## 最終的なコード
本章終了時点のコードは以下です。

```rust
#[derive(Debug)]
pub struct Dollar {
    amount: u32
}

impl Dollar {
    pub fn new(amount: u32) -> Dollar {
        Dollar { amount: amount }
    }
    pub fn times (&self, multiplier: u32) -> Dollar {
        Dollar { amount: self.amount * multiplier }
    }
    pub fn equals (&self, target: Dollar) -> bool {
        self.amount == target.amount
    }
}

#[cfg(test)]
mod tests {
    use super::*;
    #[test]
    fn test_multiplication() {
        let five = Dollar::new(5);
        assert!(Dollar::new(10).equals(five.times(2)));
        assert!(Dollar::new(15).equals(five.times(3)));
    }
    #[test]
    fn test_equality() {
        assert!(Dollar::new(5).equals(Dollar::new(5)));
        assert!(!Dollar::new(5).equals(Dollar::new(6)));
    }
}
```
https://github.com/Khigashiguchi/rust_sutras_tdd/blob/chapter/4/src/lib.rs

## この時点でのToDoList

- [ ]$5 + 10CHF = $10
- [x]$5 * 2 = $10
- [x]amountをprivateにする （すでにprivateだった...）
- [x]Dollarの副作用どうする
- [ ]Moneyの丸め処理どうする
- [x]equals()の実装
- [ ]hashCode()の実装

# 第5章 原則をあえて破るとき
現在、Dollarは表現できているが、多国籍通貨での計算を実現するにあたり他の通貨も表現しないといけない。
第一歩として、フラン（Franc）での足し算をテストから書いていく。

## ToDoListを追加
- [x]5CHF * 2 = 10CHF

## テストを書く
すでに書いている``test_multiplication``をそのまま流用してfrancの足し算を行うテストを書きます。

```rust
    #[test]
    fn test_franc_multiplication() {
        let five = Franc::new(5);
        assert!(Franc::new(10).equals(five.times(2)));
        assert!(Franc::new(15).equals(five.times(3)));
    }
```

## テストを通す実装する
DollarをコピーしてFrancを作ります。重複を生み出す大罪だとしても、まずはテストを通してリファクタリングしていく道を行きます。

```rust
pub struct Franc {
    amount: u32
}
impl Franc {
    pub fn new (amount: u32) -> Franc {
        Franc { amount: amount }
    }
    pub fn times (&self, multiplier: u32) -> Franc {
        Franc {amount: self.amount * multiplier }
    }
    pub fn equals (&self, target: Franc) -> bool {
        self.amount == target.amount
    }
}
```
[差分Commit](https://github.com/Khigashiguchi/rust_sutras_tdd/commit/830de6d0365067b95cbf5615dc1cb23b5cbd4c23)

無事テストが通りました。
今回の大罪を犯した結果生まれた新たなToDoListを追加して本章は終了です。

## 最終的なコード
本章終了時点のコードは以下です。

```rust
#[derive(Debug)]
pub struct Dollar {
    amount: u32
}

pub struct Franc {
    amount: u32
}

impl Dollar {
    pub fn new (amount: u32) -> Dollar {
        Dollar { amount: amount }
    }
    pub fn times (&self, multiplier: u32) -> Dollar {
        Dollar { amount: self.amount * multiplier }
    }
    pub fn equals (&self, target: Dollar) -> bool {
        self.amount == target.amount
    }
}

impl Franc {
    pub fn new (amount: u32) -> Franc {
        Franc { amount: amount }
    }
    pub fn times (&self, multiplier: u32) -> Franc {
        Franc {amount: self.amount * multiplier }
    }
    pub fn equals (&self, target: Franc) -> bool {
        self.amount == target.amount
    }
}

#[cfg(test)]
mod tests {
    use super::*;
    #[test]
    fn test_multiplication() {
        let five = Dollar::new(5);
        assert!(Dollar::new(10).equals(five.times(2)));
        assert!(Dollar::new(15).equals(five.times(3)));
    }
    #[test]
    fn test_equality() {
        assert!(Dollar::new(5).equals(Dollar::new(5)));
        assert!(!Dollar::new(5).equals(Dollar::new(6)));
    }
    #[test]
    fn test_franc_multiplication() {
        let five = Franc::new(5);
        assert!(Franc::new(10).equals(five.times(2)));
        assert!(Franc::new(15).equals(five.times(3)));
    }
}
```
https://github.com/Khigashiguchi/rust_sutras_tdd/blob/chapter/5/src/lib.rs

## 今時点でのToDoList

- [ ]$5 + 10CHF = $10
- [x]$5 * 2 = $10
- [x]amountをprivateにする（すでにprivateだった）
- [x]Dollarの副作用どうする
- [ ]Moneyの丸め処理どうする
- [x]equals()の実装
- [ ]hashCode()の実装
- [ ]nullとの等価性比較
- [ ]他のオブジェクトとの等価性比較
- [x]5CHF * 2 = 10CHF
- [ ]DollarとFrancの重複
- [ ]equalsの一般化
- [ ]timesの一般化

# 第6章 テスト不足に気づいたら
第5章で、重複したDollarとFrancを作ってしまいました。綺麗にリファクタリングするにあたり、概念上親に当たるMoneyオブジェクトを作る方法を行ってみる。

## Moneyオブジェクトを実装する
すでにテストコードを前回書いているもののリファクタリングになるため、実装から始めます。

- DollarやFrancが継承するTraitを作る

```rust
trait MoneyTrait {
    fn new(amount: u32) -> Money;
    fn times(&self, multiplier: u32) -> Money;
    fn equals(&self, target: Money) -> bool;
}
```

- Dollarをtraitに合わせて修正する

```rust
impl MoneyTrait for Dollar {
    fn new (amount: u32) -> Money {
        Money { amount: amount }
    }
    fn times (&self, multiplier: u32) -> Money {
        Money {amount: self.amount * multiplier }
    }
    fn equals (&self, target: Money) -> bool {
        self.amount == target.amount
    }
}
```
Dollarでは、Moneyを返り値として渡すように修正しています。


- Moneyオブジェクトを作る

```rust
struct Money {
    amount: u32
}

impl Money {
    fn times (&self, multiplier: u32) -> Money {
        Money {amount: self.amount * multiplier }
    }
    fn equals(&self, target: Money) -> bool {
        self.amount == target.amount
    }
}
```

[差分Commit](https://github.com/Khigashiguchi/rust_sutras_tdd/pull/7/commits/7fc6947262444ac58f928a74439892073f7271a5)

これで一回テストが通るはずです。

差分Commit内にはMoneyというTraitを削除している記述があります。
これは私自身が最初Traitを利用した結果コード量の削減に繋がらなさそうだと考え、別オブジェクトをreturnする上記の方法に変更した経緯で発生している差分になります。

## 重複を削除する
「Moneyオブジェクトを実装する」にて、times()とequals()はMoney内のものを使うようになったので、Dollarからメソッドを引き上げます。

[差分Commit](https://github.com/Khigashiguchi/rust_sutras_tdd/pull/7/commits/14efebc65da3d0c20cf3a6fa488304af67cabdc0)

## Francも同じく

Dollarから重複を引き上げられたので、Francも同じやり方をしたいです。でも、等価性のテストでFrancがかけていないことに気がつきました。追加します。

- テストを追加する

```rust
+        assert!(Franc::new(5).equals(Franc::new(5)));
+        assert!(!Franc::new(5).equals(Franc::new(6)));
```

- 実装する
Dollar同様実装して行きます。

  - [差分Commit1](https://github.com/Khigashiguchi/rust_sutras_tdd/pull/7/commits/b96b48bac775f639061b50173fc2fcae00946545)
  - [差分Commit2](https://github.com/Khigashiguchi/rust_sutras_tdd/pull/7/commits/4a6ce41d29621080a12ebc7d538e4d24dabde419)


## 最終的なコード
本章終了時点のコードは以下です。

```rust
pub struct Money {
    amount: u32
}

trait MoneyTrait {
    fn new(amount: u32) -> Money;
}

pub struct Dollar {
}

pub struct Franc {
}

impl Money {
    pub fn times (&self, multiplier: u32) -> Money {
        Money {amount: self.amount * multiplier }
    }
    pub fn equals(&self, target: Money) -> bool {
        self.amount == target.amount
    }
}

impl MoneyTrait for Dollar {
    fn new (amount: u32) -> Money {
        Money { amount: amount }
    }
}

impl Franc {
    pub fn new (amount: u32) -> Money {
        Money { amount: amount }
    }
}

#[cfg(test)]
mod tests {
    use super::*;
    #[test]
    fn test_multiplication() {
        let five = Dollar::new(5);
        assert!(Dollar::new(10).equals(five.times(2)));
        assert!(Dollar::new(15).equals(five.times(3)));
    }
    #[test]
    fn test_equality() {
        assert!(Dollar::new(5).equals(Dollar::new(5)));
        assert!(!Dollar::new(5).equals(Dollar::new(6)));
        assert!(Franc::new(5).equals(Franc::new(5)));
        assert!(!Franc::new(5).equals(Franc::new(6)));
    }
    #[test]
    fn test_franc_multiplication() {
        let five = Franc::new(5);
        assert!(Franc::new(10).equals(five.times(2)));
        assert!(Franc::new(15).equals(five.times(3)));
    }
}
```

## 今時点でのToDoList

- [ ]$5 + 10CHF = $10
- [x]$5 * 2 = $10
- [x]amountをprivateにする（すでにprivateだった）
- [x]Dollarの副作用どうする
- [ ]Moneyの丸め処理どうする
- [x]equals()の実装
- [ ]hashCode()の実装
- [ ]nullとの等価性比較
- [ ]他のオブジェクトとの等価性比較
- [x]5CHF * 2 = 10CHF
- [ ]DollarとFrancの重複
- [x]equalsの一般化
- [x]timesの一般化

実は、原著ではtimes()の一般化はここではやっていないのですが、実装都合上こちらで完了させました。


# 最後に

次回は、第7章以降を進めて行きます。

# 参考
- [書籍「テスト駆動開発」をRustで書く #1 （chapter1...3）
](https://qiita.com/Khigashiguchi/items/dfc382d9d2f988522721)
- [オブジェクト指向経験者のためのRust入門](https://qiita.com/nacika_ins/items/cf3782bd371da79def74)
- [RustでUnit Testを書く方法](https://qiita.com/Khigashiguchi/items/32c03c6886a4a81605d5)