如何编写RSpec测试来对这个有趣的元编程代码进行单元测试?

Kyl*_*tan 2 ruby testing rspec metaprogramming

这是一些简单的代码,对于指定的每个参数,将添加以该参数命名的特定get/set方法.如果你写attr_option :foo, :bar,那么你将看到#foo/foo=#bar/bar=实例方法Config:

module Configurator
  class Config
    def initialize()
      @options = {}
    end

    def self.attr_option(*args)
      args.each do |a|
        if not self.method_defined?(a)
          define_method "#{a}" do
            @options[:"#{a}"] ||= {}
          end

          define_method "#{a}=" do |v|
            @options[:"#{a}"] = v
          end
        else
          throw Exception.new("already have attr_option for #{a}")
        end
      end
    end
  end
end
Run Code Online (Sandbox Code Playgroud)

到现在为止还挺好.我想写一些RSpec测试来验证这段代码实际上正在做它应该做的事情.但是有一个问题!如果我attr_option :foo在其中一个测试方法中调用,那么现在在Config中永远定义了该方法.因此,后续测试将失败,因为foo已经定义:

  it "should support a specified option" do
    c = Configurator::Config
    c.attr_option :foo
    # ...
  end

  it "should support multiple options" do
    c = Configurator::Config
    c.attr_option :foo, :bar, :baz   # Error! :foo already defined
                                     # by a previous test.
    # ...
  end
Run Code Online (Sandbox Code Playgroud)

有没有办法我可以给每个测试一个Config独立于其他测试的匿名"克隆" 类?

Mar*_*off 5

"克隆" Config类的一种非常简单的方法是简单地使用匿名类对其进行子类化:

c = Class.new Configurator::Config
c.attr_option :foo

d = Class.new Configurator::Config
d.attr_option :foo, :bar
Run Code Online (Sandbox Code Playgroud)

这对我来说没有错误.这是有效的,因为所有设置的实例变量和方法都与匿名类相关联而不是Configurator::Config.

语法Class.new Foo创建一个带有Foo超类的匿名类.

此外,throw荷兰国际集团的ExceptionRuby中是不正确; Exceptions是raised. throw意思是像a一样使用goto,例如打破多个巢.阅读此Ruby编程部分,以获得有关差异的详细说明.

作为另一种风格的挑剔,尽量不要if not ...在Ruby中使用.这unless是为了什么.但除非 - 否则也是糟糕的风格.我将args.each块重写为:

raise "already have attr_option for #{a}" if self.method_defined?(a)
define_method "#{a}" do
  @options[:"#{a}"] ||= {}
end

define_method "#{a}=" do |v|
  @options[:"#{a}"] = v
end
Run Code Online (Sandbox Code Playgroud)