Minitest中不可见的模块类描述

时间:2019-05-24 09:35:47

标签: ruby include minitest

当我要将模块包含在Minitest / spec测试中时,可以从模块访问功能,但不能访问其中定义的类。示例:

module Foo
  def do_stuff
  end
  class Bar
  end
end

x=describe Foo do
  include Foo
end
p x.constants # shows :Bar

describe Foo do
  include Foo
  it "foos" do
    do_stuff # works
    Bar.new # raises a NameError
  end
end

运行此代码段将为我提供“ NameError:未初始化的常数Bar”,但是p x.constants显示已定义Bar。我查看了describe的Minitest源代码,它在某个匿名类的上下文中在块上使用了class_eval。当我在普通类的上下文中执行此操作时,它可以正常工作,并且我可以访问Bar。为什么它不能与describe/it一起使用,或者我必须做什么才能直接访问类?

编辑: 有趣的是,如果您直接在某个类上调用class_eval,则可以找到包含的类Bar ,例如

class Quux
  def it_foos
    do_stuff # works
    Bar.new # does NOT raise a NameError
  end
end
Quux.class_eval do
  include Foo
end
Quux.new.it_foos

不会抛出NameError ...

1 个答案:

答案 0 :(得分:1)

如果您查看#class_eval(例如,https://ruby-doc.org/core-2.5.0/Module.html#method-i-class_eval)的文档,您将在此处看到答案:“在mod的上下文中评估字符串或块,除非给出块,不影响常量/类变量的查找”。

因此,包含在class_eval中根本不会影响常量解析。

据我从对minitest源代码的简短了解中可以了解,describe在内部创建了一个新的匿名类(将其命名为C),并使用您提供的代码块将class_eval强制转换为该类。在此调用期间,it创建各自的测试实例方法,这些方法随后将执行。但是include不会影响C的常量解析度,因此Bar仍然是未知的。

有一个显而易见的(而且很丑陋的)解决方案-以下应该可行,因为您将Foo包含在外部上下文中,因此Bar进入了describe可访问的词法范围:

include Foo

describe Foo do
  it "foos" do
    do_stuff
    Bar.new
  end
end

但是,我会避免这样的代码。可能最好显式设置类模拟,例如

module Foo
  def do_stuff
    "foo"
  end

  class Bar
    def do_stuff
      "bar"
    end
  end
end

...

describe Foo do
  let(:cls) { Class.new }

  before { cls.include(Foo) }

  it "foos" do
    assert cls.new.do_stuff == "foo"
  end

  it "bars" do
    assert cls::Bar.new.do_stuff == "bar"
  end
end

(但是请带后者吃些盐-我几乎从不使用Minitest,所以不知道它的“常见习语”)