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,Ruby,休闲,Beck,Kent)