pyhaya’s diary

プログラミング、特にPythonについての記事を書きます。Djangoや機械学習などホットな話題をわかりやすく説明していきたいと思います。

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

書籍「テスト駆動開発」をPythonで書き直してみたシリーズの5です。前回は、DollarクラスとFrancクラスの親クラスとしてMoneyクラスを作り、重複したコードを親クラスへ引き上げました。
pyhaya.hatenablog.com

テスト駆動開発

テスト駆動開発

DollarクラスとFrancクラスを消す

テストでDollar、Francを使わないようにする

DollarクラスとFrancクラスを消すために、まずはテストコードからこれらのクラスを使っている部分をなくしていきます。

tests/test_money

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.times(2))
        self.assertEqual(Money.dollar(15), five.times(3))

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

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

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

Moneyクラスを経由してDollarクラスとFrancクラスを読み込みます。

Moneyクラスを書き直す

ではテストが通るようにMoneyクラスを書き換えます。だんだんインポートが面倒になってきたのでDollarクラスとFrancクラスをmoney.pyに移してきます。

src/money.py

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

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

    @staticmethod
    def dollar(amount):
        return Dollar(amount)

    @staticmethod
    def franc(amount):
        return Franc(amount)


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

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


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

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

Moneyクラスに吸収する

ここまでくれば、FrancクラスとDollarクラスの削除まではもう一歩です。これらのクラスの違いはcurrencyフィールドの違いだけなので、次のようにしてやれば統合できそうです。

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

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

    @staticmethod
    def dollar(amount):
        return Money(amount, 'USD')

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

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

これで、DollarとFrancクラスの統合は完了です。最後にテストを走らせて成功するのを確認します。