Tambourine作業メモ

主にスキル習得のためにやった作業のメモ。他人には基本的に無用のものです。

ケント・ベックの「テスト駆動開発」の写経をRustでやってみる(9)

第8章に入る。

この辺りから、本のコードと自分のコードの差が大きくなって、 本の戦略がそのまま使えない感じになってきた。ちょっと試行錯誤しながら進めたい。

まず、本の方ではtimesメソッドがDollarとFrancの両方にあるので、それをまとめたいと言っている。 ところが、それはもうやっちゃった(笑)。 この先の行く末としてはサブクラスをなくして、Moneyに集約していきたいのかなと思っている。

本の方では、MoneyにDollarやFrancの生成の機能を持たせようとしていて、 テストコードで

Dollar five = new Dollar(5);

としているところを、

Money five = Money.dollar(5);

とFactory Methodで生成するようにして、テストコードにあらわにDollarクラスが出てこないようにしようとしている。

意図はわかる。こうしていって、テストコードにDollarが出てこないようにしておいて、しれっとDollarクラスを消しても グリーンが維持されれば理想的だ。Factory Methodはこんな感じ。

class Money {
    static Dollar dollar(int amount) {
        return new Dollar(amount);
    }
}

同じ事をRustの方でもできるのか。Moneyはトレイトだが実装は持てる。 Moneyトレイトのメソッドが、Dollarを返すことをは許されるのか。

trait Money {
    fn new(i: i32) -> Self;

