为什么我在测试中得到RunTimeError,而且布尔值与预期相反?

时间:2017-09-30 19:12:03

标签: ruby rspec

我不明白为什么not_stormy.stormy?等于真实!!

你可能会说我对Ruby和Rspec几乎都是全新的。任何帮助都会受到赞赏,甚至可以通过提示指向正确的方向?

这是我对Airport.rb的Rspec测试

require 'airport'

describe Airport do

  class FakePlane
    attr_accessor :landed

    def initialize
      @landed = false
    end

    def landed?
      @landed
    end
  end

  class FakeWeather
    def stormy?
      false
    end
  end

  let(:plane) { FakePlane.new }
  let(:not_stormy) { FakeWeather.new }

  describe '#initialize' do
    it 'should hold zero amount of planes' do
      expect(subject.planes).to be_empty
    end
  end

  describe '#land_plane' do
    it "should instruct a plane to land" do
      p not_stormy.stormy?
      subject.land_plane(plane, not_stormy)
      expect(subject.planes[0]).to eq plane
    end
    it "should raise an error if plane has already landed" do
      subject.land_plane(plane, not_stormy)
      expect { subject.land_plane(plane, not_stormy) }.to raise_error "Sorry plane has already landed!" if subject.planes.include? plane
    end
  end

  describe '#take_off' do
    it "should instruct a plane to take off" do
      subject.land_plane(plane, weather)
      expect(subject.take_off(plane)).to eq plane
    end
    it 'should raise an error if there are no planes' do
      expect { subject.take_off(plane) }.to raise_error "Sorry, no planes!" if subject.planes.empty?
    end
  end

  describe '#take_off' do
    it "should instruct a plane to take off" do
      subject.land_plane(plane, weather)
      expect(subject.take_off(plane)).to eq plane
    end
    it 'should raise an error if there are no planes' do
      expect { subject.take_off(plane) }.to raise_error "Sorry, no planes!" if subject.planes.empty?
    end
  end

  class FakeWeather
    def stormy?
      true
    end
  end

  let(:weather) { FakeWeather.new }
  describe '#land_plane' do
    it 'should not land a plane if the weather is stormy' do
      expect { subject.land_plane(plane, weather) }.to raise_error "Sorry, too stormy to land!" if weather.stormy?
    end
  end
end

这是来自airport.rb的代码

class Airport
  attr_reader :planes

  def initialize
    @planes = []
  end

  def land_plane(plane, weather, landed = true)
    @stormy = weather.stormy?
    fail "Sorry plane has already landed!" if @planes.include? plane
    fail "Sorry, too stormy to land!" if @stormy
    plane.landed = landed
    @planes << plane
  end

  def take_off(plane)
    fail "Sorry, no planes!" if @planes.empty?
    plane.landed = false
    @planes.delete(plane)
  end

end

在这里,最后是我的错误消息

Airport#take_off should instruct a plane to take off
     Failure/Error: fail "Sorry, too stormy to land!" if @stormy

     RuntimeError:
       Sorry, too stormy to land!
     # ./lib/airport.rb:14:in `land_plane'
     # ./spec/airport_spec.rb:56:in `block (3 levels) in <top (required)>'

2 个答案:

答案 0 :(得分:2)

一种方法是创建2个天气对象,并在每个测试中使用适当的天气对象,例如

let(:good_weather) do
  weather = double :weather
  allow(weather).to receive(:stormy?).and_return false
  weather
end

let(:bad_weather) do
  weather = double :weather
  allow(weather).to receive(:stormy?).and_return true
  weather
end

或者,将测试分解为“上下文”。这可能更有意义,因为当你想到它时,大多数测试都对测试其他东西感兴趣,并且只有一两个关于检查飞机的测试在暴风雨时无法降落:

describe Airport do

  let(:plane) { FakePlane.new }
  let(:weather) do
    weather = double :weather
    allow(weather).to receive(:stormy?).and_return false
    weather
  end  

  describe '#initialize' do
    it 'should hold zero amount of planes' do
      expect(subject.planes).to be_empty
    end
  end

  describe '#land_plane' do
    it "should instruct a plane to land" do
      subject.land_plane(plane, weather)
      expect(subject.planes[0]).to eq plane
    end
    # ...
  end

  # ... more tests here

  context "when stormy" do
    let(:weather) do
      weather = double :weather
      allow(weather).to receive(:stormy?).and_return true
      weather
    end

    describe '#land_plane' do
      it 'should not land a plane' do
        expect { subject.land_plane(plane, weather) }.to raise_error "Sorry, too stormy to land!"
      end
    end
  end
end

然后RSpec输出将很好地格式化:

Airport
  #initialize
    should hold zero amount of planes
  #land_plane
    should instruct a plane to land
    should raise an error if plane has already landed
  #take_off
    should instruct a plane to take off
    should raise an error if there are no planes
  #take_off
    should instruct a plane to take off
    should raise an error if there are no planes
  when stormy
    #land_plane
      should not land a plane

答案 1 :(得分:0)

mikej's answer很好,但它无法解释为什么你的所有规格都有stormy? == false。让我解释一下:

这是你的规范被剥离到相关部分并简化:

  class FakeWeather
    def stormy?
      false
    end
  end

  it { expect(FakeWeather.new.stormy?).to eq(false) } # fail

  class FakeWeather
    def stormy?
      true
    end
  end

  it { expect(FakeWeather.new.stormy?).to eq(true) } # success

你可能会发生的事情是:

  1. 你定义的不是暴风雨的FakeWeather类
  2. 规范expect(FakeWeather.new.stormy?).to eq(false)正在运行
  3. 你重新定义了暴风雨的FakeWeather课程
  4. 规范it { expect(FakeWeather.new.stormy?).to eq(true) }正在运行
  5. 但这并非正在发生的事情,这是真正发生的事情:

    1. 你定义的不是暴风雨的FakeWeather
    2. 你添加了一个要运行的示例(它还没有运行,这就是it/specify所做的 - 你传递一个块以便稍后运行)
    3. 你重新定义了暴风雨的FakeWeather
    4. 添加另一个要运行的示例
    5. 示例运行,但在内存中定义了风暴类。