Kent Beck 的《測試驅動開發》(TDD) Money示例Ruby版
花了一天時間,邊看這個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
相關文章
- 測試驅動開發(TDD)的思考
- TDD(測試驅動開發)死了嗎?
- 測試驅動開發(TDD)例項演示
- 變異測試是測試驅動開發(TDD)的演變
- 測試驅動開發(TDD)實戰心得 - DeniMoka
- 程式設計師測試原則 - Kent Beck程式設計師
- 拒絕測試驅動開發(TDD)的10個理由
- 是否使用TDD(測試驅動開發)進行UI開發UI
- 測試驅動開發(TDD)跟敏捷開發有衝突敏捷
- 測試驅動開發(TDD)總結——原理篇
- 測試驅動開發(TDD)—— 資料庫查詢篇資料庫
- TDD---測試驅動開發,簡單的一點認識
- 產品開發鐵人三項 - Kent Beck
- 使用 TDD 測試驅動開發來構建 Laravel REST APILaravelRESTAPI
- 測試驅動開發TDD | IDCF FDCC認證學員作品
- Scrum敏捷軟體開發之技術實踐——測試驅動開發TDDScrum敏捷
- Kent Beck談敏捷開發的應用和價值觀敏捷
- "測試"驅動開發
- 使用IdleTest進行TDD單元測試驅動開發演練(2)
- 使用IdleTest進行TDD單元測試驅動開發演練(1)
- 敏捷聯盟Gordon Pask獎獲得者講“測試驅動開發”(TDD)敏捷Go
- Swift 進階開發指南:如何使用 Quick、Nimble 執行測試驅動開發(TDD)SwiftUI
- 談“測試驅動的開發”
- 【敏捷開發】驅動測試開發敏捷
- Go 語言:透過TDD測試驅動開發學習 Mocking (模擬)的思想GoMock
- 小議測試驅動開發
- 基於測試驅動的iOS開發iOS
- 面向 C++ 的測試驅動開發C++
- Kent Beck的3X模型是什麼?模型
- Laravel 測試驅動開發 -- 正向單元測試Laravel
- 你這不是測試驅動開發
- 什麼是測試驅動開發
- 放棄測試優先式開發(TDD)
- Kent Beck的test && commit || revert 敏捷協作方法MIT敏捷
- 使用IdleTest進行TDD單元測試驅動開發演練(3) 之 ASP.NET MVCASP.NETMVC
- 測試驅動開發到底好不好
- 測試驅動開發在專案中的實踐
- 基於Python的測試驅動開發實戰Python