使用 rspec 时用 irb 加载程序

loading program with irb when using rspec

我正在用 ruby 创建一个简单的二十一点程序。我这样做是为了提高我的 TDD 技能,因此我使用 Rspec 进行测试覆盖。为了使工作更清晰,我的文件结构是一个名为 blackjack 的主文件夹。其中有 lib 和 spec 文件夹。到目前为止,在 lib 中有一个 deck.rb 和 card.rb 文件。然后我为每个都有回顾性的规范文件夹。当我尝试在 irb 中加载程序时,我 运行 遇到了这样的问题。如果我想测试在两个文件夹之间拆分的方法,我会有点困惑,我应该怎么做。单独加载卡片组和卡片文件时,出现以下错误。

2.1.1 :001 > require "./deck.rb"
 => true 
2.1.1 :002 > cards = Deck.build_cards
NameError: uninitialized constant Deck::Card
    from /Users/em/ruby/blackjack/lib/deck.rb:7:in `block (2 levels) in build_cards'
    from /Users/em/ruby/blackjack/lib/deck.rb:6:in `each'
    from /Users/em/ruby/blackjack/lib/deck.rb:6:in `block in build_cards'
    from /Users/em/ruby/blackjack/lib/deck.rb:5:in `each'
    from /Users/em/ruby/blackjack/lib/deck.rb:5:in `build_cards'
    from (irb):2
    from /Users/em/.rvm/rubies/ruby-2.1.1/bin/irb:11:in `<main>'

2.1.1 :001 > require './card'
 => true 