    // Factory Method
    fn dollar(i: i32) -> Dollar {
        Dollar::new(i)
    }
    fn franc(i: i32) -> Franc {
        Franc::new(i)
    }

これは別にエラーにならない。へー。

ところが、テストコード側はエラーになる。

例えば、このようにMoney::francメソッドを呼び出すと、

#[test]
fn test_franc_multiplication() {
    let five = Money::franc(5);
    assert_eq!(Franc::new(10), five.times(2));
    assert_eq!(Franc::new(15), five.times(3));
}

このように、型アノーテーションが必要だと怒られる。

error[E0283]: type annotations needed
  --> src/main.rs:71:20
   |
11 |     fn franc(i: i32) -> Franc {
   |     ------------------------- required by `Money::franc`
...
71 |         let five = Money::franc(5);
   |                    ^^^^^^^^^^^^ cannot infer type
   |
   = note: cannot resolve `_: Money`

こうするのは問題にならない。が、そういうことじゃない(笑)。

#[test]
fn test_franc_multiplication() {
    let five = Franc::franc(5);
    assert_eq!(Franc::new(10), five.times(2));
    assert_eq!(Franc::new(15), five.times(3));
}

これはつまり、このタイミングでMoneyをトレイトから構造体に格上げしてやる必要があると言うことなんだろう。

ケント・ベックの「テスト駆動開発」の写経をRustでやってみる(8)

7章に入る。

ドルとフランは比較できるのだろうかという疑問をテストに書いてみるところから始まる。

この本のコードでは、equalsメソッドはObjectを受けているので、ドルとフランは区別なく 扱われ、スイス経済に大打撃を与えている。

一方、私のRustの実装はEqトレイトに任せているので、同じ型しか比較できない。 テストはコンパイルエラーとなる。 それはマズいので、違う通貨の場合には必ずfalseになるコードにしよう。

・・・と考えて、いろいろ試行錯誤してみたのだが、そもそもRustではJavaのように どんなものでも渡ってくるようなメソッドが書けない。 書けなくはないんだけど、動的に渡ってきた引数に対してチェックする事が出来ない。

具体的には、

impl<T> PartialEq<T> for Dollar {
    fn eq(&self, _other: &T) -> bool {
        false
    }
}

として、Tの型によって動作を変えるようなことがしたいんだけど、 どうしていいかわからない。

Rustの定石としては、enumが渡ってくるとして パターンマッチで動きを変える事になると思う。

いったんは、

impl PartialEq<Franc> for Dollar {
    fn eq(&self, _other: &Franc) -> bool {
        false
    }
}

これをDollarとFrancの両方に実装して、テストは通してしまおう・・・。 それにしても、そもそもDollarとFrancはほとんど同じなので無くしてしまいたいんだよな・・・と 本の方でも言い始めている。

ともあれ、これで7章はクリア。

ケント・ベックの「テスト駆動開発」の写経をRustでやってみる(7)

5章「原則をあえて破るとき」に入る。

この章は、クソコードを作って終わり。 ただし、これをどうにかしないうちは家に帰らないことも誓う。

とりあえず、今まで作ったドルとは別にフランを作る。それも単なるコピペで作る。 フラン・・・そんな通貨単位はもはや存在しない。時代を感じる。 あれ?CHFはスイスフランか。まだあるわ。

作ったテストは

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

これを充たすFranc構造体をDollarをコピペして5章は終わり。6章に入ろう。

6章では、このコピペを解消する。 Javaなので、DollarクラスとFrancクラスの親クラスとして Moneyクラスを導入する。

さて、Rustではどうするのだろうか。Moneyトレイトを作ることになる・・・んだと思う。 やってみよう。

Javaオブジェクト指向言語と違って、Rustでは継承がない。 トレイトによってメソッドの実装を引き継がせることは出来るのだが、 フィールドの定義を共有することはできない。 通常、構造体のデータを共有したいのなら、最も単純かつ適切な手段はHas-a、 つまり共有部分を別の構造体にして、それを構造体の要素とすることだろう。 というわけで、要素が1つしかない今現在はそれもあまり意味が無い。

この時点でベストだと考えられるのは、Money構造体を作ってそこから タプル構造体で新しい型としてDollarとFrancを作ることかも知れない。 しかし、それも先の展開を考えるとイマイチな気がする・・・。 とりあえず、timesメソッドの共有を考えてみよう。

Dollarのtimesの実装はこうなっている。

pub fn times(&self, multiplier: i32) -> Dollar {
    Dollar {
        amount: self.amount * multiplier,
    }
}

つまり、Dollerのインスタンスを作ることと、 インスタンスからamountの値を得ることができれば実装できる。

というわけで、Moneyトレイトを作ってみた。

trait Money {
    fn new(i: i32) -> Self;
    fn amount(&self) -> i32;
    fn times(&self, multiplier: i32) -> Self
    {
        Self::new(self.amount() * multiplier)
    }
}

これはコンパイルエラーになる。

the size for values of type `Self` cannot be known at compilation time

doesn't have a size known at compile-time

help: the trait `std::marker::Sized` is not implemented for `Self`
note: to learn more, visit <https://doc.rust-lang.org/book/ch19-04-advanced-types.html#dynamically-sized-types-and-the-sized-trait>
note: the return type of a function must have a statically known size
help: consider further restricting `Self`: ` where Self: std::marker::Sized `

Selfを返すモノを実装してるけど、この関数はコンパイル時にSelfのサイズが 決まらない恐れがあるのでダメだと言っている。 多分、Vecのようなホントに大きさが決まらないモノだとダメなんだろう。 メッセージの最後にSelfをSizedだと制限すれば良いんだと書いてある。なるほど?

こうかな?

trait Money {
    fn new(i: i32) -> Self;
    fn amount(&self) -> i32;
    fn times(&self, multiplier: i32) -> Self
    where
        Self: Sized,
    {
        Self::new(self.amount() * multiplier)
    }
}

おお、いけた。

というわけで、timesの実装は書かなくて良くなったものの、 amountというメソッドを新たに実装する必要が出来た。

#[derive(Debug, Eq, PartialEq)]
pub struct Dollar {
    amount: i32,
}

impl Money for Dollar {
    fn new(i: i32) -> Dollar {
        Dollar { amount: i }
    }

    fn amount(&self) -> i32 {
        self.amount
    }
}

#[derive(Debug, Eq, PartialEq)]
pub struct Franc {
    amount: i32,
}

impl Money for Franc {
    fn new(i: i32) -> Franc {
        Franc { amount: i }
    }
    fn amount(&self) -> i32 {
        self.amount
    }
}

かなりの重複感であり、まだ大分クソコードな感じが強い。イケてない。 ただ、これ以上の対応はもう少し先の実装を進めてからにしてみたい。

というわけで、6章まで終わり。

ケント・ベックの「テスト駆動開発」の写経をRustでやってみる(6)

4章「意図を語るテスト」に入っていこう。

amountという内部構造がインターフェースとして外に出ているのは 見づらいし良くないよねということでリファクタリングしている。 もっともだ。 そもそも、こちらにはコンストラクタすらないので、このタイミングで作ることにしよう。

impl Dollar {
    pub fn new(i: i32) -> Dollar {
        Dollar { amount: i }
    }
}

#[test]
fn test_multiplication() {
    let five = Dollar::new(5);
    assert_eq!(Dollar::new(10), five.times(2));
    assert_eq!(Dollar::new(15), five.times(3));
}

コンストラクタの名前は慣習的にnewがよく使われるけど、なんでも構わない。

というわけで、テストからamountというフィールド名が消えたので満足。4章は短い。

ケント・ベックの「テスト駆動開発」の写経をRustでやってみる(5)

3章「三角測量」に入る。

DollarをValue Objectにしたいと言い出す。等値であることをチェックできるようにするのだと。

Value Objectというパターンに馴染みはないんだけど、 オブジェクトを値として扱うことにはいろいろな難しさがあることは知っている。 そもそも、Javaにプリミティブ型などという非純粋なものがあるのもそこに起因するし、 等値であることを==ではなくequalsでチェックすることになるなどの ドジの原因もそうだ。

そもそも、等値は難しい。JavaScriptは「==ではなくて===を使いなさい」などと 初学者に「こいつに付いていて大丈夫だろうか」と思わせるようなことを言うし、 Rubyは、「値を比較するには、eql?/equal?/==/===の4種類のメソッドがあります」などと言い出す。

もちろん、Rustの等値も難しい。Eqトレイトが絡んでくるアレだ。 が、それは後で考えることにして、バカみたいにJavaを倣って進んでいくことにする。 というわけで、ここはあえてequalsメソッドをDollar構造体に実装することにする。

追加したテストと仮実装はこんな感じだ(mod testsは省略しているが、 実際はtestsモジュールにテストは書いている)

impl Dollar {
    pub fn equals(&self, dollar: &Dollar) -> bool {
        false
    }
}

#[test]
fn test_equality() {
    assert!(Dollar { amount: 5 }.equals(&Dollar { amount: 5 }));
}

構造体式に参照演算子&を付けたり、equalsメソッドを.で呼び出したりするとき 演算子の優先順位がどうなるのか、つまりカッコでどこかをくくらなければいけないのか ちょっと不安になったけど、そのまま試しに書いてみたら特にシンタックスエラーには ならなかった。

さあ、テストを実行してみよう。

running 2 tests
test tests::test_multiplication ... ok
test tests::test_equality ... FAILED

failures:

---- tests::test_equality stdout ----
thread 'tests::test_equality' panicked at 'assertion failed: Dollar{amount: 5,}.equals(&Dollar{amount: 5,})', src/main.rs:33:9
note: run with `RUST_BACKTRACE=1` environment variable to display a backtrace


failures:
    tests::test_equality

test result: FAILED. 1 passed; 1 failed; 0 ignored; 0 measured; 0 filtered out

もちろん、equalsが常にtrueを返す様に実装すれば、このテストは通る。

この本では、常に通る仮実装を作って、それでは通らないようにするために falseが必要になるテストを追加する・・・と進んでいるが、 実際にコードを書くときには、書いたテストが機能しているのか確かめるために わざと絶対通る様にしたり、絶対失敗するようにして確かめることをよく行う。大事だ。 trueとfalseを両方書いてみて、テストコードがこのメソッドを呼び出していることに 確信が持てた。自信を得ることこそユニットテストの目的だから、 自信が持てていないことは確かめる必要があるし、テストがちゃんと機能しているかどうかは 一番大事なことだから、ちゃんと自信を持つべきだ。

さて、では二つ目のケースをテストに足してテストが失敗することを確認したら、 ちゃんとした実装を目指そう。といっても、何も難しくない。 基本的には、すべての要素を比較すれば良いだけだし、そもそも1つの要素しかない。

pub struct Dollar {
    pub amount: i32,
}

impl Dollar {
    pub fn times(&self, multiplier: i32) -> Dollar {
        Dollar {
            amount: self.amount * multiplier,
        }
    }

    pub fn equals(&self, dollar: &Dollar) -> bool {
        self.amount == dollar.amount
    }
}

そして、何も難しくないことはコンピューターにも出来る(これについては、後で述べる)。

今、比較のためのメソッドとしてequalsを作ったが、 Rustの流儀としては、==で比較できるべきだ。 assert_eq!マクロは、2つの引数を==で比較する。 それを使ってtest_equality()を書き直そう。

#[test]
fn test_equality() {
    assert_eq!(Dollar { amount: 5 }, Dollar { amount: 5 });
    assert_ne!(Dollar { amount: 5 }, Dollar { amount: 6 });
}

こう書き直すと、コンパイルエラーになる。

binary operation `==` cannot be applied to type `Dollar`

note: an implementation of `std::cmp::PartialEq` might be missing for `Dollar`

Dollarを==で比較することはできないよと1行目は言っている。 さらに、2行目では「std::cmp::PartialEqの実装を忘れてるんじゃない?」と言ってる。 本当に驚くべき親切さだ。で、PartialEqって何だろう。

その秘密はRustの奥深さの奥にあるので各自勝手に勉強することとして、 すごくざっくりと言えばeqメソッドとneメソッドが定義されたトレイトだ。 トレイトはJavaでいうインターフェース、Rubyでいうとモジュールみたいなものだ。 トレイトは実装を持てて、neはeqをnotしたものとあらかじめ実装されているので、 実装しなければならないのはeqだけだ。 ちなみに、==演算子とeqメソッドは表記が違うだけの同じモノである。

impl PartialEq for Dollar {
    fn eq(&self, other: &Dollar) -> bool {
        self.amount == other.amount
    }
}

やっていることはさっきのequalsメソッドの実装となんら変わらない。 ただし、これを書くだけで==と!=が使えるようになるはずだ。

しかし、なんでPartialなのか。それは浮動小数点の不思議さに由来しているらしい。 詳細は省くけど、浮動小数点じゃないならPartialじゃないEqも充たすらしい。 ただし、EqトレイトはPartialじゃないよというマーカーで、何も追加して実装する必要はない。 付けておくべきなので、付けておくことにしよう。

impl Eq for Dollar{}

さて、まだコンパイルエラーは消えてない。

`Dollar` doesn't implement `std::fmt::Debug`

`Dollar` cannot be formatted using `{:?}`

help: the trait `std::fmt::Debug` is not implemented for `Dollar`
note: add `#[derive(Debug)]` or manually implement `std::fmt::Debug`
note: required because of the requirements on the impl of `std::fmt::Debug` for `&Dollar`
note: required by `std::fmt::Debug::fmt`

Dollarにはstd::fmt::Debugが実装されていないよと言ってる。 assert_eq!マクロは、テストが失敗したときに両者の値を表示してくれる。 そのときにどうやって表示するかはDebugトレイトで指定をするのだ。

相変わらずの親切さによって「#[derive(Debug)]したら?」と教えてくれている。 deriveはトレイトの実装をお任せする機能だ。 要素に全部Debugが実装されているようなものであれば、 おそらくそれを全部表示するのが求められる実装だろうと考えて、 勝手に作ってくれる。便利だ。

#[derive(Debug)]
pub struct Dollar {
    pub amount: i32,
}

これでコンパイルエラーは消えた。テストを実行しよう。 ちゃんとテストが通った。

めでたしめでたし。

・・・まだ疑問はある。さっき、Debugトレイトはお任せ実装に委ねた。 PartialEqもやっていることは持っている全要素についてeqしただけだった。 Debugがお任せできるのなら、Eqもお任せできるのではないだろうか。

そう、できる。

というわけで、最終的なソースコードはこうなる。 言語の進化を感じるよな。

fn main() {}

#[derive(Debug, Eq, PartialEq)]
pub struct Dollar {
    pub amount: i32,
}

impl Dollar {
    pub fn times(&self, multiplier: i32) -> Dollar {
        Dollar {
            amount: self.amount * multiplier,
        }
    }
}

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

    #[test]
    fn test_multiplication() {
        let five = Dollar { amount: 5 };
        assert_eq!(10, five.times(2).amount);
        assert_eq!(15, five.times(3).amount);
    }

    #[test]
    fn test_equality() {
        assert_eq!(Dollar { amount: 5 }, Dollar { amount: 5 });
        assert_ne!(Dollar { amount: 5 }, Dollar { amount: 6 });
    }
}

ケント・ベックの「テスト駆動開発」の写経をRustでやってみる(4)

2章「明白な実装」に入る。

仕様が変わった。timesメソッドはDollarを返す様になった。 あ、今、私のtypoも見つかった。DollarじゃなくてDollerになってる・・・。

ともあれ、仕様変更の結果、テストはこうなった。

