为了账号安全,请及时绑定邮箱和手机立即绑定

Kent Beck 的《测试驱动开发》(TDD) Money示例Ruby版

标签:
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

©著作权归作者所有:来自51CTO博客作者blackanger的原创作品,如需转载,请注明出处,否则将追究法律责任

Ruby休闲TDD


点击查看更多内容
TA 点赞

若觉得本文不错,就分享一下吧!

评论

作者其他优质文章

正在加载中
  • 推荐
  • 评论
  • 收藏
  • 共同学习,写下你的评论
感谢您的支持,我会继续努力的~
扫码打赏,你说多少就多少
赞赏金额会直接到老师账户
支付方式
打开微信扫一扫,即可进行扫码打赏哦
今天注册有机会得

100积分直接送

付费专栏免费学

大额优惠券免费领

立即参与 放弃机会
意见反馈 帮助中心 APP下载
官方微信

举报

0/150
提交
取消