RSpec 为 /lib 子目录中的代码引发 NameErrors

Posted

技术标签:

【中文标题】RSpec 为 /lib 子目录中的代码引发 NameErrors【英文标题】:RSpec raises NameErrors for code in subdirectory of /lib 【发布时间】:2015-05-20 07:05:23 【问题描述】:

我有一个结构如下的 gem 项目:

foo-bar
├── lib
│   └── foo
│       ├── bar
│       │   └── qux.rb
│       └── bar.rb
└── spec
    ├── spec_helper.rb
    └── unit
        ├── baz_spec.rb
        └── qux_spec.rb

lib/foo 中,bar.rb 定义了一个模块Foo::Bar,在其中定义了一个类Foo::Bar::Baz。在lib/foo/barqux.rb 内部定义了一个类Foo::Bar::Qux

spec_helper.rb 设置 RSpec 和 Simplecov,并以require 'foo/bar' 结束。 baz_spec.rbqux_spec.rb 都以 require 'spec_helper' 开头。

baz_spec.rb 具有Foo::Bar::Baz 的规格,并且工作正常。但是,具有 Foo::Bar::Qux 规范的 qux_spec.rb 失败并显示:

/Users/me/foo-bar/spec/unit/qux_spec.rb:6:in `<module:Bar>': uninitialized constant Foo::Bar::Qux (NameError)
    from /Users/me/foo-bar/spec/unit/qux_spec.rb:4:in `<module:Foo>'
    from /Users/me/foo-bar/spec/unit/qux_spec.rb:3:in `<top (required)>'
    from /Users/me/.rvm/gems/ruby-2.2.0/gems/rspec-core-3.2.2/lib/rspec/core/configuration.rb:1226:in `load'
    ...
    (etc.)

我通过将Foo::Bar::Baz 的代码从lib/foo/bar.rb 移出到它自己的文件lib/foo/bar/baz.rb 中验证了这不仅仅是一个错字,之后baz_spec.rb 也停止工作。

我是否将类声明为似乎也没有什么区别

class Foo::Bar::Qux
  ...

或作为

module Foo
  module Bar
    class Qux
      ...

我在 Mac OS X Yosemite 上使用 Ruby 2.2.0 和 RSpec 3.2.2。

很明显,我的requires 有问题,但作为一个 Ruby 新手,我没有看到它。有什么想法吗?

【问题讨论】:

可能的解决方案取决于您使用的框架类型。那么,您使用的是 Rails 还是 ActiveSupport? 两者都不是。好吧,我想 ActiveSupport 在那里,因为我的 gemspec 中有 ActiveJob,但它还没有被使用。 【参考方案1】:

您需要在./lib 下添加foo.rb 文件,并为每个文件添加require 语句,以便加载它们。您可以查看示例 gem 以供参考:dogeify 和一篇指导您完成 gem 创建的文章 build your first gem。

【讨论】:

【参考方案2】:

如果您使用的是ActiveSupport,这样的一行代码将对您有所帮助:

ActiveSupport::Dependencies.autoload_paths << "./lib"

如果您现在尝试使用Foo::Bar::Qux,ActiveSupport 将在lib 文件夹中查找名为foo/bar/qux.rb 的文件。

【讨论】:

我不建议使用 ActiveSupport 自动加载功能,因为它引入了您可能不需要的依赖项,并且也无法控制文件加载的顺序。此外,如果您使用的是最新版本,请考虑使用 ruby​​ 的内置自动加载功能 (ruby-doc.org/core-2.1.0/Module.html#method-i-autoload)【参考方案3】:

已解决:在仔细查看了我参与 cargo-culting 的其他项目后,我意识到我误解了 require

与(显然)它的 Rails 等效项不同,开箱即用的 Kernel.require 只加载 .rb 文件(和扩展库)。所以在上面的例子中,require 'foo/bar' 不会从foo/bar 目录加载文件,它只是加载foo/bar.rb

为了加载foo/bar 下的文件,包括qux.rb,我必须进入bar.rb 并在模块声明的顶部显式加载这些文件:

module Foo
  module Bar
    Dir.glob(File.expand_path('../bar/*.rb', __FILE__), &method(:require))

    # ...module declaration continues...

我想,这只是等待那些从其他更重量级语言转向 Ruby 的人所面临的众多脚本语言传统陷阱之一。

【讨论】:

以上是关于RSpec 为 /lib 子目录中的代码引发 NameErrors的主要内容,如果未能解决你的问题,请参考以下文章

在某些 RSpec rails 请求示例中测试 HTTP 状态代码,但在其他示例中测试引发异常

如何在 RSpec 中运行单个测试/规范文件?

RSpec 中的 should_receive

Rails 4, RSpec 3.2 - 如何模拟 ActionMailer 的 Deliver_now 方法来引发异常

RSpec2 错误代码测试

未初始化的常数 Rspec