テスト駆動開発入門を Ruby で写経する 9章〜16章

昨日に引き続き、今日も写経。
一部、未完成なところはあるけど、16章まで写経しました。

やったこと

9章〜16章までの写経。

テストコード

require 'test/unit'
require 'money'

class TC_Money < Test::Unit::TestCase
	def testMultiplication
		five = Money.dollar(5)
		assert_equal(Money.dollar(10), five.times(2))
		assert_equal(Money.dollar(15), five.times(3))
	end
	def testEquality
		assert(Money.dollar(5).equals(Money.dollar(5)))
		assert(!Money.dollar(5).equals(Money.dollar(6)))
		assert(Money.franc(5).equals(Money.franc(5)))
	end
	def testSimpleAddition
		five = Money.dollar(5)
		result = five.plus(five)
		sum = result
		assert_equal(five, sum.augend)
		assert_equal(five, sum.addend)
	end
	def testReduceSum
		sum = Sum.new(Money.dollar(3), Money.dollar(4))
		bank = Bank.new
		result = bank.reduce(sum, "USD")
		assert_equal(Money.dollar(7), result)
	end
	def testReduceMoney
		bank = Bank.new
		result = bank.reduce(Money.dollar(1), "USD")
		assert_equal(Money.dollar(1), result)
	end
	def testReduceMoneyDifferentCurrency
		bank = Bank.new
		bank.addRate("CHF", "USD", 2)
		result = bank.reduce(Money.franc(2), "USD")
		assert_equal(Money.dollar(1), result)
	end
	def testIdentityRate
		assert_equal(1, Bank.new.rate("USD", "USD"))
	end
	def testMixedAdditionh
		fiveBucks = Money.dollar(5)
		tenFrancs = Money.franc(10)
		bank = Bank.new
		bank.addRate("CHF", "USD", 2)
		result = bank.reduce(fiveBucks.plus(tenFrancs), "USD")
		assert_equal(Money.dollar(10), result)
	end
	def testSumPlusMoney
		fiveBucks = Money.dollar(5)
		tenFrancs = Money.franc(10)
		bank = Bank.new
		bank.addRate("CHF", "USD", 2)
		sum = Sum.new(fiveBucks, tenFrancs).plus(fiveBucks)
		result = bank.reduce(sum, "USD")
		assert_equal(Money.dollar(15), result)
	end
	def testSumTimes
		fiveBucks = Money.dollar(5)
		tenFrancs = Money.franc(10)
		bank = Bank.new
		bank.addRate("CHF", "USD", 2)
		sum = Sum.new(fiveBucks, tenFrancs).times(2)
		result = bank.reduce(sum, "USD")
		assert_equal(Money.dollar(20), result)
	end
	def testPlusSameCurrencyReturnsMoney
		sum = Money.dollar(1).plus(Money.dollar(1))
		assert(sum.class == Money)
	end
end

プロダクトコード

class Money
	def initialize(amount, currency)
		@amount = amount
		@currency = currency
	end
	def equals(obj)
		return @amount == obj.amount && @currency == obj.currency
	end
	def ==(obj)
		@amount == obj.amount && @currency == obj.currency
	end
	def self.dollar(amount)
		return Money.new(amount, "USD")
	end
	def self.franc(amount)
		return Money.new(amount, "CHF")
	end
	def times(multiplier)
		return Money.new(@amount * multiplier, @currency)
	end
	def plus(addend)
		return Sum.new(self, addend)
	end
	def reduce(bank, to)
		rate = bank.rate(@currency, to)
		return Money.new(@amount / rate, to)
	end
	attr_accessor :amount
	attr_accessor :currency
end

class Bank
	def initialize
		@rates = Hash.new
	end
	def reduce(source, to)
		return source.reduce(self, to)
	end
	def rate(from, to)
		if from == to
			return 1
		end
		rate = @rates[Pair.new(from, to)]
		return rate
	end
	def addRate(from, to, rate)
		@rates[Pair.new(from, to)] = rate
	end
end

class Sum
	attr_accessor :augend
	attr_accessor :addend
	attr_accessor :amount
	def initialize(augend, addend)
		@augend = augend
		@addend = addend
	end
	def reduce(bank, to)
		@amount = @augend.reduce(bank, to).amount \
						+ @addend.reduce(bank, to).amount
		return Money.new(@amount, to)
	end
	def plus addend
		return Sum.new(self, addend)
	end
	def times multiplier
		return Sum.new(@augend.times(multiplier), @addend.times(multiplier))
	end
end

class Pair
	def initialize(from, to)
		@from = from
		@to = to
	end
	def eql?(obj)
		return @from == obj.from && @to == obj.to
	end
	def hash
		return 0
	end
	attr_accessor :from
	attr_accessor :to
end

はまったこと

  • ハッシュのキーに自作クラスを使う
  • 16 章の"$5 + $5 から Money の返却"の Ruby での実装方法がわからない。

うーん。最後の部分がすっきりしないなあ。