2.1.1 :002 > cards = Deck.build_cards
NameError: uninitialized constant Deck
    from (irb):2
    from /Users/em/.rvm/rubies/ruby-2.1.1/bin/irb:11:in `<main>'

如果有人能帮我澄清一下,我将不胜感激。我已经包含了下面的其余代码。

card_spec.rb

require 'card'
require 'deck'

describe Card do 

  it "should accept a suit and value when building" do 
    card = Card.new(:clubs, 10)
    expect(card.suit).to eq (:clubs)
    expect(card.value).to eq(10)
  end 

  it "should have a value of 10 for facecards" do 
    facecards = ["J", "Q", "K"]
    facecards.each do |facecard|
        card = Card.new(:hearts, facecard)
        expect(card.value).to eq(10)
    end
  end
  it "should have a value of 4 for the 4-clubs" do 
    card = Card.new(:clubs, 4)
    expect(card.value).to eq(4)
  end

  it "should return 11 for Ace" do 
    card = Card.new(:diamonds, "A")
    expect(card.value).to eq(11)
  end

  it "should be formatted nicely" do 
    card = Card.new(:diamonds, "A")
    expect(card.to_s).to eq("A-diamonds")
  end
end

deck_spec.rb

require 'card'
require 'deck'

describe Deck do 

    it 'should build 52 cards' do 
        expect(Deck.build_cards.length).to eq(52)
    end
end

card.rb

require 'deck'
class Card

    attr_reader :suit, :value
    def initialize(suit, value) 
        @suit = suit
        @value = value
        #the value here is what the card should return-facevalue
    end

    def value
        return 10 if ["J", "Q", "K"].include?(@value) 
        return 11 if @value == "A"
        return @value
    end

    def to_s
        "A-diamonds"
        "#{@value}-#{suit}"
    end
end

deck.rb

class Deck
    def self.build_cards
        cards = []
        [:clubs, :diamonds, :spades, :hearts].each do |suit|
            (2..10).each do |number|
            cards << Card.new(suit, number)
        end
        ["J", "Q", "K", "A"].each do |facecard|
            cards << Card.new(suit, facecard)
        end
    end
    cards
end
    #going to occur on the class as it is a class method and will execute once
    #The idea of a deck needs to be able to build a deck
end

您需要将 require "card" 添加到 deck.rb,因为您在 .build_cards 中引用了 Card。您还需要将 lib 添加到您的加载路径,因为在 Ruby 中不再为您完成。您可以在命令行上执行此操作:

irb -Ilib
ruby -Ilib

请注意,rspec 会自动为您执行此操作,这就是您的规格有效的原因。

如果需要(假设您在 bin 中有一个脚本,您想在 lib 中加载文件),您也可以在代码中执行此操作。

# Assuming this code is in bin
$LOAD_PATH.unshift File.expand_path("../../lib", __FILE__)

加载路径说明

-I 标志将目录添加到 $LOAD_PATH 全局变量。您可以直接在您的程序中检查它:

> ruby -e 'puts $LOAD_PATH'
/Users/xavier/.rubies/ruby-2.1.3/lib/ruby/site_ruby/2.1.0
/Users/xavier/.rubies/ruby-2.1.3/lib/ruby/site_ruby/2.1.0/x86_64-darwin13.0
/Users/xavier/.rubies/ruby-2.1.3/lib/ruby/site_ruby
/Users/xavier/.rubies/ruby-2.1.3/lib/ruby/vendor_ruby/2.1.0
/Users/xavier/.rubies/ruby-2.1.3/lib/ruby/vendor_ruby/2.1.0/x86_64-darwin13.0
/Users/xavier/.rubies/ruby-2.1.3/lib/ruby/vendor_ruby
/Users/xavier/.rubies/ruby-2.1.3/lib/ruby/2.1.0
/Users/xavier/.rubies/ruby-2.1.3/lib/ruby/2.1.0/x86_64-darwin13.0
> ruby -Itesting -e 'puts $LOAD_PATH'
/Users/xavier/Code/ex/ruby/testing
/Users/xavier/.rubies/ruby-2.1.3/lib/ruby/site_ruby/2.1.0
/Users/xavier/.rubies/ruby-2.1.3/lib/ruby/site_ruby/2.1.0/x86_64-darwin13.0
/Users/xavier/.rubies/ruby-2.1.3/lib/ruby/site_ruby
/Users/xavier/.rubies/ruby-2.1.3/lib/ruby/vendor_ruby/2.1.0
/Users/xavier/.rubies/ruby-2.1.3/lib/ruby/vendor_ruby/2.1.0/x86_64-darwin13.0
/Users/xavier/.rubies/ruby-2.1.3/lib/ruby/vendor_ruby
/Users/xavier/.rubies/ruby-2.1.3/lib/ruby/2.1.0
/Users/xavier/.rubies/ruby-2.1.3/lib/ruby/2.1.0/x86_64-darwin13.0

注意第二次调用如何包含额外的 testing 目录。如果您使用 IRB 进行了类似的测试,您会发现如果没有 -I,您的 lib 目录将不在 $LOAD_PATH 中,这就是它无法找到您需要的文件的原因。

Ruby 使用此 $LOAD_PATH 变量来确定在您需要某些东西时要查找的位置。在伪代码中:

def require(filename)
  $LOAD_PATH.each do |dir|
    path = File.join(dir, filename)
    if File.exist?(path)
      load(path)
      return
    end
  end
end

它实际上比那更复杂(也更高效),但这就是它的要点。如果你有兴趣,我 talked about it some at RubyConf 2013.

其他没有直接回答你问题的相关点:

您还应该从 card.rb 中删除 require "deck",因为该文件中的任何内容都不需要了解 Deck。如果你有警告,这会警告你 "circular require",如果你有两个文件互相加载就会发生这种情况。这通常是一种不好的做法。 (要清楚:这不是你在这里做的,因为 card.rb 并不 实际上 依赖于 deck.rb

您的规格应该只需要正在测试的特定事物。在这种情况下,card_spec.rb 应该需要 card 而 deck_spec.rb 应该需要 deck。他们不需要两者都需要。

您的 IRB 要求中的 .rb 是多余的,不是必需的。