在前面的章节,我们用cucumber从应用的层面,从外部描述codebreaker游戏的行为。我们定义了步骤,但是留下一个异常,我们希望game返回消息,但是返回的却是一个空数组。
在本章,我们将使用RSpec来描述更细粒度的行为,game对象实例的预期行为。
创建spec/codebreaker目录
- mkdir -p spec/codebreaker
创建game_spec.rb文件
- vi spec/codebreaker/game_spec.rb
输入下面的内容
- require 'spec_helper'
- module Codebreaker
- describe Game do
- describe "#start" do
- it "sends a welcome message"
- it "prompts for the first guess"
- end
- end
- end
创建spec_helper.rb文件
- vi spec/spec_helper.rb
输入下面的内容
- require 'codebreaker'
执行命令
- rspec spec/codebreaker/game_spec.rb --format doc
修改spec/codebreaker/game_spec.rb的内容
- require 'spec_helper'
- module Codebreaker
- describe Game do
- describe "#start" do
- it "sends a welcome message" do
- output=double('output')
- game=Game.new(output)
- output.should_receive(:puts).with('Welcome to Codebreaker!')
- game.start
- end
- it "prompts for the first guess"
- end
- end
- end
执行命令
- rspec spec/codebreaker/game_spec.rb --color
我们看到了红色的错误。有的时候是逻辑错误,有的时候是其他错误。不管什么错误,我们要把他们变成绿色的。
编辑文件
- vi spec/codebreaker/game_spec.rb
更新内容
- require 'spec_helper'
- module Codebreaker
- describe Game do
- describe "#start" do
- it "sends a welcome message" do
- output=double('output')
- game=Game.new(output)
- output.should_receive(:puts).with('Welcome to Codebreaker!')
- game.start
- end
- it "prompts for the first guess"
- end
- end
- end
执行命令
- rspec spec/codebreaker/game_spec.rb --color
这下变成绿色了。
执行命令
- cucumber features/codebreaker_start_game.feature
Then I should see "Welcome to Codebreaker!"也变成了绿色。
接下来我们继续编辑spec/codebreaker/game_spec.rb文件。
- vi sepc/codebreaker/game_spec.rb
替换第二个it中的内容。
- it "prompts for the first guess" do
- output=double("output")
- game=Game.new(output)
- output.should_receive(:puts).with("Enter guess:")
- game.start
- end
然后再次执行
- rspec spec/codebreaker/game_spec.rb
还是有错误
- .F
- Failures:
- 1) Codebreaker::Game#start prompts for the first guess
- Failure/Error: game.start
- Double "output" received :puts with unexpected arguments
- expected: ("Enter guess:")
- got: ("Welcome to Codebreaker!")
- # ./lib/codebreaker/game.rb:8:in `start'
- # ./spec/codebreaker/game_spec.rb:18:in `block (3 levels) in <module:Codebreaker>'
- Finished in 0.00178 seconds
- 2 examples, 1 failure
再次修改lib/codebreaker/game.rb
- vi lib/codebreaker/game.rb
添加
- module Codebreaker
- class Game
- def initialize(output)
- @outputoutput=output
- end
- def start
- @output.puts "Welcome to Codebreaker!"
- @output.puts "Enter guess:"
- end
- end
- end
执行
- rspec spec/codebreaker/game_spec.rb
这下麻烦了,问题更大了,不仅第二个测试没有通过,就连刚才通过的第一个也出现了问题。
两次测试都没有接收到应该接收到信息,导致测试都失败了。
解决这个问题,有一个方法,就是as_null_object。
我们来修改一下game_spec.rb
- vi spec/codebreaker/game_spec.rb
内容如下
- require 'spec_helper'
- module Codebreaker
- describe Game do
- describe "#start" do
- it "sends a welcome message" do
- output=double('output').as_null_object
- game=Game.new(output)
- output.should_receive(:puts).with('Welcome to Codebreaker!')
- game.start
- end
- it "prompts for the first guess" do
- output=double("output").as_null_object
- game=Game.new(output)
- output.should_receive(:puts).with("Enter guess:")
- game.start
- end
- end
- end
- end
- ~
执行命令
- rspec spec/codebreaker/game_spec.rb --color
这回好了,又出现了我们的绿色,测试都通过了。
是重构的时候了。
Martin Flower在重构一书中写到。
重构,就是在不改变代码外在行为的情况下,改进代码的内部结构。
如何来确保外在行为没有被改变呢?在每次改变之后,我们都会进行检查。如果测试通过,那就说我们成功了。如果没有通过,就要找出问题,甚至回滚到前一个阶段,来保证外在行为还保持在绿色状态。
重构有一个作用,就是消除重复,提高重用。
在game_spec.rb中有两行代码
- output=double("output").as_null_object
- game=Game.new(output)
在两次测试都出现了。我们来修改一下game_spec.rb,引入before(:each)。
- require 'spec_helper'
- module Codebreaker
- describe Game do
- describe "#start" do
- before(:each) do
- @output=double('output').as_null_object
- @game=Game.new(@output)
- end
- it "sends a welcome message" do
- @output.should_receive(:puts).with('Welcome to Codebreaker!')
- @game.start
- end
- it "prompts for the first guess" do
- @output.should_receive(:puts).with("Enter guess:")
- @game.start
- end
- end
- end
- end
before(:each)中的代码在每个测试运行之前都会执行。
如果只是创建变量,给变量赋值。在RSpec中我们可以使用let(:method){}来替代。
- require 'spec_helper'
- module Codebreaker
- describe Game do
- describe "#start" do
- let(:output) { double("output").as_null_object }
- let(:game) { Game.new(output) }
- it "sends a welcome message" do
- output.should_receive(:puts).with('Welcome to Codebreaker!')
- game.start
- end
- it "prompts for the first guess" do
- output.should_receive(:puts).with("Enter guess:")
- game.start
- end
- end
- end
- end
- ~
重构完成了。跑一下测试,也是绿色的,没有问题,重构成功了。
让我们来做一个可执行的game。
- mkdir bin
- vi bin/codebreaker
在文件中输入
- #!/usr/bin/evn ruby
- $LOAD_PATH.unshift File.expand_path('../../lib',__FILE__)
- require 'codebreaker'
- game=Codebreaker::Game.new(STDOUT)
- game.start
让我们执行以下
- ruby bin/codebreaker
看到了我们想要的内容。
- Welcome to Codebreaker!
- Enter guess:
总结
本章我们从一个cucumber的逻辑错误开始。从外部的cucumber周期,转向内部的rspec周期。
本章我们使用RSpec来完成了一个red/green/refactor的周期。
这就是BDD的周期。从外到里的驱动开发,从使用cucumber描述的业务scenario,到使用rspec描述的内部对象。
教程参考:The RSpec Book
所有的代码放在
https://github.com/woaigithub/the-rspec-book-with-cucumber