    #[test]
    fn test_multiplication() {
        let five = Dollar { amount: 5 };
        let five_times_two = five.times(2);
        assert_eq!(10, five_times_two.amount);
        let five_times_three = five.times(3);
        assert_eq!(15, five_times_three.amount);
    }

Dollar構造体のインスタンス(って表現して良いのかな?)がimmutableで良くなったので、 なんとなくコードの質的にもよくなった気がする。

さて、これを通すためにコードを修正するのだが・・・ コンパイルは出来るがエラーになる状態にするのが難しい。流石Rustである。 というわけで、コンパイルが通るようにしたらここまで直してしまった。

impl Dollar {
    pub fn times(&self, multiplier: i32) -> Dollar {
        Dollar {
            amount: self.amount * multiplier,
        }
    }
}

mutableな借用を貰えなくなったが、もちろんそれで問題ない。 テストはちゃんと通った。よしよし。

テストコードだが、Javaのコードに対応するためにfive.times()の戻りを 変数に代入しているが、immutableにしているのでその直後でしか使っていない。 なら、直接assert_eq!に渡してしまっても同じなので、変更しちゃう。

    #[test]
    fn test_multiplication() {
        let five = Dollar { amount: 5 };
        assert_eq!(10, five.times(2).amount);
        assert_eq!(15, five.times(3).amount);
    }

もちろん、テストがグリーンのままであることを確認しておく。 これで2章は終わりだ。

ケント・ベックの「テスト駆動開発」の写経をRustでやってみる(3)

いよいよ「テスト駆動開発」に入っていこう。

まずは、P4の最初のテストだ。おもむろに存在しないDollerクラスをnewして、 ありもしないtimesメソッドやamountフィールドを参照している。テストファーストなのだ。

・・・Rustでデータ型を表現するのはどうするんだったかな・・・(本をめくる)・・・ 構造体だ。ざっと眺めて要点を飲み込む。

こんな感じでいいんだろうか。

fn main() {
    println!("Hello, world!");
}

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

