Pythonで、書籍「テスト駆動開発」の第I部「多国通貨」を体験してみる

書籍「[テスト駆動開発](https://honto.jp/netstore/pd-book_28393266.html)」の**第I部「多国通貨」**で取り上げられている実例は、JAVAベースのため、自分の理解を深めるためにも、Pythonで同等のプラクティスに挑んでみました。
<img src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/103595/88eb6ee1-5b91-c3f8-190b-89a393920fa7.png" width=30%>

ちなみに、テスト駆動開発プラクティスのコードは、以下のGithubに保管しています。
https://github.com/ttsubo/study_of_test_driven_development/tree/master/python

# ■ テスト駆動開発の進め方
書籍「[テスト駆動開発](https://honto.jp/netstore/pd-book_28393266.html)」で提唱しているテスト駆動開発の進め方は、次の通りです。

- 小さいテストを1つ書く。
- すべてのテストを実行し、1つ失敗することを確認する。
- 小さい変更を行う。
- 再びテストを実行し、すべて成功することを確認する。 
- リファクタリングを行い、重複を除去する。

今回のプラクティスでも、極力、この進め方に追従していきます。

# ■ 多国通貨を通じて、テスト駆動開発を経験する
実現すべき要件は、以下の２つ。

- 通貨の異なる2つの金額を足し、通貨間の為替レートに基づいて換算された金額を得る。
- 金額(通貨単位あたりの額)に数値(通貨単位数)を掛け、金額を得る。

## 第1章: 仮実装 (Multi-Currency Money)
まずは、`金額(通貨単位あたりの額)に数値(通貨単位数)を掛け、金額を得る。`という仕組みを目指します。
具体的には、`$5*2=$10`が成立するように、コード化を進めていきます。

### (1) まずは、小さいテストを1つ書く。
`$5*2=$10`が成立することを確認するテストを作成します。

```python:tests/test_money.py
from testtools import TestCase
from example.dollar import Dollar

class MoneyTest(TestCase):
    def testMultiplication(self):
        five = Dollar(5)
        five.times(2)
        self.assertEqual(10, five.amount)
```
### (2) テスト対象のコードを準備しておく
`Dollar`クラスをロードするだけのコードを配備しておきます。

```python:example/dollar.py
class Dollar:
    pass
```
### (3) テストを実行し、1つ失敗することを確認する。
`Dollar`クラスのオブジェクト化できませんので、テストは失敗します。

```
$ pytest -v

...(snip)

tests/test_money.py::MoneyTest::testMultiplication FAILED                [100%]
================================== FAILURES ===================================
___________________________ MoneyTest.testMultiplication ______________________
NOTE: Incompatible Exception Representation, displaying natively:

testtools.testresult.real._StringException: Traceback (most recent call last):
  File "/Users/ttsubo/source/study_of_test_driven_development/tests/test_money.py", line 6, in testMultiplication
    five = Dollar(5)
TypeError: Dollar() takes no arguments
```

### (4) 小さい変更を行う。
`Dollar`クラスのオブジェクト化が行えるように、コンストラクタを定義します。

```python:example/dollar.py
class Dollar:
    def __init__(self, amount):  
        self.amount = amount
```
### (5) 再度、テストを実行し、1つ失敗することを確認する。
`Dollar`クラスには、`times`メソッドが定義されていないので、テストは失敗します。

```
$ pytest -v

...(snip)
tests/test_money.py::MoneyTest::testMultiplication FAILED                [100%]
================================== FAILURES ===================================
___________________________ MoneyTest.testMultiplication ______________________
NOTE: Incompatible Exception Representation, displaying natively:

testtools.testresult.real._StringException: Traceback (most recent call last):
  File "/Users/ttsubo/source/study_of_test_driven_development/tests/test_money.py", line 7, in testMultiplication
    five.times(2)
AttributeError: 'Dollar' object has no attribute 'times'
```
### (6) 再度、小さい変更を行う。
`金額に数値を掛け、金額を得る。`ことができるように、`times`メソッドが定義します。

```python:example/dollar.py
class Dollar:
    def __init__(self, amount):  
        self.amount = amount
                                                                                    
    def times(self, multiplier):                                                    
        self.amount *= multiplier
```
### (7) 再びテストを実行し、すべて成功することを確認する。-> <font color="green">OK</font>
ようやく、テストが成功するようになりました。

```
$ pytest -v

...(snip)
tests/test_money.py::MoneyTest::testMultiplication PASSED                [100%]
```

## 第2章: 明白な実装 (Degenerate Objects)
第1章では、`金額(通貨単位あたりの額)に数値(通貨単位数)を掛け、金額を得る。`という仕組みとして、必要最低限の実装を試みました。さらに、ここでは、`金額(通貨単位あたりの額)に数値(通貨単位数)を掛け、金額を得る。`という仕組みを、何度も、呼び出せるように拡張していきます。

### (1) まずは、小さいテストを1つ書く。
`$5*2=$10`が成立することを確認したのち、`$5*3=$15`も成立するテストを作成します。
`Dollar`クラスのオブジェクト化を一度行った上で、掛け算の値を色々と変更してテストしていくことを想定します。

```python:tests/test_money.py
from testtools import TestCase
from example.dollar import Dollar

class MoneyTest(TestCase):
    def testMultiplication(self):
        five = Dollar(5)
        product = five.times(2)
        self.assertEqual(10, product.amount)
        product = five.times(3)
        self.assertEqual(15, product.amount)
```
### (2) テストを実行し、1つ失敗することを確認する。
まだ、`Dollar`オブジェクトの`times`メソッドの実行結果を取得するロジックを実装されていないので、テストは失敗します。

```
$ pytest -v

...(snip)

tests/test_money.py::MoneyTest::testMultiplication FAILED                [100%]
================================== FAILURES ===================================
___________________________ MoneyTest.testMultiplication ______________________
NOTE: Incompatible Exception Representation, displaying natively:

testtools.testresult.real._StringException: Traceback (most recent call last):
  File "/Users/ttsubo/source/study_of_test_driven_development/python/tests/test_money.py", line 8, in testMultiplication
    self.assertEqual(10, product.amount)
AttributeError: 'NoneType' object has no attribute 'amount'
```
### (3) 小さい変更を行う。
`Dollar`オブジェクトの`times`メソッドの実行結果を取得するロジックを実装します。

- `Dollar`オブジェクトの`times`メソッド呼び出し時、新たな`Dollar`オブジェクトを生成する
- 新たな`Dollar`オブジェクトのインスタンス変数`amount`に、掛け算結果を保存する


```python:example/dollar.py
class Dollar:
    def __init__(self, amount):  
        self.amount = amount
                                                                                    
    def times(self, multiplier):                                                    
        return Dollar(self.amount * multiplier)
```
### (4) 再びテストを実行し、すべて成功することを確認する。-> <font color="green">OK</font>
テストが成功するようになりました。

```
$ pytest -v

...(snip)
tests/test_money.py::MoneyTest::testMultiplication PASSED                [100%]
```


## 第3章: 三角測量 (Equality for All)
第2章では、`金額(通貨単位あたりの額)に数値(通貨単位数)を掛け、金額を得る。`という仕組みを、何度も、呼び出せるように拡張しました。ここでは、`ある5ドルは、他の5ドルと同じ価値である`ことを確認できる仕組みを追加していきます。ちなみに、pythonの特殊メソッド`__eq__`を活用してオブジェクトの同一性を確認しています。

### (1) まずは、小さいテストを1つ書く。
`ある5ドルは、他の5ドルと等価である`ことを確認するテストを作成します。

```python:tests/test_money.py
from testtools import TestCase
from example.dollar import Dollar

class MoneyTest(TestCase):
    def testMultiplication(self):
        five = Dollar(5)
        product = five.times(2)
        self.assertEqual(10, product.amount)
        product = five.times(3)
        self.assertEqual(15, product.amount)
    
    def testEquality(self):
        self.assertTrue(Dollar(5) == Dollar(5))
        self.assertFalse(Dollar(5) == Dollar(6))
```
### (2) テストを実行し、1つ失敗することを確認する。
まだ、`Dollar`クラスには、`ある5ドルは、他の5ドルと等価である`ことが確認できる仕組みが存在しないため、テストは失敗します。

```
$ pytest -v

...(snip)
tests/test_money.py::MoneyTest::testEquality FAILED  　　　　　　　　　　　　[ 50%]
tests/test_money.py::MoneyTest::testMultiplication PASSED                [100%]
================================== FAILURES ===================================
___________________________ MoneyTest.testMultiplication ______________________
NOTE: Incompatible Exception Representation, displaying natively:

testtools.testresult.real._StringException: Traceback (most recent call last):
  File "/Users/ttsubo/source/study_of_test_driven_development/python/tests/test_money.py", line 13, in testEquality
    self.assertTrue(Dollar(5) == Dollar(5))
  File "/Users/ttsubo/.pyenv/versions/3.8.0/lib/python3.8/unittest/case.py", line 765, in assertTrue
    raise self.failureException(msg)
AssertionError: False is not true
```

### (3) 小さい変更を行う。
特殊メソッド`__eq__`を活用して、`ある5ドルは、他の5ドルと等価である`ことが確認できるように定義します。

```python:example/dollar.py
class Dollar:
    def __init__(self, amount):  
        self.amount = amount
    
    def __eq__(self, other):
        return self.amount == other.amount
                                                                                    
    def times(self, multiplier):                                                    
        return Dollar(self.amount * multiplier)
```
### (4) 再びテストを実行し、すべて成功することを確認する。-> <font color="green">OK</font>
テストが成功するようになりました。

```
$ pytest -v

...(snip)

tests/test_money.py::MoneyTest::testEquality PASSED  　　　　　　　　　　　　[ 50%]
tests/test_money.py::MoneyTest::testMultiplication PASSED                [100%]
```

## 第4章: 意図を語るテスト (Privacy)
第3章では、`ある5ドルは、他の5ドルと同じ価値である`ことを確認できる仕組みを追加しました。
ここでは、 これまでのテストの記述について、重複している箇所を見通し良くリファクタリングしていきます。
さらに、`Dollar`オブジェクトのインスタンス変数`amount`をプライベートメンバに変更します。

### (1) テスト：`MoneyTest:testMultiplication`のリファクタリングを行う。
`Dollar`クラスの`times`メソッドは、自身の金額と`multiplier` 引数を掛けた値を、Dollarオブジェクトのインスタンス変数`amount`に保持する`Dollar`オブジェクトが返却されます。
これまで書いていたテスト記述では、`Dollar`オブジェクトが返却されることが伝わりにくいため、リファクタリングを行って、可読性を高めていきます。

```python:tests/test_money.py
from testtools import TestCase
from example.dollar import Dollar

class MoneyTest(TestCase):
    def testMultiplication(self):
        five = Dollar(5)
        self.assertEqual(Dollar(10), five.times(2))
        self.assertEqual(Dollar(15), five.times(3))
    
    def testEquality(self):
        self.assertTrue(Dollar(5) == Dollar(5))
        self.assertFalse(Dollar(5) == Dollar(6))
```
### (2) テストを実行し、すべて成功することを確認する。
リファクタリング後も、テストは成功しています。

```
$ pytest -v

...(snip)

tests/test_money.py::MoneyTest::testEquality PASSED  　　　　　　　　　　　　[ 50%]
tests/test_money.py::MoneyTest::testMultiplication PASSED                [100%]
```

### (3) 小さい変更を行う。
`Dollar`オブジェクトのインスタンス変数`amount`をプライベートメンバに変更します。

```python:example/dollar.py
class Dollar:
    def __init__(self, amount):  
        self.__amount = amount
    
    def __eq__(self, other):
        return self.__amount == other.__amount
                                                                                    
    def times(self, multiplier):                                                    
        return Dollar(self.__amount * multiplier)
```
### (4) テストを実行し、すべて成功することを確認する。-> <font color="green">OK</font>
テストは成功しています。

```
$ pytest -v

...(snip)

tests/test_money.py::MoneyTest::testEquality PASSED  　　　　　　　　　　　　[ 50%]
tests/test_money.py::MoneyTest::testMultiplication PASSED                [100%]
```

## 第5章: 原則をあえて破るとき (Franc-ly Speaking)
第4章までで、多通貨の一つとして、**ドル通貨**に関わる`金額(通貨単位あたりの額)に数値(通貨単位数)を掛け、金額を得る。`の仕組みを実現してきました。ここでは、 別の通貨として、**フラン通貨**でも同様な仕組みを実現していきます。

### (1) まずは、小さいテストを1つ書く。
ドル通貨のテストと同じものを、フラン通貨でも確認できるように、テストを追加します。

```python:tests/test_money.py
from testtools import TestCase
from example.dollar import Dollar
from example.franc import Franc

class MoneyTest(TestCase):
    def testMultiplication(self):
        five = Dollar(5)
        self.assertEqual(Dollar(10), five.times(2))
        self.assertEqual(Dollar(15), five.times(3))
    
    def testEquality(self):
        self.assertTrue(Dollar(5) == Dollar(5))
        self.assertFalse(Dollar(5) == Dollar(6))

    def testFrancMultiplication(self):
        five = Franc(5)
        self.assertEqual(Franc(10), five.times(2))
        self.assertEqual(Franc(15), five.times(3))
```
### (2) テスト対象のコードを準備しておく
`Franc`クラスをロードするだけのコードを配備しておきます。

```python:example/franc.py
class Franc:
    pass
```
### (3) テストを実行し、1つ失敗することを確認する。
`Franc`クラスのオブジェクト化できませんので、テストは失敗します。

```
$ pytest -v

...(snip)

tests/test_money.py::MoneyTest::testEquality PASSED                      [ 33%]
tests/test_money.py::MoneyTest::testFrancMultiplication FAILED           [ 66%]
tests/test_money.py::MoneyTest::testMultiplication PASSED                [100%]
================================== FAILURES ===================================
___________________________ MoneyTest.testMultiplication ______________________
NOTE: Incompatible Exception Representation, displaying natively:

testtools.testresult.real._StringException: Traceback (most recent call last):
  File "/Users/ttsubo/source/study_of_test_driven_development/tests/test_money.py", line 16, in testFrancMultiplication
    five = Franc(5)
TypeError: Franc() takes no arguments
```

### (4) 小さい変更を行う。
第1章でやったことを参考に、`Franc`クラスを定義していきます。

```python:example/franc.py
class Franc:
    def __init__(self, amount):  
        self.__amount = amount
    
    def __eq__(self, other):
        return self.__amount == other.__amount
                                                                                    
    def times(self, multiplier):                                                    
        return Franc(self.__amount * multiplier)
```
### (4) テストを実行し、すべて成功することを確認する。-> <font color="green">OK</font>
テストは成功しています。

```
$ pytest -v

...(snip)

tests/test_money.py::MoneyTest::testEquality PASSED                      [ 33%]
tests/test_money.py::MoneyTest::testFrancMultiplication PASSED           [ 66%]
tests/test_money.py::MoneyTest::testMultiplication PASSED                [100%]
```

## 第6章: テスト不足に気づいたら (Equality for All, Redux)
第5章では、フラン通貨に関わる`金額(通貨単位あたりの額)に数値(通貨単位数)を掛け、金額を得る。`の仕組みを実現したわけですが、その方法は、これまでのドル通貨の仕組みをまるまるコピーして対応したため、重複箇所が大量に発生してしまいました。
重複を排除する営みとして、まず、手始めに、第3章で手掛けた`ある5ドルは、他の5ドルと同じ価値である`と`ある5フランは、他の5フランと同じ価値である`の部分に着手します。

### (1) まずは、小さいテストを1つ書く。
ドル通貨のテスト`ある5ドルは、他の5ドルと等価である`を、フラン通貨でも確認できるように、テストを追加します。

```python:tests/test_money.py
from testtools import TestCase
from example.dollar import Dollar
from example.franc import Franc

class MoneyTest(TestCase):
    def testMultiplication(self):
        five = Dollar(5)
        self.assertEqual(Dollar(10), five.times(2))
        self.assertEqual(Dollar(15), five.times(3))
    
    def testEquality(self):
        self.assertTrue(Dollar(5) == Dollar(5))
        self.assertFalse(Dollar(5) == Dollar(6))
        self.assertTrue(Franc(5) == Franc(5))
        self.assertFalse(Franc(5) == Franc(6))

    def testFrancMultiplication(self):
        five = Franc(5)
        self.assertEqual(Franc(10), five.times(2))
        self.assertEqual(Franc(15), five.times(3))
```
### (2) テストを実行し、すべて成功することを確認する。-> <font color="green">OK</font>
追加したテスト部分も、問題なく成功しています。

```
$ pytest -v

...(snip)

tests/test_money.py::MoneyTest::testEquality PASSED                      [ 33%]
tests/test_money.py::MoneyTest::testFrancMultiplication PASSED           [ 66%]
tests/test_money.py::MoneyTest::testMultiplication PASSED                [100%]
```

### (3) リファクタリングを行い、重複を除去する。
ここでは、次のリファクタリングを実施します。

- 親クラス`Money`を新たに定義する
- `Dollar`, `Franc`クラスで定義していた特殊メソッド`__eq__`の重複箇所を排除し、`Money`クラスで共通化する

```python:example/money.py
class Money:
    def __init__(self, amount):  
        self.__amount = amount

    def __eq__(self, other):
        return self.__amount == other.__amount
```
```python:example/dollar.py
from example.money import Money

class Dollar(Money):
    def __init__(self, amount):  
        self.__amount = amount
    
    def times(self, multiplier):                                                    
        return Dollar(self.__amount * multiplier)
```
```python:example/franc.py
from example.money import Money

class Franc(Money):
    def __init__(self, amount):  
        self.__amount = amount
    
    def times(self, multiplier):                                                    
        return Franc(self.__amount * multiplier)
```
### (4) 再びテストを実行し、すべて成功することを確認する。-> <font color="red">NG</font>

<font color="red">想定外に、テストが全滅してしまいました。</font>

```
$ pytest -v

...(snip)
tests/test_money.py::MoneyTest::testEquality FAILED                      [ 33%]
tests/test_money.py::MoneyTest::testFrancMultiplication FAILED           [ 66%]
tests/test_money.py::MoneyTest::testMultiplication FAILED                [100%]
================================== FAILURES ===================================
______________________________ MoneyTest.testEquality _________________________
NOTE: Incompatible Exception Representation, displaying natively:

testtools.testresult.real._StringException: Traceback (most recent call last):

...(snip)

  File "/Users/ttsubo/source/study_of_test_driven_development/example/money.py", line 6, in __eq__
    return self.__amount == other.__amount
AttributeError: 'Dollar' object has no attribute '_Money__amount'

________________________ MoneyTest.testFrancMultiplication ____________________
NOTE: Incompatible Exception Representation, displaying natively:

testtools.testresult.real._StringException: Traceback (most recent call last):

...(snip)

  File "/Users/ttsubo/source/study_of_test_driven_development/example/money.py", line 6, in __eq__
    return self.__amount == other.__amount
AttributeError: 'Franc' object has no attribute '_Money__amount'

__________________________ MoneyTest.testMultiplication ______________________
NOTE: Incompatible Exception Representation, displaying natively:

testtools.testresult.real._StringException: Traceback (most recent call last):

...(snip)

File "/Users/ttsubo/source/study_of_test_driven_development/example/money.py", line 6, in __eq__
    return self.__amount == other.__amount
AttributeError: 'Dollar' object has no attribute '_Money__amount'
```
### (5) リファクタリングにより、テストが失敗した原因に対処する。
テストが失敗した原因は、`Dollar`, `Franc`のオブジェクト化のタイミングで、親クラス`Money`のインスタンス変数`amount`にも、値を保存する必要がありましたので、コード修正します。

```python:example/dollar.py
from example.money import Money

class Dollar(Money):
    def __init__(self, amount):  
        super(Dollar, self).__init__(amount)
        self.__amount = amount
    
    def times(self, multiplier):                                                    
        return Dollar(self.__amount * multiplier)
```
```python:example/franc.py
from example.money import Money

class Franc(Money):
    def __init__(self, amount):  
        super(Franc, self).__init__(amount)
        self.__amount = amount
    
    def times(self, multiplier):                                                    
        return Franc(self.__amount * multiplier)
```

### (6) 再びテストを実行し、すべて成功することを確認する。-> <font color="green">OK</font>

今度は、テストは成功しました。
<font color="blue">ちょっとしたリファクタリングのミスにも、すぐに気がつくことができるのは、まさに、テスト駆動開発のおかげですね。</font>

```
$ pytest -v

...(snip)
tests/test_money.py::MoneyTest::testEquality PASSED                      [ 33%]
tests/test_money.py::MoneyTest::testFrancMultiplication PASSED           [ 66%]
tests/test_money.py::MoneyTest::testMultiplication PASSED                [100%]
```

## 第7章: 疑念をテストに翻訳する (Apples and Oranges)
これまで、多通貨として、"ドル通貨"と"フラン通貨"に対応してきました。
ここで、疑問に思うのが、「**ドル通貨**と**フラン通貨**を比較したらどうなるだろうか？」ということです。

### (1) まずは、小さいテストを1つ書く。
**ドル通貨**と**フラン通貨**が等しくないことを確認するテストを追加します。

```python:tests/test_money.py
from testtools import TestCase
from example.dollar import Dollar
from example.franc import Franc

class MoneyTest(TestCase):
    def testMultiplication(self):
        five = Dollar(5)
        self.assertEqual(Dollar(10), five.times(2))
        self.assertEqual(Dollar(15), five.times(3))
    
    def testEquality(self):
        self.assertTrue(Dollar(5) == Dollar(5))
        self.assertFalse(Dollar(5) == Dollar(6))
        self.assertTrue(Franc(5) == Franc(5))
        self.assertFalse(Franc(5) == Franc(6))
        self.assertFalse(Franc(5) == Dollar(5))

    def testFrancMultiplication(self):
        five = Franc(5)
        self.assertEqual(Franc(10), five.times(2))
        self.assertEqual(Franc(15), five.times(3))
```
### (2) テストを実行する。-> <font color="red">NG</font>
テストは失敗します。つまり、ドルとフランが等しいという結果になってしまってます。

```
$ pytest -v

...(snip)

tests/test_money.py::MoneyTest::testEquality FAILED                      [ 33%]
tests/test_money.py::MoneyTest::testFrancMultiplication PASSED           [ 66%]
tests/test_money.py::MoneyTest::testMultiplication PASSED                [100%]
================================== FAILURES ===================================
______________________________ MoneyTest.testEquality _________________________
NOTE: Incompatible Exception Representation, displaying natively:

testtools.testresult.real._StringException: Traceback (most recent call last):
  File "/Users/ttsubo/source/study_of_test_driven_development/tests/test_money.py", line 16, in testEquality
    self.assertFalse(Franc(5) == Dollar(5))
  File "/Users/ttsubo/.pyenv/versions/3.8.0/lib/python3.8/unittest/case.py", line 759, in assertFalse
    raise self.failureException(msg)
AssertionError: True is not false
```

### (3) テストが失敗した原因に対処する。
等価性比較を行うには、`Dollar`オブジェクトと`Franc`オブジェクトを比較する必要がありましたので、`Money`クラスの特殊メソッド`__eq__`の判定ロジック箇所を、修正します。

```python:example/money.py
class Money:
    def __init__(self, amount):  
        self.__amount = amount

    def __eq__(self, other):
        return (self.__amount == other.__amount
                and self.__class__.__name__ == other.__class__.__name__)
```

### (4) 再びテストを実行し、すべて成功することを確認する。-> <font color="green">OK</font>

今度は、テストは成功しました。

```
$ pytest -v

...(snip)
tests/test_money.py::MoneyTest::testEquality PASSED                      [ 33%]
tests/test_money.py::MoneyTest::testFrancMultiplication PASSED           [ 66%]
tests/test_money.py::MoneyTest::testMultiplication PASSED                [100%]
```


## 第8章: 実装を隠す (Makin’ Objects)
第6,7章で、等価性比較に関わるリファクタリングを行いました。
これから、しばらくの間、`金額(通貨単位あたりの額)に数値(通貨単位数)を掛け、金額を得る。`という仕組みの重複箇所の排除を試みます。

### (1) まずは、小さいテストを書く。
ここでは、`Dollar`, `Franc`クラスで定義していた`times`メソッドの重複箇所を排除し、`Money`クラスで共通化するための前段として、次のリファクタリングを実施することを想定して、テストを修正します。

- `Money`クラスにクラスメソッド`dollar`を定義して、`Dollar`オブジェクトを生成するようにする
- `Money`クラスにクラスメソッド`franc`を定義して、`Franc`オブジェクトを生成するようにする

```python:tests/test_money.py
from testtools import TestCase
from example.money import Money

class MoneyTest(TestCase):
    def testMultiplication(self):
        five = Money.dollar(5)
        self.assertEqual(Money.dollar(10), five.times(2))
        self.assertEqual(Money.dollar(15), five.times(3))
    
    def testEquality(self):
        self.assertTrue(Money.dollar(5) == Money.dollar(5))
        self.assertFalse(Money.dollar(5) == Money.dollar(6))
        self.assertTrue(Money.franc(5) == Money.franc(5))
        self.assertFalse(Money.franc(5) == Money.franc(6))
        self.assertFalse(Money.franc(5) == Money.dollar(5))

    def testFrancMultiplication(self):
        five = Money.franc(5)
        self.assertEqual(Money.franc(10), five.times(2))
        self.assertEqual(Money.franc(15), five.times(3))
```

### (2) リファクタリングを行う。
ここでは、`Dollar`, `Franc`クラスで定義していた`times`メソッドの重複箇所を排除し、`Money`クラスで共通化するための前段として、次のリファクタリングを実施します。

- `Money`クラスにクラスメソッド`dollar`を定義して、`Dollar`オブジェクトを生成するようにする
- `Money`クラスにクラスメソッド`franc`を定義して、`Franc`オブジェクトを生成するようにする

```python:example/money.py
from abc import ABCMeta, abstractmethod
from example.dollar import Dollar
from example.franc import Franc

class Money(metaclass=ABCMeta):
    def __init__(self, amount):  
        self.__amount = amount

    def __eq__(self, other):
        return (self.__amount == other.__amount
                and self.__class__.__name__ == other.__class__.__name__)

    @abstractmethod
    def times(self, multiplier):
        pass

    @classmethod
    def dollar(cls, amount):
        return Dollar(amount)

    @classmethod
    def franc(cls, amount):
        return Franc(amount)
```

### (3) テストを実行する。-> <font color="red">NG</font>
テストが起動できず、エラーになってしまいました。Pythonのモジュールの循環インポートが原因のようですね。

```
$ pytest -v

...(snip)

tests/test_money.py::MoneyTest::testEquality FAILED                      [ 33%]
tests/test_money.py::MoneyTest::testFrancMultiplication FAILED           [ 66%]
tests/test_money.py::MoneyTest::testMultiplication PASSED                [100%]
=================================== ERRORs ====================================
ImportError while importing test module '/Users/ttsubo/source/study_of_test_driven_development/tests/test_money.py'.
Hint: make sure your test modules/packages have valid Python names.
Traceback:
tests/test_money.py:2: in <module>
    from example.money import Money
example/money.py:2: in <module>
    from example.dollar import Dollar
example/dollar.py:1: in <module>
    from example.money import Money
E   ImportError: cannot import name 'Money' from partially initialized module 'example.money' (most likely due to a circular import) (/Users/ttsubo/source/study_of_test_driven_development/example/money.py)
```

### (4) テストが失敗した原因に対処する。
Pythonのモジュールの循環インポートが原因なので、`Dollar`クラスと、`Franc`クラスを、money.pyで定義するように修正します。

```python:example/money.py
from abc import ABCMeta, abstractmethod

class Money(metaclass=ABCMeta):
    def __init__(self, amount):  
        self.__amount = amount

    def __eq__(self, other):
        return (self.__amount == other.__amount
                and self.__class__.__name__ == other.__class__.__name__)

    @abstractmethod
    def times(self, multiplier):
        pass

    @classmethod
    def dollar(cls, amount):
        return Dollar(amount)

    @classmethod
    def franc(cls, amount):
        return Franc(amount)


class Dollar(Money):
    def __init__(self, amount):  
        super(Dollar, self).__init__(amount)
        self.__amount = amount
    
    def times(self, multiplier):                                                    
        return Dollar(self.__amount * multiplier)


class Franc(Money):
    def __init__(self, amount):
        super(Franc, self).__init__(amount)
        self.__amount = amount

    def times(self, multiplier):
        return Franc(self.__amount * multiplier)
```
### (5) 再びテストを実行し、すべて成功することを確認する。-> <font color="green">OK</font>

今度は、テストは成功しました。

```
$ pytest -v

...(snip)
tests/test_money.py::MoneyTest::testEquality PASSED                      [ 33%]
tests/test_money.py::MoneyTest::testFrancMultiplication PASSED           [ 66%]
tests/test_money.py::MoneyTest::testMultiplication PASSED                [100%]
```

## 第9章: 歩幅の調整 (Times We’re Livin’ In)
前章に引き続き、`金額(通貨単位あたりの額)に数値(通貨単位数)を掛け、金額を得る。`という仕組みの重複箇所の排除を試みます。

### (1) まずは、小さいテストを書く。
ここでは、`Dollar`, `Franc`クラスで定義していた`times`メソッドの重複箇所を排除し、`Money`クラスで共通化するための前段として、`Dollar`オブジェクトおよび`Franc`オブジェクトを区別することを目的とした**"currency"**という概念を適用すること想定して、テスト`testCurrency`を追加します。

- `Dollar`オブジェクトを生成する際に、インスタンス変数`__currency`に、"USD"を設定する
- `Dollar`オブジェクトのプライベートメンバ`__currency`を参照できるように、`currency`メソッドを定義する
- `Franc`オブジェクトを生成する際に、インスタンス変数`__currency`に、"CHF"を設定する
- `Franc`オブジェクトのプライベートメンバ`__currency`を参照できるように、`currency`メソッドを定義する

```python:tests/test_money.py
from testtools import TestCase
from example.money import Money

class MoneyTest(TestCase):
    def testMultiplication(self):
        five = Money.dollar(5)
        self.assertEqual(Money.dollar(10), five.times(2))
        self.assertEqual(Money.dollar(15), five.times(3))
    
    def testEquality(self):
        self.assertTrue(Money.dollar(5) == Money.dollar(5))
        self.assertFalse(Money.dollar(5) == Money.dollar(6))
        self.assertTrue(Money.franc(5) == Money.franc(5))
        self.assertFalse(Money.franc(5) == Money.franc(6))
        self.assertFalse(Money.franc(5) == Money.dollar(5))

    def testFrancMultiplication(self):
        five = Money.franc(5)
        self.assertEqual(Money.franc(10), five.times(2))
        self.assertEqual(Money.franc(15), five.times(3))
    
    def testCurrency(self):
        self.assertEqual("USD", Money.dollar(1).currency())
        self.assertEqual("CHF", Money.franc(1).currency())
```

### (2) リファクタリングを行う。
ここでは、`Dollar`, `Franc`クラスで定義していた`times`メソッドの重複箇所を排除し、`Money`クラスで共通化するための前段として、`Dollar`オブジェクトおよび`Franc`オブジェクトを区別することを目的とした`currency`という概念を適用します。

- `Dollar`オブジェクトを生成する際に、インスタンス変数`__currency`に、"USD"を設定する
- `Dollar`オブジェクトのプライベートメンバ`__currency`を参照できるように、`currency`メソッドを定義する
- `Franc`オブジェクトを生成する際に、インスタンス変数`__currency`に、"CHF"を設定する
- `Franc`オブジェクトのプライベートメンバ`__currency`を参照できるように、`currency`メソッドを定義する

```python:example/money.py
from abc import ABCMeta, abstractmethod

class Money(metaclass=ABCMeta):
    def __init__(self, amount, currency):  
        self.__amount = amount
        self.__currency = currency

    def __eq__(self, other):
        return (self.__amount == other.__amount
                and self.__class__.__name__ == other.__class__.__name__)

    @abstractmethod
    def times(self, multiplier):
        pass

    def currency(self):
        return self.__currency

    @classmethod
    def dollar(cls, amount):
        return Dollar(amount, "USD")

    @classmethod
    def franc(cls, amount):
        return Franc(amount, "CHF")


class Dollar(Money):
    def __init__(self, amount, currency):
        super().__init__(amount, currency)
        self.__amount = amount
    
    def times(self, multiplier):                                                    
        return Money.dollar(self.__amount * multiplier)


class Franc(Money):
    def __init__(self, amount, currency):
        super().__init__(amount, currency)
        self.__amount = amount

    def times(self, multiplier):
        return Money.franc(self.__amount * multiplier)
```
### (3) テストを実行し、すべて成功することを確認する。-> <font color="green">OK</font>
期待通りに、テストは成功しました。

```
$ pytest -v

...(snip)
tests/test_money.py::MoneyTest::testCurrency PASSED                      [ 25%]
tests/test_money.py::MoneyTest::testEquality PASSED                      [ 50%]
tests/test_money.py::MoneyTest::testFrancMultiplication PASSED           [ 75%]
tests/test_money.py::MoneyTest::testMultiplication PASSED                [100%]
```

## 第10章: テストに聞いてみる (Interesting Times)
これまで着手してきた、`金額(通貨単位あたりの額)に数値(通貨単位数)を掛け、金額を得る。`という仕組みの重複箇所の排除を試みを完成させます。

### (1) まずは、小さいテストを書く。
`金額(通貨単位あたりの額)に数値(通貨単位数)を掛け、金額を得る。`という仕組みの重複箇所の排除のリファクタリングが完成したことを想定して、テスト`testDifferentClassEquality`を追加します。

```python:tests/test_money.py
from testtools import TestCase
from example.money import Money, Franc

class MoneyTest(TestCase):
    def testMultiplication(self):
        five = Money.dollar(5)
        self.assertEqual(Money.dollar(10), five.times(2))
        self.assertEqual(Money.dollar(15), five.times(3))
    
    def testEquality(self):
        self.assertTrue(Money.dollar(5) == Money.dollar(5))
        self.assertFalse(Money.dollar(5) == Money.dollar(6))
        self.assertTrue(Money.franc(5) == Money.franc(5))
        self.assertFalse(Money.franc(5) == Money.franc(6))
        self.assertFalse(Money.franc(5) == Money.dollar(5))

    def testFrancMultiplication(self):
        five = Money.franc(5)
        self.assertEqual(Money.franc(10), five.times(2))
        self.assertEqual(Money.franc(15), five.times(3))
    
    def testCurrency(self):
        self.assertEqual("USD", Money.dollar(1).currency())
        self.assertEqual("CHF", Money.franc(1).currency())

    def testDifferentClassEquality(self):
        self.assertTrue(Money(10, "CHF") == Franc(10, "CHF"))
```
### (2) リファクタリングを行う。
次のリファクタリングを実施します。

- `Dollar`, `Franc`クラスで定義していた`times`メソッドを削除する
- `Money`クラスに、共通化された`times`メソッドとして定義する
- `Money`オブジェクトの等価性比較のやり方を見直し、特殊メソッド`__eq__`の修正する
- `Money`オブジェクト生成を想定して、抽象クラスとして扱っていた`Money`クラスも変更する

```python:example/money.py
class Money():
    def __init__(self, amount, currency):  
        self.__amount = amount
        self.__currency = currency

    def __eq__(self, other):
        return (self.__amount == other.__amount
                and self.currency() == other.currency())

    def times(self, multiplier):
        return Money(self.__amount * multiplier, self.__currency)

    def currency(self):
        return self.__currency

    @classmethod
    def dollar(cls, amount):
        return Dollar(amount, "USD")

    @classmethod
    def franc(cls, amount):
        return Franc(amount, "CHF")


class Dollar(Money):
    def __init__(self, amount, currency):
        super().__init__(amount, currency)


class Franc(Money):
    def __init__(self, amount, currency):
        super().__init__(amount, currency)
```

### (3) テストを実行し、すべて成功することを確認する。-> <font color="green">OK</font>
期待通りに、テストは成功しました。

```
$ pytest -v

...(snip)
tests/test_money.py::MoneyTest::testCurrency PASSED                      [ 20%]
tests/test_money.py::MoneyTest::testDifferentClassEquality PASSED        [ 40%]
tests/test_money.py::MoneyTest::testEquality PASSED                      [ 60%]
tests/test_money.py::MoneyTest::testFrancMultiplication PASSED           [ 80%]
tests/test_money.py::MoneyTest::testMultiplication PASSED                [100%]
```

## 第11章: 不要になったら消す (The Root of All Evil)
`Dollar`, `Franc`クラスも、不要にできるので、これまで実施してきたリファクタリングを完結させます。

### (1) まずは、小さいテストを書く。
`金額(通貨単位あたりの額)に数値(通貨単位数)を掛け、金額を得る。`という仕組みのリファクタリングが完成したことを想定して、テスト自体もリファクタリングします。

```python:tests/test_money.py
from testtools import TestCase
from example.money import Money

class MoneyTest(TestCase):
    def testMultiplication(self):
        five = Money.dollar(5)
        self.assertEqual(Money.dollar(10), five.times(2))
        self.assertEqual(Money.dollar(15), five.times(3))
    
    def testEquality(self):
        self.assertTrue(Money.dollar(5) == Money.dollar(5))
        self.assertFalse(Money.dollar(5) == Money.dollar(6))
        self.assertFalse(Money.franc(5) == Money.dollar(5))

    def testCurrency(self):
        self.assertEqual("USD", Money.dollar(1).currency())
        self.assertEqual("CHF", Money.franc(1).currency())
```


### (2) リファクタリングを行う。
すでに、`Money`オブジェクトにて、"**ドル通貨**"、および"**フラン通貨**"を区別できるようになっているので、`Dollar`, `Franc`クラスの定義は、不要になります。
リファクタリングの完結として、`Dollar`, `Franc`クラスを削除します。

```python:example/money.py
class Money():
    def __init__(self, amount, currency):  
        self.__amount = amount
        self.__currency = currency

    def __eq__(self, other):
        return (self.__amount == other.__amount
                and self.currency() == other.currency())

    def times(self, multiplier):
        return Money(self.__amount * multiplier, self.__currency)

    def currency(self):
        return self.__currency

    @classmethod
    def dollar(cls, amount):
        return Money(amount, "USD")

    @classmethod
    def franc(cls, amount):
        return Money(amount, "CHF")
```

### (3) テストを実行し、すべて成功することを確認する。-> <font color="green">OK</font>
期待通りに、テストは成功しました。

```
$ pytest -v

...(snip)
tests/test_money.py::MoneyTest::testCurrency PASSED                      [ 33%]
tests/test_money.py::MoneyTest::testEquality PASSED                      [ 66%]
tests/test_money.py::MoneyTest::testMultiplication PASSED                [100%]
```

## 第12章: 設計とメタファー (Addition, Finally)
これから、`通貨の異なる2つの金額を足し、通貨間の為替レートに基づいて換算された金額を得る。`の要件を満足する仕組みを実現していきます。なお、この要件は、次の２つの`ToDo`に分解することができそうです。

- `$5 + $5 = $10`
- `$5 + 10 CHF = $10(レートが 2:1 の場合)`

まずは、`2つの金額を足し、金額を得る。`の仕組みとして、`$5 + $5 = $10`が成立するように、コード化を進めています。

### (1) まずは、小さいテストを書く。
`2つの金額を足し、金額を得る。`の仕組みを確認するため、テスト`testSimpleAddition`を追加します。
さらに、今後の設計について、次の二つの概念を念頭に置くものとします。

- `通貨を換算するのは銀行の責務であるべきという考え`に基づく`Bankオブジェクト`という概念
- `為替レートを適用することによって得られる換算結果`を保存するための`reduced`変数という概念

```python:tests/test_money.py
from testtools import TestCase
from example.money import Money
from example.bank import Bank

class MoneyTest(TestCase):
    def testMultiplication(self):
        five = Money.dollar(5)
        self.assertEqual(Money.dollar(10), five.times(2))
        self.assertEqual(Money.dollar(15), five.times(3))

    def testEquality(self):
        self.assertTrue(Money.dollar(5) == Money.dollar(5))
        self.assertFalse(Money.dollar(5) == Money.dollar(6))
        self.assertFalse(Money.franc(5) == Money.dollar(5))

    def testCurrency(self):
        self.assertEqual("USD", Money.dollar(1).currency())
        self.assertEqual("CHF", Money.franc(1).currency())

    def testSimpleAddition(self):
        five = Money.dollar(5)
        _sum = five.plus(five)
        bank = Bank()
        reduced = bank.reduce(_sum, "USD")
        self.assertEqual(Money.dollar(10), reduced)
```

### (2) 小さい変更を行う。
この章で扱う実装は、だいぶややこしいので、結構、大きめな変更になってしまいますが、こんな感じです。

- `2つの金額を足し、金額を得る。`の仕組みを実現するために、`Money`クラスに、`plus`メソッドを追加します。
- 抽象クラス`Expression`を継承して、`Money`オブジェクトは生成される
- `Bank`クラスの`reduce`メソッドについては、ここでは、仮実装とする

```python:example/money.py
from example.expression import Expression

class Money(Expression):
    def __init__(self, amount, currency):  
        self.__amount = amount
        self.__currency = currency

    def __eq__(self, other):
        return (self.__amount == other.__amount
                and self.currency() == other.currency())

    def times(self, multiplier):
        return Money(self.__amount * multiplier, self.__currency)

    def plus(self, addend):
        return Money(self.__amount + addend.__amount, self.__currency)

    def currency(self):
        return self.__currency

    @classmethod
    def dollar(cls, amount):
        return Money(amount, "USD")

    @classmethod
    def franc(cls, amount):
        return Money(amount, "CHF")
```
```python:example/bank.py
from example.money import Money

class Bank():
    def reduce(self, source , toCurrency):
        return Money.dollar(10)
```
```python:example/expression.py
from abc import ABCMeta

class Expression(metaclass=ABCMeta):
    pass
```

### (3) テストを実行し、すべて成功することを確認する。-> <font color="green">OK</font>
テストは成功しています。

```
$ pytest -v

...(snip)
tests/test_money.py::MoneyTest::testCurrency PASSED                      [ 25%]
tests/test_money.py::MoneyTest::testEquality PASSED                      [ 50%]
tests/test_money.py::MoneyTest::testMultiplication PASSED                [ 75%]
tests/test_money.py::MoneyTest::testSimpleAddition PASSED                [100%]
```

## 第13章: 実装を導くテスト (Make It)
引き続き、`2つの金額を足し、金額を得る。`の仕組みとして、`$5 + $5 = $10`が成立するように、コード化を進めます。
前回は、`Bank`クラスの`reduce`メソッドの振る舞いを、仮実装として定義していた箇所に着手します。
なお、ここでの取り組みは、結構なボリュームになりそうなので、以下のステップで進めていきます。

- STEP1: `$5 + $5`の処理を書く
- STEP2: `Bank`クラスの`reduce`メソッドの仮実装を実体化する
- STEP3: `Bank`クラスの`reduce`メソッドの仮実装を実体化する(Cont.)

### STEP1: `$5 + $5`の処理を書く
`$5 + $5 = $10`を実現する前段として、`$5 + $5`の部分の処理を実体化します。

#### (1) まずは、小さいテストを書く。
テスト`testPlusReturnsSum`を追加します。

```python:tests/test_money.py
from testtools import TestCase
from example.money import Money
from example.bank import Bank

class MoneyTest(TestCase):
    def testMultiplication(self):
        five = Money.dollar(5)
        self.assertEqual(Money.dollar(10), five.times(2))
        self.assertEqual(Money.dollar(15), five.times(3))

    def testEquality(self):
        self.assertTrue(Money.dollar(5) == Money.dollar(5))
        self.assertFalse(Money.dollar(5) == Money.dollar(6))
        self.assertFalse(Money.franc(5) == Money.dollar(5))

    def testCurrency(self):
        self.assertEqual("USD", Money.dollar(1).currency())
        self.assertEqual("CHF", Money.franc(1).currency())

    def testSimpleAddition(self):
        five = Money.dollar(5)
        _sum = five.plus(five)
        bank = Bank()
        reduced = bank.reduce(_sum, "USD")
        self.assertEqual(Money.dollar(10), reduced)

    def testPlusReturnsSum(self):
        five = Money.dollar(5)
        _sum = five.plus(five)
        self.assertEqual(five, _sum.augend)
        self.assertEqual(five, _sum.addend)
```
#### (2) 小さい変更を行う。
次の変更を行います。

- `Sum`クラスを新たに定義する
- `Sum`オブジェクトでは、2つの金額: "augend(被加算数)"と"addend(加数)"の状態を保存する
- `Money`クラスの`plus`メソッドでは、`Sum`オブジェクトを返却するように変更する

```python:example/money.py
from example.expression import Expression

class Money(Expression):
    def __init__(self, amount, currency):  
        self.__amount = amount
        self.__currency = currency

    def __eq__(self, other):
        return (self.__amount == other.__amount
                and self.currency() == other.currency())

    def times(self, multiplier):
        return Money(self.__amount * multiplier, self.__currency)

    def plus(self, addend):
        return Sum(self, addend)

    def currency(self):
        return self.__currency

    @classmethod
    def dollar(cls, amount):
        return Money(amount, "USD")

    @classmethod
    def franc(cls, amount):
        return Money(amount, "CHF")

class Sum(Expression):
    def __init__(self, augend, addend):
        self.augend = augend
        self.addend = addend
```

#### (3) テストを実行し、すべて成功することを確認する。-> <font color="green">OK</font>
テストは成功しています。

```
$ pytest -v

...(snip)
tests/test_money.py::MoneyTest::testCurrency PASSED                      [ 20%]
tests/test_money.py::MoneyTest::testEquality PASSED                      [ 40%]
tests/test_money.py::MoneyTest::testMultiplication PASSED                [ 60%]
tests/test_money.py::MoneyTest::testPlusReturnsSum PASSED                [ 80%]
tests/test_money.py::MoneyTest::testSimpleAddition PASSED                [100%]
```

### STEP2: `Bank`クラスの`reduce`メソッドの仮実装を実体化する
`Bank`クラスの`reduce`メソッドを実体化していきます。ここでは、`Sum`オブジェクトを対象にします。

#### (1) まずは、小さいテストを書く。
テスト`testReduceSum`を追加します。

```python:tests/test_money.py
from testtools import TestCase
from example.money import Money, Sum
from example.bank import Bank

class MoneyTest(TestCase):
    def testMultiplication(self):
        five = Money.dollar(5)
        self.assertEqual(Money.dollar(10), five.times(2))
        self.assertEqual(Money.dollar(15), five.times(3))

    def testEquality(self):
        self.assertTrue(Money.dollar(5) == Money.dollar(5))
        self.assertFalse(Money.dollar(5) == Money.dollar(6))
        self.assertFalse(Money.franc(5) == Money.dollar(5))

    def testCurrency(self):
        self.assertEqual("USD", Money.dollar(1).currency())
        self.assertEqual("CHF", Money.franc(1).currency())

    def testSimpleAddition(self):
        five = Money.dollar(5)
        _sum = five.plus(five)
        bank = Bank()
        reduced = bank.reduce(_sum, "USD")
        self.assertEqual(Money.dollar(10), reduced)

    def testPlusReturnsSum(self):
        five = Money.dollar(5)
        _sum = five.plus(five)
        self.assertEqual(five, _sum.augend)
        self.assertEqual(five, _sum.addend)

    def testReduceSum(self):
        _sum = Sum(Money.dollar(3), Money.dollar(4))
        bank = Bank()
        result = bank.reduce(_sum, "USD")
        self.assertEqual(Money.dollar(7), result)
```
#### (2) 小さい変更を行う。
次の変更を行います。

- `Bank`クラスの`reduce`メソッドでは、`Sum`オブジェクトの`reduce`メソッドの処理結果を返却できるようにする
- `Sum`クラスの`reduce`メソッドにて、`2つの金額を足し、金額を得る。`の仕組みを定義する
- `Money`クラスに、プライベートメンバ`__amount`を外部から参照できるよう、`amount`メソッドを定義する

```python:example/money.py
from example.expression import Expression

class Money(Expression):
    def __init__(self, amount, currency):  
        self.__amount = amount
        self.__currency = currency

    def __eq__(self, other):
        return (self.__amount == other.__amount
                and self.currency() == other.currency())

    def times(self, multiplier):
        return Money(self.__amount * multiplier, self.__currency)

    def plus(self, addend):
        return Sum(self, addend)

    def amount(self):
        return self.__amount

    def currency(self):
        return self.__currency

    @classmethod
    def dollar(cls, amount):
        return Money(amount, "USD")

    @classmethod
    def franc(cls, amount):
        return Money(amount, "CHF")

class Sum(Expression):
    def __init__(self, augend, addend):
        self.augend = augend
        self.addend = addend

    def reduce(self, toCurrency):
        amount = self.augend.amount() + self.addend.amount()
        return Money(amount, toCurrency)
```
```python:example/bank.py
class Bank():
    def reduce(self, source , toCurrency):
        return source.reduce(toCurrency)
```

#### (3) テストを実行し、すべて成功することを確認する。-> <font color="green">OK</font>
テストは成功しています。

```
$ pytest -v

...(snip)
tests/test_money.py::MoneyTest::testCurrency PASSED                      [ 16%]
tests/test_money.py::MoneyTest::testEquality PASSED                      [ 33%]
tests/test_money.py::MoneyTest::testMultiplication PASSED                [ 50%]
tests/test_money.py::MoneyTest::testPlusReturnsSum PASSED                [ 66%]
tests/test_money.py::MoneyTest::testReduceSum PASSED                     [ 83%]
tests/test_money.py::MoneyTest::testSimpleAddition PASSED                [100%]
```

### STEP3: `Bank`クラスの`reduce`メソッドの仮実装を実体化する(Cont.)
`Bank`クラスの`reduce`メソッドを実体化していきます。ここでは、`Money`オブジェクトを対象にします。

#### (1) まずは、小さいテストを書く。
テスト`testReduceMoney`を追加します。

```python:tests/test_money.py
from testtools import TestCase
from example.money import Money, Sum
from example.bank import Bank

class MoneyTest(TestCase):
    def testMultiplication(self):
        five = Money.dollar(5)
        self.assertEqual(Money.dollar(10), five.times(2))
        self.assertEqual(Money.dollar(15), five.times(3))

    def testEquality(self):
        self.assertTrue(Money.dollar(5) == Money.dollar(5))
        self.assertFalse(Money.dollar(5) == Money.dollar(6))
        self.assertFalse(Money.franc(5) == Money.dollar(5))

    def testCurrency(self):
        self.assertEqual("USD", Money.dollar(1).currency())
        self.assertEqual("CHF", Money.franc(1).currency())

    def testSimpleAddition(self):
        five = Money.dollar(5)
        _sum = five.plus(five)
        bank = Bank()
        reduced = bank.reduce(_sum, "USD")
        self.assertEqual(Money.dollar(10), reduced)
    
    def testPlusReturnsSum(self):
        five = Money.dollar(5)
        _sum = five.plus(five)
        self.assertEqual(five, _sum.augend)
        self.assertEqual(five, _sum.addend)

    def testReduceSum(self):
        _sum = Sum(Money.dollar(3), Money.dollar(4))
        bank = Bank()
        result = bank.reduce(_sum, "USD")
        self.assertEqual(Money.dollar(7), result)

    def testReduceMoney(self):
        bank = Bank()
        result = bank.reduce(Money.dollar(1), "USD")
        self.assertEqual(Money.dollar(1), result)
```
#### (2) 小さい変更を行う。
次の変更を行います。

- `Bank`クラスの`reduce`メソッドでは、`Money`オブジェクトの`reduce`メソッドの処理結果を返却できるようにする
- `Money `クラスの`reduce`メソッドを定義する（仮実装）
- 抽象クラス`Expression`に、抽象メソッド`reduce`を定義して、`Money`クラスおよび、`Sum`クラスでの`reduce`メソッド定義を強制させる

```python:example/money.py
from example.expression import Expression

class Money(Expression):
    def __init__(self, amount, currency):  
        self.__amount = amount
        self.__currency = currency

    def __eq__(self, other):
        return (self.__amount == other.__amount
                and self.currency() == other.currency())

    def times(self, multiplier):
        return Money(self.__amount * multiplier, self.__currency)

    def plus(self, addend):
        return Sum(self, addend)

    def reduce(self, toCurrency):
        return self

    def amount(self):
        return self.__amount

    def currency(self):
        return self.__currency

    @classmethod
    def dollar(cls, amount):
        return Money(amount, "USD")

    @classmethod
    def franc(cls, amount):
        return Money(amount, "CHF")

class Sum(Expression):
    def __init__(self, augend, addend):
        self.augend = augend
        self.addend = addend

    def reduce(self, toCurrency):
        amount = self.augend.amount() + self.addend.amount()
        return Money(amount, toCurrency)
```
```python:example/expression.py
from abc import ABCMeta, abstractmethod

class Expression(metaclass=ABCMeta):
    @abstractmethod
    def reduce(self, toCurrency):
        pass
```

#### (3) テストを実行し、すべて成功することを確認する。-> <font color="green">OK</font>
テストは成功しています。

```
$ pytest -v

...(snip)
tests/test_money.py::MoneyTest::testCurrency PASSED                      [ 14%]
tests/test_money.py::MoneyTest::testEquality PASSED                      [ 28%]
tests/test_money.py::MoneyTest::testMultiplication PASSED                [ 42%]
tests/test_money.py::MoneyTest::testPlusReturnsSum PASSED                [ 57%]
tests/test_money.py::MoneyTest::testReduceMoney PASSED                   [ 71%]
tests/test_money.py::MoneyTest::testReduceSum PASSED                     [ 85%]
tests/test_money.py::MoneyTest::testSimpleAddition PASSED                [100%]
```

## 第14章: 学習用テストと回帰テスト (Change)
ここでは、2フランを1ドルに換算する処理を実現していきます。

- STEP1: 2フランを1ドルに換算する処理（為替レートは、仮実装）
- STEP2: 2フランを1ドルに換算する処理（為替レート表の実装）

### STEP1: 2フランを1ドルに換算する処理（為替レートは、仮実装）
2フランを1ドルに換算する処理を実現していきます。
ただし、為替レート-> USD:CHF＝2:1を前提とします。

#### (1) まずは、小さいテストを書く。
テスト`testReduceMoneyDifferentCurrency`を追加します。

```python:tests/test_money.py
from testtools import TestCase
from example.money import Money, Sum
from example.bank import Bank

class MoneyTest(TestCase):
    def testMultiplication(self):
        five = Money.dollar(5)
        self.assertEqual(Money.dollar(10), five.times(2))
        self.assertEqual(Money.dollar(15), five.times(3))

    def testEquality(self):
        self.assertTrue(Money.dollar(5) == Money.dollar(5))
        self.assertFalse(Money.dollar(5) == Money.dollar(6))
        self.assertFalse(Money.franc(5) == Money.dollar(5))

    def testCurrency(self):
        self.assertEqual("USD", Money.dollar(1).currency())
        self.assertEqual("CHF", Money.franc(1).currency())

    def testSimpleAddition(self):
        five = Money.dollar(5)
        _sum = five.plus(five)
        bank = Bank()
        reduced = bank.reduce(_sum, "USD")
        self.assertEqual(Money.dollar(10), reduced)
    
    def testPlusReturnsSum(self):
        five = Money.dollar(5)
        _sum = five.plus(five)
        self.assertEqual(five, _sum.augend)
        self.assertEqual(five, _sum.addend)

    def testReduceSum(self):
        _sum = Sum(Money.dollar(3), Money.dollar(4))
        bank = Bank()
        result = bank.reduce(_sum, "USD")
        self.assertEqual(Money.dollar(7), result)

    def testReduceMoney(self):
        bank = Bank()
        result = bank.reduce(Money.dollar(1), "USD")
        self.assertEqual(Money.dollar(1), result)

    def testReduceMoneyDifferentCurrency(self):
        bank = Bank()
        bank.add_rate("CHF", "USD", 2)
        result = bank.reduce(Money.franc(2), "USD")
        self.assertEqual(Money.dollar(1), result)
```
#### (2) 小さい変更を行う。
次の変更を行います。

- `Bank`クラスに、`add_rate`メソッドする（仮実装）
- `Bank`クラスの`reduce`メソッドから、`Money`オブジェクトの`reduce`メソッドを呼び出す際に、自分の`Bank`オブジェクトを渡すようにする
- `Bank`クラスの`reduce`メソッドから、`Sum`オブジェクトの`reduce`メソッドを呼び出す際に、自分の`Bank`オブジェクトを渡すようにする
- `Bank`クラスの`rate`メソッドを定義し、暫定的に、為替レートを取得できるようにする（為替レート-> USD:CHF＝2:1）

```python:example/money.py
from example.expression import Expression

class Money(Expression):
    def __init__(self, amount, currency):  
        self.__amount = amount
        self.__currency = currency

    def __eq__(self, other):
        return (self.__amount == other.__amount
                and self.currency() == other.currency())

    def times(self, multiplier):
        return Money(self.__amount * multiplier, self.__currency)

    def plus(self, addend):
        return Sum(self, addend)

    def reduce(self, bank, toCurrency):
        rate = bank.rate(self.__currency, toCurrency)
        return Money(self.__amount / rate, toCurrency)

    def amount(self):
        return self.__amount

    def currency(self):
        return self.__currency

    @classmethod
    def dollar(cls, amount):
        return Money(amount, "USD")

    @classmethod
    def franc(cls, amount):
        return Money(amount, "CHF")

class Sum(Expression):
    def __init__(self, augend, addend):
        self.augend = augend
        self.addend = addend

    def reduce(self, bank, toCurrency):
        amount = self.augend.amount() + self.addend.amount()
        return Money(amount, toCurrency)
```
```python:example/bank.py
class Bank():
    def reduce(self, source , toCurrency):
        return source.reduce(self, toCurrency)

    def add_rate(self, fromCurrency, toCurrency, rate):
        pass

    def rate(self, fromCurrency, toCurrency):
        return 2 if (fromCurrency == "CHF" and toCurrency == "USD") else 1
```
```python:example/expression.py
from abc import ABCMeta, abstractmethod

class Expression(metaclass=ABCMeta):
    @abstractmethod
    def reduce(self, bank, toCurrency):
        pass
```

#### (3) テストを実行し、すべて成功することを確認する。-> <font color="green">OK</font>
テストは成功しています。

```
$ pytest -v

...(snip)
tests/test_money.py::MoneyTest::testCurrency PASSED                      [ 12%]
tests/test_money.py::MoneyTest::testEquality PASSED                      [ 25%]
tests/test_money.py::MoneyTest::testMultiplication PASSED                [ 37%]
tests/test_money.py::MoneyTest::testPlusReturnsSum PASSED                [ 50%]
tests/test_money.py::MoneyTest::testReduceMoney PASSED                   [ 62%]
tests/test_money.py::MoneyTest::testReduceMoneyDifferentCurrency PASSED  [ 75%]
tests/test_money.py::MoneyTest::testReduceSum PASSED                     [ 87%]
tests/test_money.py::MoneyTest::testSimpleAddition PASSED                [100%]
```

### STEP2: 2フランを1ドルに換算する処理（為替レート表の実装）
為替レート表に基づき、2フランを1ドルに換算する処理を実現していきます。

#### (1) まずは、小さいテストを書く。
テスト`testIdentityRate`を追加します。

```python:tests/test_money.py
from testtools import TestCase
from example.money import Money, Sum
from example.bank import Bank

class MoneyTest(TestCase):
    def testMultiplication(self):
        five = Money.dollar(5)
        self.assertEqual(Money.dollar(10), five.times(2))
        self.assertEqual(Money.dollar(15), five.times(3))

    def testEquality(self):
        self.assertTrue(Money.dollar(5) == Money.dollar(5))
        self.assertFalse(Money.dollar(5) == Money.dollar(6))
        self.assertFalse(Money.franc(5) == Money.dollar(5))

    def testCurrency(self):
        self.assertEqual("USD", Money.dollar(1).currency())
        self.assertEqual("CHF", Money.franc(1).currency())

    def testSimpleAddition(self):
        five = Money.dollar(5)
        _sum = five.plus(five)
        bank = Bank()
        reduced = bank.reduce(_sum, "USD")
        self.assertEqual(Money.dollar(10), reduced)
    
    def testPlusReturnsSum(self):
        five = Money.dollar(5)
        _sum = five.plus(five)
        self.assertEqual(five, _sum.augend)
        self.assertEqual(five, _sum.addend)

    def testReduceSum(self):
        _sum = Sum(Money.dollar(3), Money.dollar(4))
        bank = Bank()
        result = bank.reduce(_sum, "USD")
        self.assertEqual(Money.dollar(7), result)

    def testReduceMoney(self):
        bank = Bank()
        result = bank.reduce(Money.dollar(1), "USD")
        self.assertEqual(Money.dollar(1), result)

    def testReduceMoneyDifferentCurrency(self):
        bank = Bank()
        bank.add_rate("CHF", "USD", 2)
        result = bank.reduce(Money.franc(2), "USD")
        self.assertEqual(Money.dollar(1), result)

    def testIdentityRate(self):
        self.assertEqual(1, Bank().rate("USD", "USD"))
```
#### (2) 小さい変更を行う。
次の変更を行います。

- `Bank`クラスにて、為替レート表を保持できるようにする
- `Bank`オブジェクトの`add_rate`メソッドにより、為替レート表に各種レートを追加できる
- `Bank`オブジェクトの`rate`メソッドにより、必要なレートを参照できる

```python:example/bank.py
class Bank():
    def __init__(self):
        self._rates = {}

    def reduce(self, source , toCurrency):
        return source.reduce(self, toCurrency)

    def add_rate(self, fromCurrency, toCurrency, rate):
        target_rate = "{0}:{1}".format(fromCurrency, toCurrency)
        self._rates[target_rate] = rate

    def rate(self, fromCurrency, toCurrency):
        target_rate = "{0}:{1}".format(fromCurrency, toCurrency)
        if fromCurrency == toCurrency:
            return 1
        return self._rates.get(target_rate)
```

#### (3) テストを実行し、すべて成功することを確認する。-> <font color="green">OK</font>
テストは成功しています。

```
$ pytest -v

...(snip)
tests/test_money.py::MoneyTest::testCurrency PASSED                      [ 11%]
tests/test_money.py::MoneyTest::testEquality PASSED                      [ 22%]
tests/test_money.py::MoneyTest::testIdentityRate PASSED                  [ 33%]
tests/test_money.py::MoneyTest::testMultiplication PASSED                [ 44%]
tests/test_money.py::MoneyTest::testPlusReturnsSum PASSED                [ 55%]
tests/test_money.py::MoneyTest::testReduceMoney PASSED                   [ 66%]
tests/test_money.py::MoneyTest::testReduceMoneyDifferentCurrency PASSED  [ 77%]
tests/test_money.py::MoneyTest::testReduceSum PASSED                     [ 88%]
tests/test_money.py::MoneyTest::testSimpleAddition PASSED                [100%]
```


## 第15章: テスト任せとコンパイラ任せ (Mixed Currencies)
これまで、`通貨の異なる2つの金額を足し、通貨間の為替レートに基づいて換算された金額を得る。`の要件を満足する仕組みの実現化を目指してきました。ここでは、`$5 + 10 CHF = $10(レートが 2:1 の場合)`のToDoに着手していきます。

### (1) まずは、小さいテストを書く。
テスト`testMixedAddition`を追加します。

```python:tests/test_money.py
from testtools import TestCase
from example.money import Money, Sum
from example.bank import Bank

class MoneyTest(TestCase):
    def testMultiplication(self):
        five = Money.dollar(5)
        self.assertEqual(Money.dollar(10), five.times(2))
        self.assertEqual(Money.dollar(15), five.times(3))

    def testEquality(self):
        self.assertTrue(Money.dollar(5) == Money.dollar(5))
        self.assertFalse(Money.dollar(5) == Money.dollar(6))
        self.assertFalse(Money.franc(5) == Money.dollar(5))

    def testCurrency(self):
        self.assertEqual("USD", Money.dollar(1).currency())
        self.assertEqual("CHF", Money.franc(1).currency())

    def testSimpleAddition(self):
        five = Money.dollar(5)
        _sum = five.plus(five)
        bank = Bank()
        reduced = bank.reduce(_sum, "USD")
        self.assertEqual(Money.dollar(10), reduced)
    
    def testPlusReturnsSum(self):
        five = Money.dollar(5)
        _sum = five.plus(five)
        self.assertEqual(five, _sum.augend)
        self.assertEqual(five, _sum.addend)

    def testReduceSum(self):
        _sum = Sum(Money.dollar(3), Money.dollar(4))
        bank = Bank()
        result = bank.reduce(_sum, "USD")
        self.assertEqual(Money.dollar(7), result)

    def testReduceMoney(self):
        bank = Bank()
        result = bank.reduce(Money.dollar(1), "USD")
        self.assertEqual(Money.dollar(1), result)

    def testReduceMoneyDifferentCurrency(self):
        bank = Bank()
        bank.add_rate("CHF", "USD", 2)
        result = bank.reduce(Money.franc(2), "USD")
        self.assertEqual(Money.dollar(1), result)

    def testIdentityRate(self):
        self.assertEqual(1, Bank().rate("USD", "USD"))

    def testMixedAddition(self):
        fiveBucks = Money.dollar(5)
        tenFrancs = Money.franc(10)
        bank = Bank()
        bank.add_rate("CHF", "USD", 2)
        result = bank.reduce(fiveBucks.plus(tenFrancs), "USD")
        self.assertEqual(Money.dollar(10), result)
```
### (2) 小さい変更を行う。
次の変更を行います。

- `Sum`オブジェクトの`reduce`メソッドでの`amount`導出方法を修正する
- `Sum`クラスに、`plus`メソッドを定義する
- 抽象クラス`Expression`に、抽象メソッド`plus`を定義して、`Money`クラスおよび、`Sum`クラスでの`plus`メソッド定義を強制させる

```python:example/money.py
from example.expression import Expression

class Money(Expression):
    def __init__(self, amount, currency):  
        self.__amount = amount
        self.__currency = currency

    def __eq__(self, other):
        return (self.__amount == other.__amount
                and self.currency() == other.currency())

    def times(self, multiplier):
        return Money(self.__amount * multiplier, self.__currency)

    def plus(self, addend):
        return Sum(self, addend)

    def reduce(self, bank, toCurrency):
        rate = bank.rate(self.__currency, toCurrency)
        return Money(self.__amount / rate, toCurrency)

    def amount(self):
        return self.__amount

    def currency(self):
        return self.__currency

    @classmethod
    def dollar(cls, amount):
        return Money(amount, "USD")

    @classmethod
    def franc(cls, amount):
        return Money(amount, "CHF")

class Sum(Expression):
    def __init__(self, augend, addend):
        self.augend = augend
        self.addend = addend

    def reduce(self, bank, toCurrency):
        amount = self.augend.reduce(bank, toCurrency).amount() + \
            self.addend.reduce(bank, toCurrency).amount()
        return Money(amount, toCurrency)

    def plus(self, addend):
        pass
```
```python:example/expression.py
from abc import ABCMeta, abstractmethod

class Expression(metaclass=ABCMeta):
    @abstractmethod
    def plus(self, addend):
        pass

    @abstractmethod
    def reduce(self, bank, toCurrency):
        pass
```

### (3) テストを実行し、すべて成功することを確認する。-> <font color="green">OK</font>
テストは成功しています。

```
$ pytest -v

...(snip)
tests/test_money.py::MoneyTest::testCurrency PASSED                      [ 10%]
tests/test_money.py::MoneyTest::testEquality PASSED                      [ 20%]
tests/test_money.py::MoneyTest::testIdentityRate PASSED                  [ 30%]
tests/test_money.py::MoneyTest::testMixedAddition PASSED                 [ 40%]
tests/test_money.py::MoneyTest::testMultiplication PASSED                [ 50%]
tests/test_money.py::MoneyTest::testPlusReturnsSum PASSED                [ 60%]
tests/test_money.py::MoneyTest::testReduceMoney PASSED                   [ 70%]
tests/test_money.py::MoneyTest::testReduceMoneyDifferentCurrency PASSED  [ 80%]
tests/test_money.py::MoneyTest::testReduceSum PASSED                     [ 90%]
tests/test_money.py::MoneyTest::testSimpleAddition PASSED                [100%]
```

## 第16章: 将来の読み手を考えたテスト (Abstraction, Finally)
いよいよ、`通貨の異なる2つの金額を足し、通貨間の為替レートに基づいて換算された金額を得る。`の要件を満足する仕組みの実現化を完成させます。

- STEP1: `Sum`クラスの`plus`メソッドを完成させる
- STEP2: `Sum`クラスの`times`メソッドを完成させる

### STEP1: `Sum`クラスの`plus`メソッドを完成させる
`Sum`クラスの`plus`メソッドを完成させます。

#### (1) まずは、小さいテストを書く。
テスト`testSumPlusMoney`を追加します。

```python:tests/test_money.py
from testtools import TestCase
from example.money import Money, Sum
from example.bank import Bank

class MoneyTest(TestCase):
    def testMultiplication(self):
        five = Money.dollar(5)
        self.assertEqual(Money.dollar(10), five.times(2))
        self.assertEqual(Money.dollar(15), five.times(3))

    def testEquality(self):
        self.assertTrue(Money.dollar(5) == Money.dollar(5))
        self.assertFalse(Money.dollar(5) == Money.dollar(6))
        self.assertFalse(Money.franc(5) == Money.dollar(5))

    def testCurrency(self):
        self.assertEqual("USD", Money.dollar(1).currency())
        self.assertEqual("CHF", Money.franc(1).currency())

    def testSimpleAddition(self):
        five = Money.dollar(5)
        _sum = five.plus(five)
        bank = Bank()
        reduced = bank.reduce(_sum, "USD")
        self.assertEqual(Money.dollar(10), reduced)
    
    def testPlusReturnsSum(self):
        five = Money.dollar(5)
        _sum = five.plus(five)
        self.assertEqual(five, _sum.augend)
        self.assertEqual(five, _sum.addend)

    def testReduceSum(self):
        _sum = Sum(Money.dollar(3), Money.dollar(4))
        bank = Bank()
        result = bank.reduce(_sum, "USD")
        self.assertEqual(Money.dollar(7), result)

    def testReduceMoney(self):
        bank = Bank()
        result = bank.reduce(Money.dollar(1), "USD")
        self.assertEqual(Money.dollar(1), result)

    def testReduceMoneyDifferentCurrency(self):
        bank = Bank()
        bank.add_rate("CHF", "USD", 2)
        result = bank.reduce(Money.franc(2), "USD")
        self.assertEqual(Money.dollar(1), result)

    def testIdentityRate(self):
        self.assertEqual(1, Bank().rate("USD", "USD"))

    def testMixedAddition(self):
        fiveBucks = Money.dollar(5)
        tenFrancs = Money.franc(10)
        bank = Bank()
        bank.add_rate("CHF", "USD", 2)
        result = bank.reduce(fiveBucks.plus(tenFrancs), "USD")
        self.assertEqual(Money.dollar(10), result)

    def testSumPlusMoney(self):
        fiveBucks = Money.dollar(5)
        tenFrancs = Money.franc(10)
        bank = Bank()
        bank.add_rate("CHF", "USD", 2)
        _sum = Sum(fiveBucks, tenFrancs).plus(fiveBucks)
        result = bank.reduce(_sum, "USD")
        self.assertEqual(Money.dollar(15), result)
```
#### (2) 小さい変更を行う。
次の変更を行います。

- `Sum`オブジェクトの、`plus`メソッドが呼ばれたとき、`Sum`オブジェクトを返却する
- 抽象クラス`Expression`に、抽象メソッド`plus`を定義して、`Money`クラスおよび、`Sum`クラスでの`plus`メソッド定義を強制させる

```python:example/money.py
from example.expression import Expression

class Money(Expression):
    def __init__(self, amount, currency):  
        self.amount = amount
        self._currency = currency

    def __eq__(self, other):
        return (self.amount == other.amount
                and self.currency() == other.currency())

    def times(self, multiplier):
        return Money(self.amount * multiplier, self._currency)

    def plus(self, addend):
        return Sum(self, addend)

    def reduce(self, bank, toCurrency):
        rate = bank.rate(self.currency(), toCurrency)
        return Money(self.amount / rate, toCurrency)

    def currency(self):
        return self._currency

    @classmethod
    def dollar(cls, amount):
        return Money(amount, "USD")

    @classmethod
    def franc(cls, amount):
        return Money(amount, "CHF")

class Sum(Expression):
    def __init__(self, augend, addend):
        self.augend = augend
        self.addend = addend

    def reduce(self, bank, toCurrency):
        amount = self.augend.reduce(bank, toCurrency).amount + \
            self.addend.reduce(bank, toCurrency).amount
        return Money(amount, toCurrency)

    def plus(self, addend):
        return Sum(self, addend)
```
```python:example/bank.py
class Bank():
    def __init__(self):
        self._rates = {}

    def reduce(self, source , toCurrency):
        return source.reduce(self, toCurrency)

    def add_rate(self, fromCurrency, toCurrency, rate):
        self._rates[(fromCurrency, toCurrency)] = rate

    def rate(self, fromCurrency, toCurrency):
        if fromCurrency == toCurrency:
            return 1
        return self._rates.get((fromCurrency, toCurrency))
```

### (3) テストを実行し、すべて成功することを確認する。-> <font color="green">OK</font>
テストは成功しています。

```
$ pytest -v

...(snip)
tests/test_money.py::MoneyTest::testCurrency PASSED                      [  9%]
tests/test_money.py::MoneyTest::testEquality PASSED                      [ 18%]
tests/test_money.py::MoneyTest::testIdentityRate PASSED                  [ 27%]
tests/test_money.py::MoneyTest::testMixedAddition PASSED                 [ 36%]
tests/test_money.py::MoneyTest::testMultiplication PASSED                [ 45%]
tests/test_money.py::MoneyTest::testPlusReturnsSum PASSED                [ 54%]
tests/test_money.py::MoneyTest::testReduceMoney PASSED                   [ 63%]
tests/test_money.py::MoneyTest::testReduceMoneyDifferentCurrency PASSED  [ 72%]
tests/test_money.py::MoneyTest::testReduceSum PASSED                     [ 81%]
tests/test_money.py::MoneyTest::testSimpleAddition PASSED                [ 90%]
tests/test_money.py::MoneyTest::testSumPlusMoney PASSED                  [100%]
```

### STEP2: `Sum`クラスの`times`メソッドを完成させる
`Sum`クラスの`times`メソッドを完成させます。

#### (1) まずは、小さいテストを書く。
テスト`testSumTimes`を追加します。

```python:tests/test_money.py
from testtools import TestCase
from example.money import Money, Sum
from example.bank import Bank

class MoneyTest(TestCase):
    def testMultiplication(self):
        five = Money.dollar(5)
        self.assertEqual(Money.dollar(10), five.times(2))
        self.assertEqual(Money.dollar(15), five.times(3))

    def testEquality(self):
        self.assertTrue(Money.dollar(5) == Money.dollar(5))
        self.assertFalse(Money.dollar(5) == Money.dollar(6))
        self.assertFalse(Money.franc(5) == Money.dollar(5))

    def testCurrency(self):
        self.assertEqual("USD", Money.dollar(1).currency())
        self.assertEqual("CHF", Money.franc(1).currency())

    def testSimpleAddition(self):
        five = Money.dollar(5)
        _sum = five.plus(five)
        bank = Bank()
        reduced = bank.reduce(_sum, "USD")
        self.assertEqual(Money.dollar(10), reduced)
    
    def testPlusReturnsSum(self):
        five = Money.dollar(5)
        _sum = five.plus(five)
        self.assertEqual(five, _sum.augend)
        self.assertEqual(five, _sum.addend)

    def testReduceSum(self):
        _sum = Sum(Money.dollar(3), Money.dollar(4))
        bank = Bank()
        result = bank.reduce(_sum, "USD")
        self.assertEqual(Money.dollar(7), result)

    def testReduceMoney(self):
        bank = Bank()
        result = bank.reduce(Money.dollar(1), "USD")
        self.assertEqual(Money.dollar(1), result)

    def testReduceMoneyDifferentCurrency(self):
        bank = Bank()
        bank.add_rate("CHF", "USD", 2)
        result = bank.reduce(Money.franc(2), "USD")
        self.assertEqual(Money.dollar(1), result)

    def testIdentityRate(self):
        self.assertEqual(1, Bank().rate("USD", "USD"))

    def testMixedAddition(self):
        fiveBucks = Money.dollar(5)
        tenFrancs = Money.franc(10)
        bank = Bank()
        bank.add_rate("CHF", "USD", 2)
        result = bank.reduce(fiveBucks.plus(tenFrancs), "USD")
        self.assertEqual(Money.dollar(10), result)

    def testSumPlusMoney(self):
        fiveBucks = Money.dollar(5)
        tenFrancs = Money.franc(10)
        bank = Bank()
        bank.add_rate("CHF", "USD", 2)
        _sum = Sum(fiveBucks, tenFrancs).plus(fiveBucks)
        result = bank.reduce(_sum, "USD")
        self.assertEqual(Money.dollar(15), result)

    def testSumTimes(self):
        fiveBucks = Money.dollar(5)
        tenFrancs = Money.franc(10)
        bank = Bank()
        bank.add_rate("CHF", "USD", 2)
        _sum = Sum(fiveBucks, tenFrancs).times(2)
        result = bank.reduce(_sum, "USD")
        self.assertEqual(Money.dollar(20), result)
```
#### (2) 小さい変更を行う。
次の変更を行います。

- `Sum`オブジェクトの、`times`メソッドが呼ばれたとき、`Sum`オブジェクトを返却する
- 抽象クラス`Expression`に、抽象メソッド`times`を定義して、`Money`クラスおよび、`Sum`クラスでの`times`メソッド定義を強制させる

```python:example/money.py
from example.expression import Expression

class Money(Expression):
    def __init__(self, amount, currency):  
        self.__amount = amount
        self.__currency = currency

    def __eq__(self, other):
        return (self.__amount == other.__amount
                and self.currency() == other.currency())

    def times(self, multiplier):
        return Money(self.__amount * multiplier, self.__currency)

    def plus(self, addend):
        return Sum(self, addend)

    def reduce(self, bank, toCurrency):
        rate = bank.rate(self.__currency, toCurrency)
        return Money(self.__amount / rate, toCurrency)

    def amount(self):
        return self.__amount

    def currency(self):
        return self.__currency

    @classmethod
    def dollar(cls, amount):
        return Money(amount, "USD")

    @classmethod
    def franc(cls, amount):
        return Money(amount, "CHF")

class Sum(Expression):
    def __init__(self, augend, addend):
        self.augend = augend
        self.addend = addend

    def reduce(self, bank, toCurrency):
        amount = self.augend.reduce(bank, toCurrency).amount() + \
            self.addend.reduce(bank, toCurrency).amount()
        return Money(amount, toCurrency)

    def plus(self, addend):
        return Sum(self, addend)

    def times(self, multiplier):
        return Sum(self.augend.times(multiplier), self.addend.times(multiplier))
```
```python:example/expression.py
from abc import ABCMeta, abstractmethod

class Expression(metaclass=ABCMeta):
    @abstractmethod
    def plus(self, addend):
        pass

    @abstractmethod
    def reduce(self, bank, toCurrency):
        pass

    @abstractmethod
    def times(self, multiplier):
        pass
```

### (3) テストを実行し、すべて成功することを確認する。-> <font color="green">OK</font>
テストは成功していますし、カバレッジも概ね良好です。

```
$ pytest -v --cov=example

...(snip)
tests/test_money.py::MoneyTest::testCurrency PASSED                      [  8%]
tests/test_money.py::MoneyTest::testEquality PASSED                      [ 16%]
tests/test_money.py::MoneyTest::testIdentityRate PASSED                  [ 25%]
tests/test_money.py::MoneyTest::testMixedAddition PASSED                 [ 33%]
tests/test_money.py::MoneyTest::testMultiplication PASSED                [ 41%]
tests/test_money.py::MoneyTest::testPlusReturnsSum PASSED                [ 50%]
tests/test_money.py::MoneyTest::testReduceMoney PASSED                   [ 58%]
tests/test_money.py::MoneyTest::testReduceMoneyDifferentCurrency PASSED  [ 66%]
tests/test_money.py::MoneyTest::testReduceSum PASSED                     [ 75%]
tests/test_money.py::MoneyTest::testSimpleAddition PASSED                [ 83%]
tests/test_money.py::MoneyTest::testSumPlusMoney PASSED                  [ 91%]
tests/test_money.py::MoneyTest::testSumTimes PASSED                      [100%]

...(snip)
---------- coverage: platform darwin, python 3.8.0-final-0 -----------
Name                    Stmts   Miss  Cover
-------------------------------------------
example/__init__.py         0      0   100%
example/bank.py            13      0   100%
example/expression.py      11      3    73%
example/money.py           35      0   100%
-------------------------------------------
TOTAL                      59      3    95%

...(snip)
```

以上で、**テスト駆動開発**を活用したPython版「多国通貨」開発を体験してみました。

# ■ 終わりに ...
書籍「テスト駆動開発」の第Ⅱ部「xUnit」でも、Pythonで、テスト駆動開発が体験できます。
実際に、体験してみて気が付いたのですが、**テスト駆動開発**は、私がイメージしていたテストとだいぶ乖離していて、完全に誤解していました。
書籍の第32章「TDDを身につける」からの引用ですが、まさに、これです！！
> 皮肉なことに、TDD はテスト技法ではない(Cunningham の公案)。 TDD は分析技法であり、設計技法であり、実際には開発のすべてのアク ティビティを構造化する技法なのだ。

あと、付録C「訳者解説:テスト駆動開発の現在」は、TDD/BDDに対する理解を助けるものであり、ものすごく勉強になりました。

# ■ 参考URL
- [「テスト駆動開発」をPythonで書き直してみた](https://pyhaya.hatenablog.com/entry/2018/11/02/214649)
- [『テスト駆動開発』のソースをPythonで書いてみる（第Ⅰ部終了）](https://hiroronn.hatenablog.jp/entry/20171203/1512286106)
- [「テスト駆動開発」を読んだ](https://blog.haramishio.xyz/entry/read-book-tdd)