pyhaya’s diary

機械学習系の記事をメインで書きます

「テスト駆動開発」をPythonで書き直してみた 7

書籍「テスト駆動開発」をPythonで書き直したシリーズです。前回の記事はこちらです。
pyhaya.hatenablog.com

今回は、いよいよ多国通貨を扱うための準備に取り掛かります。

テスト駆動開発

テスト駆動開発

Bank(銀行)

多国通貨を扱うためには、為替レートを使って通貨を換算する必要があります。その役割を銀行クラスを作って任せます。どのような動作を期待するのか、テストを書いて明示します。

tests/test_money.py

import sys
sys.path.append('../src')

import unittest
from money import Money

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

    def testEquality(self):
        self.assertNotEqual(Money.franc(5), Money.dollar(5))

    def testSimpleAddition(self):
        bank = Bank()    #銀行を用意
        sum_ = Money.dollar(5) + Money.dollar(5)
        reduced = bank.reduce(sum_, "USD")    #USDに換算する
        self.assertEqual(reduced, Money.dollar(10))

if __name__ == '__main__':
    unittest.main()

銀行を表すオブジェクトを用意しておいて、reduceメソッドで通貨の両替を行います。

明白な実装

このテストを通す明白な実装はこのようになります。

src/bank.py

from money import Money

class Bank:
    def reduce(self, source, to):
        return Money.dollar(10)

Moneyの実装を再考する

ここまで書いたら、一度Moneyの実装を考えてみます。今回、両替を銀行に委譲しました。なので、通貨同士の足し算は足した時点では通貨は決定していない状態です。ただ、足される2つの通貨を持っているだけの中間状態のようなものが必要です。いわば抽象的な通貨を表すためのクラスが必要です。

Sumクラスを実装する

このようなクラスとして、Sumクラスを作ってみます。

src/sum.py

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

src/money.py

from sum import Sum

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

    def __eq__(self, other):
        return self.__dict__ == other.__dict__

    def __add__(self, other):
        return Sum(self, other)

    def __mul__(self, multiplier):
        return Money(self.amount * multiplier, self.currency)
    
    @staticmethod
    def dollar(amount):
        return Money(amount, 'USD')

    @staticmethod
    def franc(amount):
        return Money(amount, 'CHF')

__add__メソッドはSumクラスのインスタンスを返します。Sumクラスは初期化の際に被加算数(addend)と加算数(augent)を持ちます。

Bankクラスの本実装

ここまでくれば、ロジックが固まってきたのでBankクラスのreduceメソッドを書き下せます。source引数はSumクラスのインスタンスを引数に持つので、

src/bank.py

from money import Money

class Bank:
    def reduce(self, source, to):
        amount = source.augent.amount + source.addend.amount
        return Money(amount, to)

まとめ

今回は多国通貨を扱うための下準備を行いました。ここまでくるとだいぶロジックが複雑になってくるのでテストを書いてどのようなロジックにしたいのか明確にしておくことが重要です。