    #[test]
    fn test_multiplication() {
        let five = Doller { amount: 5 };
        five.times(2);
        assert_eq!(10, five.amount);
    }
}

まず、Rustにおいて関数のテストは同じファイル内に書く。 単に#[test]と属性を付ければ、それがテストだと見なされる。

ただし、慣例としてtestsというモジュールを作り、その中に入れる。 そこに#[cfg(test)]と属性を付けておけば、テストはtest実行以外の場合ではビルド対象から外される。

で、VScodeでテストを走らせるにはどうしたら良いのか。 コードで#[test]と書くと、その下に Run test という表示が出て、押すとテストが走る。 これはRust(rls)拡張機能がやっているらしい。

押すと、ターミナルが開き、こんな感じのログが流れた。

> Executing task: cargo test -- --nocapture test_multiplication <

   Compiling money v0.1.0 (/Users/tambara/study/tdd_rust/money)
error[E0422]: cannot find struct, variant or union type `Doller` in this scope
  --> src/main.rs:11:20
   |
11 |         let five = Doller { amount: 5 };
   |                    ^^^^^^ not found in this scope

warning: unused import: `super::*`
 --> src/main.rs:7:9
  |
7 |     use super::*;
  |         ^^^^^^^^
  |
  = note: `#[warn(unused_imports)]` on by default

