注册 | 登录 忘记密码? 51cto首页 | 博客 | 论坛 | 招聘
热点文章 [业内传闻]今天,7月25日..
 帮助

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


2007-10-02 19:41:59
 标签:TDD Ruby Beck Kent   [推送到技术圈]

版权声明:原创作品,允许转载,转载时请务必以超链接形式标明文章 原始出处 、作者信息和本声明。否则将追究法律责任。http://blackanger.blog.51cto.com/140924/44838
花了一天时间,边看这个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

本文出自 “{ :Alex Space => " Ruby Notes " }” 博客,请务必保留此出处http://blackanger.blog.51cto.com/140924/44838





    文章评论
 
 

发表评论

昵   称:
验证码:  点击图片可刷新验证码  博客过2级,无需填写验证码
内   容: