Pythonで実装 TDD駆動開発15章

money.py

from abc import ABC, abstractmethod

# 通貨を扱う「式」を表す抽象基底クラス
class Expression(ABC):

@abstractmethod
def plus(self,addend:'Expression'):
pass
@abstractmethod
def reduce(self, bank: 'Bank', to: str) -> 'Money':
"""指定された通貨に金額を換算する抽象メソッド。"""
pass
@abstractmethod
def __add__(self, other: 'Expression') -> 'Expression':
"""'+' 演算子をオーバーロードするための抽象メソッド。"""
pass

# 金額と通貨を扱うクラス
class Money(Expression):
def __init__(self, amount: int, currency: str):
self.amount = amount
self._currency = currency

def __eq__(self, other: object) -> bool:
"""2つのMoneyオブジェクトが等しいか比較する。"""
if isinstance(other, Money):
# 金額と通貨が両方等しいか比較
return self.amount == other.amount and self.currency() == other.currency()
return False

def __repr__(self) -> str:
"""オブジェクトを開発者向けの文字列表現で返す。"""
return f"Money({self.amount}, '{self.currency()}')"

def __hash__(self) -> int:
"""辞書のキーとして使えるようハッシュ値を返す。"""
return hash((self.amount, self.currency()))#金額,通貨

def __add__(self, other: 'Expression') -> 'Expression':
"""'+' 演算子でSumオブジェクトを返す。"""
return Sum(self, other)

def times(self, multiplier: int) -> 'Money':
"""金額を指定された倍率で掛ける。"""
return Money(self.amount * multiplier, self.currency())

def currency(self) -> str:
"""通貨の種類を返す。"""
return self._currency

def reduce(self, bank: 'Bank', to: str) -> 'Money':
"""金額を指定された通貨に換算する。"""
rate = bank.rate(self.currency(), to)
# 金額を指定されたレートで換算し、新しいMoneyオブジェクトとして返す
return Money(int(self.amount / rate), to)

@classmethod
def dollar(cls, amount: int) -> 'Money':
"""ドル(USD)のインスタンスを生成する。"""
return cls(amount, "USD")

@classmethod
def franc(cls, amount: int) -> 'Money':
"""フラン(CHF)のインスタンスを生成する。"""
return cls(amount, "CHF")

# 2つの金額の和を表すクラス
class Sum(Expression):
def __init__(self, augend: Expression, addend: Expression):
self.augend = augend #足される数
self.addend = addend #足す数

def reduce(self, bank: 'Bank', to: str) -> 'Money':
"""和を換算し、合計金額を返す。"""
# augendとaddendをそれぞれreduceして、amountを足す
amount = self.augend.reduce(bank, to).amount + self.addend.reduce(bank, to).amount
return Money(amount, to)

def __add__(self, other: 'Expression') -> 'Expression':
"""'+' 演算子で新しいSumオブジェクトを返す。"""
return Sum(self, other)

def plus(self,addend:'Expression'):
return None

# 銀行クラス:通貨換算レートを管理する
class Bank:
def __init__(self):
"""通貨ペアと換算レートを保存する辞書を初期化する。"""
self.rates: dict['Pair', int] = {}

def add_rate(self, from_currency: str, to: str, rate: int) -> None:
"""換算レートを追加する。"""
self.rates[Pair(from_currency, to)] = rate

def rate(self, from_currency: str, to: str) -> int:
"""指定された通貨間のレートを取得する。"""
# 同一通貨の処理
if from_currency == to:
return 1
# 登録済みレートの取得
# 「1」は、デフォルト値
return self.rates.get(Pair(from_currency, to))

# 通貨ペアを表すクラス
class Pair:
def __init__(self, from_currency: str, to: str):
self.from_currency = from_currency
self.to = to

def __eq__(self, other: object) -> bool:
"""2つのPairオブジェクトが論理的に等しいか判定する。"""
if isinstance(other, Pair):
return self.from_currency == other.from_currency and self.to == other.to
return False

def __hash__(self) -> int:
"""辞書のキーとして使えるようハッシュ値を返す。"""
return hash((self.from_currency, self.to))

MoneyTest.py

import unittest
from money import Bank, Expression, Money, Sum

class MoneyTest(unittest.TestCase):

# フランを掛け算するテスト
def testMultiplication(self):
five = Money.franc(5)
self.assertEqual(Money.franc(10), five.times(2))
# フランに3を掛けた結果が15フランと等しいか(このテストは成功する)
self.assertEqual(Money.franc(15), five.times(3))


# 通貨が同じか、違うかを比較するテスト
def testEquality(self):
# 5ドルと6ドルは等しくない
self.assertEqual(Money.dollar(5), Money.dollar(5))
# 5フランと6フランは等しくない
self.assertNotEqual(Money.dollar(5), Money.dollar(6))
# 5フランと6ドルは等しくない
self.assertNotEqual(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):
#5ドルをfiveに格納
five = Money.dollar(5)
# 5ドル+5ドル=10ドル
#sum = Money.dollar(5).plus(five)
sum = five + five
#5ドルにplusメソッドで5ドル加算しsumに格納
bank = Bank()
#Expressionに為替レートを適用することによって得られる換算結果
reduced = bank.reduce(sum,"USD")
#Moneyクラスの10ドルとbankクラスのsum(five.plus(five))が同じ値かどうかチェック
self.assertEqual(Money.dollar(10),reduced)

def testPlusReturnsSum(self):
five = Money.dollar(5)
result: Expression = five + five
#five.plus(five)とSumクラスで手動で計算された結果が同じかどうかテスト
self.assertIsInstance(result,Sum)
self.assertEqual(five, result.augend)
self.assertEqual(five, result.addend)

# 加算された結果が同じかどうかテスト
def testReduceSum(self):
sum = Sum(Money.dollar(3),Money.dollar(4))
bank = Bank()
#7ドル,USD
self.result = bank.reduce(sum,"USD")
#Moneyクラスのインスタンスの値とresult(7ドル,USD)が同じか確認する
self.assertEqual(Money.dollar(7),self.result)

# 金額と貨幣の種類(通貨)の両方が一致しているかどうかを検証
def testReduceMoney(self):
bank = Bank()
self.result = bank.reduce(Money.dollar(1),"USD")
# Moneyクラスのインスタンスとreduceの結果が一致しているかどうか
self.assertEqual(Money.dollar(1),self.result)

def testReduceMoneyDifferentCurrency(self):
bank = Bank()
bank.addRate("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):
self.fiveBucks = Money.dollar(5)
self.tenFrancs = Money.franc(10)
self.bank = Bank()
self.bank.addRate("CHF","USD",2)
self.result = self.bank.reduce(self.fiveBucks + self.tenFrancs,"USD")
self.assertEqual(Money.dollar(10),self.result)

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

ありがとうございました。

タイトルとURLをコピーしました