Модульные классы не видны в описании Minitest - PullRequest
0 голосов
/ 24 мая 2019

Когда я хочу включить модуль в тест 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 определено.Я посмотрел исходный код Minitest для describe, и он использует 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 Ответ

1 голос
/ 24 мая 2019

Если вы проверите документацию для #class_eval (например, https://ruby -doc.org / core-2.5.0 / Module.html # method-i-class_eval ), вы увидите ответьте там: «Оценивает строку или блок в контексте мода, , за исключением того, что когда задан блок, поиск константы / переменной класса не затрагивается ».

Таким образом, включение в class_eval просто не влияет на разрешение констант.

Насколько я понимаю из краткого обзора исходного кода minitest, describe внутренне создает новый анонимный класс (назовем его C) и помещает в него class_eval с предоставленным вами блоком. Во время этого вызова it s создайте соответствующие методы экземпляра теста, которые будут выполнены позже. Но 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, поэтому понятия не имею о его "общих идиомах")

...