Kent Beck 的《測試驅動開發》(TDD) Money示例Ruby版

weixin_34377065發表於2007-10-02

花了一天時間,邊看這個Money例子,邊把這個java寫的Money示例改成了Ruby,只是程式碼上加了註釋,可以按書上的介紹來看。

總結一下:TDD方面,感覺確實是很好的開發方法。這種開發方法應該一直貫穿下去。
Ruby方面,途中對Ruby的多型(duck type)有了更深的瞭解。但是也碰到一些問題,沒有列出來。解決了再說。

測試程式碼:
test/test_dollar.rb


CODE:
$: .unshift File.join(File.dirname(__FILE__),"..","lib")
require'test/unit'
require'dollar'
class TestDollar < Test::Unit::TestCase
#測試數值物件的相等性。這裡需要在dollar.rb裡過載==方法。
#因為比較值大小,ruby和java不同,ruby過載的是==方法,而不是equals.
#再新增一行測試,應用三角法。
def testEquality
    assert(Dollar.new(5) == (Dollar.new(5)))
    assert_equal(false,Dollar.new(5) == (Dollar.new(6)))
end
#第六步測試要重寫testEquality方法
#第七步測試讓美元物件和法郎物件進行比較,失敗!修改==方法
#修改完,搞定第七步測試
def testEquality
    assert(Dollar.new(5) == (Dollar.new(5)))
    assert_equal(false,Dollar.new(5) == (Dollar.new(6)))
    assert(Franc.new(5) == (Franc.new(5)))
    assert_equal(false,Franc.new(5) == (Franc.new(6)))
    assert_equal(false,Franc.new(5) == (Dollar.new(5)))
