Rails ActiveSupport :: TestCase - Как мне динамически определять тесты вместе с их вспомогательными методами? - PullRequest
0 голосов
/ 03 мая 2018

Я встраиваю некоторую абстракцию в свои тесты Rails (5.2), поскольку хочу запускать одни и те же тесты несколько раз с разными наборами параметров.

Я могу успешно создать помощника для генерации тестовых классов на лету. В моем файле test_helper.rb это выглядит следующим образом:

class << self
    def test_configs(configs: DEFAULT_CONFIGS, &block)
        configs.each do |c|
            Class.new(ActiveSupport::TestCase) { yield(c) }
        end
    end
end

Я могу использовать этого помощника в любом файле теста следующим образом:

require 'test_helper'

class SampleTest < ActiveSupport::TestCase
  test_configs do |c|
    test "#{c[:name]} - something is true" do
      puts "#{c[:name]}" => # Correctly outputs c[:name]
    end
  end
end

"#{c[:name]}" корректно интерполируется для каждой итерации в соответствии с тем, что «config» передается от помощника. Пока все хорошо.

У меня возникла проблема при создании некоторых вспомогательных методов, которые также используют переменную c, либо в самом методе test_configs, либо в отдельных тестовых файлах.

Ни одно из следующих действий не дает согласованного соответствия между переменной c, передаваемой заголовкам тестов, и тем, что происходит внутри самих тестов:

# Approach 1
class SampleTest < ActiveSupport::TestCase
  test_configs do |c|
    def config_name
      "#{c[:name]}" # => undefined local variable or method `c'
    end

    test "#{c[:name]} - something is true" do
      puts "#{config_name}"
    end
  end
end

# Approach 2
class SampleTest < ActiveSupport::TestCase
  test_configs do |c|
    define_method("config_name") {
      "#{c[:name]}"
    }

    test "#{c[:name]} - something is true" do
      puts "#{config_name}" # => Uses only the last definition 
    end
  end
end

# Approach 3
class << self
    def test_configs(configs: DEFAULT_CONFIGS, &block)
        configs.each do |c|
            define_method "config_name" do # (same with def config_name)
                "#{c[:name]}"
            end
            Class.new(ActiveSupport::TestCase) { yield(c) }
        end
    end
end
# => undefined local variable or method `config_name'

Как мне правильно "внедрить" методы, которые используют переданную переменную?

1 Ответ

0 голосов
/ 23 мая 2018

Правильный подход был похож на 3-й в моем вопросе, но yield должен быть заменен на instance_eval, чтобы код внутри блока наследовал контекст нового класса, который я создаю:

# test_helper.rb
class ActiveSupport::TestCase
  class << self
    def test_configs(configs: DEFAULT_CONFIGS, &block)
      configs.each do |c|
        Class.new(ActiveSupport::TestCase) {
          define_singleton_method "config_title" do
            "#{c[:name]}".capitalize
          end

          define_method "config_name" do
            "#{c[:name]}"
          end

          instance_eval(&block)
        end
      end
    end
  end
end


# sample_test.rb
require 'test_helper'

class SampleTest < ActiveSupport::TestCase
  test_configs do
    test "#{config_title} - something is true" do # => Correctly invokes the class method defined above
      puts "#{config_name}" # => Correctly invokes the instance method defined above
    end
  end
end

Как вы можете видеть, есть также разница в том, что методы класса должны быть определены для использования в названиях тестов, в то время как методы экземпляров должны быть определены для использования в тестах.

В случае, если c должно быть передано в блок (например, для определения дополнительных методов внутри самого блока), instance_exec должен быть подходящим способом: docs .

...