error: aborting due to previous error

For more information about this error, try `rustc --explain E0422`.
error: could not compile `money`.

To learn more, run the command again with --verbose.
The terminal process terminated with exit code: 101

Terminal will be reused by tasks, press any key to close it.

要するに cargo で特定のテストだけ実行しているわけだ。

テストを全部流したかったらどうするのか・・・メニューのTerminal - Run Taskから出来るみたい。

> Executing task: cargo test <

   Compiling money v0.1.0 (/Users/tambara/study/tdd_rust/money)
error[E0422]: cannot find struct, variant or union type `Doller` in this scope
  --> src/main.rs:11:20
   |
11 |         let five = Doller { amount: 5 };
   |                    ^^^^^^ not found in this scope

warning: unused import: `super::*`
 --> src/main.rs:7:9
  |
7 |     use super::*;
  |         ^^^^^^^^
  |
  = note: `#[warn(unused_imports)]` on by default

error: aborting due to previous error

For more information about this error, try `rustc --explain E0422`.
error: could not compile `money`.

To learn more, run the command again with --verbose.
The terminal process terminated with exit code: 101

Terminal will be reused by tasks, press any key to close it.

確かにcargo testをやってる。

で、エラーの方は、Dollerってなんやねんと言われている。それはそうだ。

P6 でやっているように、空の実装を足してやる。