end
#測試八,修改testEquality方法
def testEquality
    assert(Money.dollar(5) == (Money.dollar(5)))
    assert_equal(false,Money.dollar(5) == (Money.dollar(6)))
    assert(Money.franc(5) == (Money.franc(5)))
    assert_equal(false,Money.franc(5) == (Money.franc(6)))
    assert_equal(false,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(five)
    bank = Bank.new
    reduced = bank.reduce(sum,"USD")
    assert_equal(Money.dollar(10),reduced)
end
#測試十二
def testPlusRetrunSum
    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
#測試十三,當Money為引數時
def testReduceMoney
    bank = Bank.new
    result = bank.reduce(Money.dollar(1),"USD")
    assert_equal(Money.dollar(1),result)
end
#自己加的
def testHashEquals
    hash = Hash["from,to" => 1]
    assert_equal(1,hash["from,to"])
end
#測試十四,擁抱變化,帶換算的Reduce Money
def testReduceMoneyDifferentCurrency
    bank = Bank.new
    bank.addRate("CHF","USD",2)
    result = bank.reduce(Money.franc(2),"USD")
    assert_equal(Money.dollar(1),result)
end
#每個Bank物件都不同。。。
#加了第二個測試用例
def testIdentityRate
    bank = Bank.new
    bank.addRate("CHF","USD",2)
    assert_equal(1,Bank.new.rate("USD","USD"))
    assert_equal(2,Bank.new.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
=begin
#測試十一,對子類有引用的testcase可取消
#測試乘法
def testMultiplication
    five = Dollar.new(5)
    five.times(2)
    assert_equal(10,five.amount)
end
#測試Dollar類的副作用,這裡重寫了第一個斷言。
#當新增另一個測試five.times(3)的時候,失敗了。
#這是因為,第一次測試的時候,已經把amount的直由5變成了10
#需要新增另一個物件
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
=begin
#測試十一,對子類有引用的testcase可取消
#重寫第二個斷言,讓Dollar物件之間進行比較
#Ruby中的例項變數預設是私有的,所以私有性測試就不做了,第四步測試完
def testMultiplication
    five = Dollar.new(5)
    assert_equal(Dollar.new(10),five.times(2))
    assert_equal(Dollar.new(15),five.times(3))
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
=begin
#測試八,修改testMultiplication方法和testFrancMultiplication方法。
#測試十一,對子類有引用的testcase可取消
def testMultiplication
    five = Money.dollar(5)
    assert_equal(Dollar.new(10,"USD"),five.times(2))
    assert_equal(Dollar.new(15,"USD"),five.times(3))
end
def testFrancMultiplication
    five = Money.franc(5)
    assert_equal(Franc.new(10,"CHF"),five.times(2))
    assert_equal(Franc.new(15,"CHF"),five.times(3))
end
=end
=begin
#測試十
#測試十一,對子類有引用的testcase可取消
def testDifferentClassEquality
    assert(Money.new(10,"CHF")==(Franc.new(10,"CHF")))
end
=end
end
lib/dollar.rb
CODE:
#為了避免這種複製貼上程式碼的惡性迴圈,我們用繼承來解決這個問題。
#測試十一,消除了子類。
class Money
#測試八,為了消除重複的times方法,增加兩個Money的類方法。(工廠模式)
#測試十一,將對子類的引用修改為對父類的引用,即,把原來的Dollar.new,Franc.new修改為Money.new
#這樣我們就可以刪除掉子類了
def self.dollar(amount)
@amount = amount
    return Money.new(@amount,"USD")
end
def self.franc(amount)
@amount = amount
    return Money.new(@amount,"CHF")
end
  attr_reader :amount,:currency
def initialize(amount=nil,currency=nil)
@amount = amount
@currency = currency  
end
#測試九
def currency
    return @currency
end
#第十步測試完畢 
def times(multiplier)
    return Money.new(@amount * multiplier,currency)
end
#第12,加法
def plus(addend)
    return Sum.new(self,addend)
end
#為了消除類判定,Money中引進reduce方法
def reduce(bank,to)
@rate = bank.rate(currency,to)
    return Money.new(@amount/@rate,to)
end
#第七步測試,要判斷兩個money物件是否相等,當且僅當它們的值和類均相同才行。
#即,蘋果不能和桔子比較
#到測試十的時候,為了消除子類,我們引進了貨幣,這個時候比較的就不是類了,應該是貨幣。我們修改
def ==(obj)
    money = obj
    return @amount.eql?(money.amount) && (self.currency).eql?(money.currency)
end
end
#在第十二步和十三步測試的時候,我們引入了Bank類和Sum類。
#Sum類的物件作為計算兩個Money物件值的"錢包"物件
#測試十三到此結束,對duck type有了更深的理解
class Bank
  attr_reader :rates,:rate
#用一個Hash物件來儲存  匯率   
  @@rates = Hash.new
def addRate(from,to,rate)
    @@rates["#{from},#{to}"]=rate
end
def reduce(source,to)
#    return source if source.class == Money
    return source.reduce(self,to)
end
#呼叫rate方法是查詢匯率
def rate(from,to)
#    return from.eql?("CHF") && to.eql?("USD") ? 2 : 1
     return 1 if from.eql?(to)
@rate = @@rates["#{from},#{to}"]
     return @rate
end
end
#第十五步測試
#Ruby到這一步就結束了。因為沒有那個Expression介面
class Sum
  attr_reader :augend,:addend,:amount
def reduce(bank,to)
@amount = augend.reduce(bank,to).amount + addend.reduce(bank,to).amount
    return Money.new(amount,to)
end
def initialize(augend,addend)
@augend = augend
@addend = addend
end
end
#測試十一,消除子類
#讓Dollar繼承自Money
#class Dollar < Money
=begin
#第六步測試要把這個新增到了Money中
#改造建構函式,由initialize(amount)改成下面形式
#這個是我自己加的,可以通過無引數的建構函式來建立物件
#測試九,增加貨幣
#上移建構函式到Money,這裡用super就行
def initialize(amount=nil,currency=nil)
    super(amount,currency)
end
=end
=begin
#消除重複設計,測試程式碼裡有5和2,現在把5和2用變數代替
#既@amount = 5 * 2 替換成              @amount = @amount * multiplier
#進一步重構,把@amount = @amount * multiplier 替換成 @amount *= multiplier
#這樣,我們的第一個測試,乘法測試到此完成。
def times(multiplier)
@amount *= multiplier
end
=end
=begin
#為了消除Dollar的副作用,返回一個新的物件
#到此為止完成了第二個測試
#測試九修改了times方法,使用了工廠方法return Money.dollar(@amount * multiplier)
#測試十為了消除子類的times方法,以退為進。和Franc類一樣的修改,這樣就可以把times方法上移到Money類中。
def times(multiplier)
    return Money.new(@amount * multiplier,currency)
end
=end
=begin
#測試相等性的時候,先直接return true。通過,然後再新增測試。
#通過三角法,一般化了==方法的程式碼。第三個測試完畢。
#第六步測試要把判等方法上移到Money中
def ==(obj)
    dollar = obj
@amount.eql?(dollar.amount)
end
=end
#end
#法郎在哭泣?通過醜陋的copy程式碼的方法來止住法郎的哭泣
#class Franc < Money
=begin
   attr_reader :amount
def initialize(amount=nil,currency=nil)
     super(amount,currency)
end
=end
=begin 
#測試十,修改Franc為Money,試驗測試能否工作,不行,再恢復原貌
#在修改了==方法以後,我們又可以用Money
def times(multiplier)
    return Money.new(@amount * multiplier,currency)
end
=end
#end

相關文章