RSpec 仅当 运行 顺序正确时测试 运行s

RSpec test runs correctly only when run in sequence

我有一个似乎是我的测试套件特有的问题。

我有一个模块,它在常量中保存一些默认值,如下所示:

module MyModule

  DEFAULTS = {
    pool: 15
  }
  def self.options
    @options ||= DEFAULTS
  end

  def self.options=(opts)
    @options = opts
  end

end

module MyModule
  class MyClass

    def options
      MyModule.options
    end

    def import_options(opts)
      MyModule.options = opts
    end

  end
end

我允许程序在没有选项的情况下启动,或者用户可以指定选项。如果没有给出选项,我们将使用默认值,但如果给出了选项,我们将使用默认值。示例测试套件如下所示:

RSpec.describe MyModule::MyClass do
  context "with deafults" do
    let(:my) { MyModule::MyClass.new }
    it 'has a pool of 15' do
      expect(my.options[:pool]).to eq 15
    end
  end
  context "imported options" do
    let(:my) { MyModule::MyClass.new }
    it 'has optional pool size' do
      my.import_options(pool: 30)
      expect(my.options[:pool]).to eq 30
    end
  end
end

如果这些测试 运行 按顺序进行,很好,一切都通过了。如果它 运行s 反向(第二个测试先进行),第一个测试的池大小为 30。

我没有 'real world' 会发生这种情况的场景,程序启动一次,仅此而已,但我想相应地对此进行测试。有什么想法吗?

@options 是该模块中的一个 class 变量。我不确定这在技术上是否是正确的名称,但这就是它的行为方式。作为实验,在 self.options 中访问它之前打印出 @options.object_id。然后 运行 你的测试。您会看到,在这两种情况下,它都打印出相同的 ID。这就是为什么当你的测试被翻转时你得到 30。@options 已经定义所以 @options ||= DEFAULTS 没有将 @options 设置为 DEFAULTS

$ cat foo.rb
module MyModule

  DEFAULTS = {
    pool: 15
  }
  def self.options
    puts "options_id: #{@options.object_id}"
    @options ||= DEFAULTS
  end

  def self.options=(opts)
    @options = opts
  end
end

module MyModule
  class MyClass

    def options
      MyModule.options
    end

    def import_options(opts)
      MyModule.options = opts
    end

  end
end

puts "pool 30"
my = MyModule::MyClass.new
my.import_options(pool: 30)
my.options[:pool]

puts
puts "defaults"
my = MyModule::MyClass.new
my.options[:pool]

然后 运行宁...

$ ruby foo.rb
pool 30
options_id: 70260665635400

defaults
options_id: 70260665635400

你总是可以使用 before(:each)

before(:each) do
  MyModule.options = MyModule::DEFAULTS
end

旁注 - 可能是配置的 class。

类似于:

module MyModule
  class Configuration
    def initialize
      @foo = 'default'
      @bar = 'default'
      @baz = 'default'
    end

    def load_from_yaml(path)
      # :)
    end

    attr_accessor :foo, :bar, :baz
  end
end

然后您可以添加如下内容:

module MyModule
  class << self
    attr_accessor :configuration
  end

  # MyModule.configure do |config|
  #   config.baz = 123
  # end
  def self.configure
    self.configuration ||= Configuration.new
    yield(configuration)
  end
end

最后您将以更有意义的方式重置配置

before(:each) do
  MyModule.configuration = MyModule::Configuration.new
end