// これ消すと怒られるのなぜ?
fn main() {}

pub struct Doller {
    pub amount: i32,
}

impl Doller {
    pub fn times(multiplier: i32) {}
}

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

    #[test]
    fn test_multiplication() {
        let five = Doller { amount: 5 };
        five.times(2);
        assert_eq!(10, five.amount);
    }
}

良さそうに思えるけど、まだコンパイルが通らない。

error[E0599]: no method named `times` found for struct `Doller` in the current scope
  --> src/main.rs:19:14
   |
4  | pub struct Doller {
   | ----------------- method `times` not found for this
...
19 |         five.times(2);
   |         -----^^^^^
   |         |    |
   |         |    this is an associated function, not a method
   |         help: use associated function syntax instead: `Doller::times`
   |
   = note: found the following associated functions; to be used as methods, functions must have a `self` parameter
note: the candidate is defined in an impl for the type `Doller`
  --> src/main.rs:9:5
   |
9  |     pub fn times(multiplier: i32) {}
   |     ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^

Rustはコンパイルを通すのが凄く難しい言語だが、 エラーメッセージが驚くほど親切だ。

Dollerにはtimesなんてメソッドはないぞと怒られているんだけども、 「お前、selfをパラメータに入れ忘れてるんじゃね?」と教えてくれている。

そう、Rustで構造体のメソッドを定義したいのなら、1つ目のメソッドはselfにしないといけない。 こうだ。

impl Doller {
    pub fn times(&mut self, multiplier: i32) {}
}

エラーが変わった。

  --> src/main.rs:19:9
   |
18 |         let five = Doller { amount: 5 };
   |             ---- help: consider changing this to be mutable: `mut five`
19 |         five.times(2);
   |         ^^^^ cannot borrow as mutable

fiveがmutableじゃないとダメと怒られた。それはそうだ。

    fn test_multiplication() {
        let mut five = Doller { amount: 5 };
        five.times(2);
        assert_eq!(10, five.amount);
    }

こうなってないといけない。これでエラーが消えた。

> Executing task: cargo test <

warning: unused variable: `multiplier`
 --> src/main.rs:9:29
  |
9 |     pub fn times(&mut self, multiplier: i32) {}
  |                             ^^^^^^^^^^ help: consider prefixing with an underscore: `_multiplier`
  |
  = note: `#[warn(unused_variables)]` on by default

    Finished test [unoptimized + debuginfo] target(s) in 0.00s
     Running target/debug/deps/money-043e3ba9eebcce93

running 1 test
test tests::test_multiplication ... FAILED

failures:

---- tests::test_multiplication stdout ----
thread 'tests::test_multiplication' panicked at 'assertion failed: `(left == right)`
  left: `10`,
 right: `5`', src/main.rs:20:9
note: run with `RUST_BACKTRACE=1` environment variable to display a backtrace


failures:
    tests::test_multiplication

test result: FAILED. 0 passed; 1 failed; 0 ignored; 0 measured; 0 filtered out

error: test failed, to rerun pass '--bin money'
The terminal process terminated with exit code: 101

うん。ちゃんとテスト結果も出ている。FAILEDだ。 しかし、グリーンなのかレッドなのかわかりにくいな・・・。

では、グリーンにしておこうか。

// これ消すと怒られるのなぜ?
fn main() {}

pub struct Doller {
    pub amount: i32,
}

impl Doller {
    pub fn times(&mut self, multiplier: i32) {
        self.amount *= multiplier;
    }
}

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

    #[test]
    fn test_multiplication() {
        let mut five = Doller { amount: 5 };
        five.times(2);
        assert_eq!(10, five.amount);
    }
}
> Executing task: cargo test -- --nocapture test_multiplication <

    Finished test [unoptimized + debuginfo] target(s) in 0.00s
     Running target/debug/deps/money-043e3ba9eebcce93

running 1 test
test tests::test_multiplication ... ok

test result: ok. 1 passed; 0 failed; 0 ignored; 0 measured; 0 filtered out

テストが通った。素晴らしい。これで、1章は終わりだ。