Skip to main content
The Kai Way

TDD by Example书中的例子Ruby版

早上看了blackanger写的TDD by Ex这本书里的资金例子,自己也想写一写。和他不同,我是全过程详细写出来。第一次用Ruby写代码,第一次用Ruby的Unit框架,而且下午睡醒后迷迷糊糊写的,可能有很多错误,请多多指正。

第一次迭代后的代码(简单的TDD代码)

# file tc_doller.rb $:.unshift File.join(File.dirname(FILE), "..", "src") require 'test/unit' require 'dollar' class TestMoney < Test::Unit::TestCase def testMultiplication five = Dollar.new(5) five.times(2) assert_equal(10, five.amount) end end

# file doller.rb class Dollar def initialize(amount) @amount = amount end def times(multiplier) @amount = @amount * multiplier end def amount return @amount end end

第二次迭代后的代码

# file tc_doller.rb $:.unshift File.join(File.dirname(FILE), "..", "src") require 'test/unit' require 'dollar' class TestMoney < Test::Unit::TestCase def testMultiplication five = Dollar.new(5) product = five.times(2) assert_equal(10, product.amount) product = five.times(3) assert_equal(15, product.amount) end end

# file doller.rb class Dollar attr_reader :amount protected :amount def initialize(amount) @amount = amount end

def times(multiplier) return Dollar.new @amount * multiplier end end

第三,四次迭代后的代码(添加了相等性测试,刚好Ruby中的equal?和==的语意和Java相反)

# file tc_doller.rb $:.unshift File.join(File.dirname(FILE), "..", "src") require 'test/unit' require 'dollar'

class TestMoney < Test::Unit::TestCase def testMultiplication five = Dollar.new(5) product = five.times(2) assert_equal(10, product.amount) product = five.times(3) assert_equal(15, product.amount) end def testEquality assert(Dollar.new(5) == Dollar.new(5)) assert(Dollar.new(5) != Dollar.new(6)) end end

# file doller.rb class Dollar attr_reader :amount protected :amount def initialize(amount) @amount = amount end def times(multiplier) return Dollar.new @amount * multiplier end def ==(obj) return obj.amount == @amount end end

第五,六,七次迭代后的代码(短命的Franc对象登场)

# file tc_doller.rb $:.unshift File.join(File.dirname(FILE), "..", "src") require 'test/unit' require 'money' require 'dollar' require 'franc'

class TestMoney < Test::Unit::TestCase def testMultiplication five = Dollar.new(5) assert_equal(Dollar.new(10), five.times(2)) assert_equal(Dollar.new(15), five.times(3)) end

def testEquality assert(Dollar.new(5) == Dollar.new(5)) assert(Dollar.new(5) != Dollar.new(6)) assert(Franc.new(5) == Franc.new(5)) assert(Franc.new(5) != Franc.new(6)) assert(Franc.new(5) != Dollar.new(5)) end

def testFrancMultiplication five = Franc.new(5) assert_equal(Franc.new(10), five.times(2)) assert_equal(Franc.new(15), five.times(3)) end end

# file doller.rb class Dollar < Money def initialize(amount) super(amount) end def times(multiplier) return Dollar.new(@amount * multiplier) end end

# file franc.rb class Franc < Money def initialize(amount) super(amount) end def times(multiplier) return Franc.new(@amount * multiplier) end end

# file money.rb class Money attr_reader :amount protected :amount def initialize(amount) @amount = amount end def ==(obj) return obj.amount.equal?(@amount) end end

第八,九,十,十一次迭代(消除子类,很巧妙的一步)

# file tc_doller.rb $:.unshift File.join(File.dirname(FILE), "..", "src") require 'test/unit' require 'money'

class TestMoney < 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 testFrancMultiplication five = Money.franc(5) assert_equal(Money.franc(10), five.times(2)) assert_equal(Money.franc(15), five.times(3)) end

def testEquality assert(Money.dollar(5) == Money.dollar(5)) assert(Money.dollar(5) != Money.dollar(6)) assert(Money.franc(5) != Money.dollar(5)) end

def testCurrency assert_equal("USD", Money.dollar(1).currency) assert_equal("CHF", Money.franc(1).currency) end end

# file money.rb class Money attr_reader :amount, :currency protected :amount

def initialize(amount, currency) @amount = amount @currency = 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 Money.new(@amount + addend.amount, currency) end

def ==(obj) return obj.amount.equal?(@amount) && (obj.currency == @currency) end end

最后的一部分 最后是完成不同货币之间的计算,引入了两个新的对象负责处理汇率的Bank和货币相加的Sum对象。

由于Ruby的动态性无须让Sum和Money实现同一接口,反正原书中的Expression对象也是为了Sum和Money对象可以通信。或许可以把Sum简化掉,有空时再想想。

# file tc_doller.rb $:.unshift File.join(File.dirname(FILE), "..", "src") require 'test/unit' require 'money' require 'bank' require 'sum'

class TestMoney < Test::Unit::TestCase def testDollarMultiplication five = Money.dollar(5) assert_equal(Money.dollar(10), five.times(2)) assert_equal(Money.dollar(15), five.times(3)) end

def testFrancMultiplication five = Money.franc(5) assert_equal(Money.franc(10), five.times(2)) assert_equal(Money.franc(15), five.times(3)) end

def testEquality assert(Object.new != Money.dollar(1)) assert(Money.dollar(5) == Money.dollar(5)) assert(Money.dollar(5) != Money.dollar(6)) assert(Money.franc(5) != Money.dollar(5)) end

def testCurrency assert_equal("USD", Money.dollar(1).currency) assert_equal("CHF", Money.franc(1).currency) end

def testSimpleAddition five = Money.dollar(5) sum = five.plus(Money.dollar(5)) bank = Bank.new() reduced = bank.reduce(sum, "USD") assert_equal(Money.dollar(10), reduced) 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 testReduceMoneyDiffentCurrency bank = Bank.new bank.addRate("CHF", "USD", 2) result = bank.reduce(Money.franc(2), "USD") assert_equal(Money.dollar(1), result) end

def testIndentityRate bank = Bank.new bank.addRate("USD", "CHF", 0.5) assert_equal(1, bank.rate("USD", "USD")) assert_equal(0.5, bank.rate("USD", "CHF")) assert_equal(2, bank.rate("CHF", "USD")) end

def testMixedAddition 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 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 end

# file money.rb class Money attr_reader :amount, :currency

def initialize(amount, currency) @amount = amount @currency = 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 ==(obj) return obj.amount.equal?(@amount) && (obj.currency == @currency) end

def reduce(bank, to) rate = bank.rate(@currency, to) return Money.new(@amount / rate, to) end end

# file bank.rb class Bank attr_accessor :rates @@rates = {}

def reduce(source, to) return source.reduce(self, to) end

def addRate(from, to, rate) @@rates["#{from}-#{to}"] = rate @@rates["#{to}-#{from}"] = 1 / rate end

def rate(from, to) return 1 if(from == to) return @@rates["#{from}-#{to}"] end end

# file sum.rb class Sum attr_reader :augend, :addend

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 times(multiplier) return Sum.new(augend.times(multiplier), addend.times(multiplier)) end end