【问题标题】:How to unit-test sameness/equality of class instances in Ruby?如何在 Ruby 中对类实例的相同性/相等性进行单元测试?
【发布时间】:2012-10-20 13:40:14
【问题描述】:

我正在尝试对 Ruby 中一个类的两个实例的相同性进行单元测试:

def test_example
    a = Object.new
    b = Object.new

    assert_equal a, b
end

我知道这会失败,因为实例是不同的变量,每个变量都有自己的内存指针。我所追求的是如果实例在所有方面都相同但它们的引用指针相同,则通过此测试。

这是一个更复杂(如果是人为的话)的例子:

# Let's stir up something...
class FooBar
    attr_accessor :seed, :foo, :bar

    def foo_the_bar()
        @bar = @seed + @foo * 3
    end
end

f = FooBar.new
f.seed = "Mountains "
f.foo  = "are just mountains "
f.bar = "obliterate me!"
f.foo_the_bar
p f.bar # "Mountains are just mountains are just mountains are just mountains "


# So far so good, now let's test if one FooBar is same as another...

require 'test/unit'
class FooBarTest < Test::Unit::TestCase

    # Test fails: <#<FooBar:0x9a40d18>> expected but was <#<FooBar:0x9a40d04>>.
    # But what we really want is a way to make this pass
    # since the objects are exactly the same in every respect,
    # besides their pointers.
    def test_foobar_1_init
        f1 = FooBar.new
        f2 = FooBar.new

        assert_equal f1, f2
    end

    # Again, test fails, but we want it to pass,
    # since the instance variables are the same.
    def test_foobar_2_instance_var
        f1 = FooBar.new
        f2 = FooBar.new

        f1.seed = "Santa says "
        f1.foo = "ho "
        f1.bar = "something"
        f1.foo_the_bar

        f2.seed = f1.seed
        f2.foo = f1.foo
        f2.foo_the_bar

        assert_equal f1, f2
    end

    # This test fails for the wrong reason.
    # We want this to fail because the instance variables are different.
    # This test should also fail if we add a method to one of the instances,
    # or make the instances differ from each some other way.
    def test_foobar_3_diff
        f1 = FooBar.new
        f2 = FooBar.new

        f1.seed = "Kitty goes "
        f1.foo = "meow "
        f1.foo_the_bar

        f2.seed = "Doggies goes "
        f2.foo = "woof "
        f2.foo_the_bar

        assert_equal f1, f2
    end
end

【问题讨论】:

标签: ruby unit-testing class instance-variables


【解决方案1】:
f1.attributes.keys.collect(&:to_sym).each do |field|
 assert_equal f1.send(field), f2.send(field)
end

这将断言所有字段的相等性。但缺点是断言的数量。如果您不希望这种情况发生,请像这样为对象分配 id

f1.id = 1
f2.id = 1 
assert_equal f1, f2

但请务必不要保存可能导致不一致的对象。

【讨论】:

  • attributes 方法仅在 ActiveRecord 模型中可用。 ActiveRecord 已经重新定义了相等性,只考虑记录的 id 而不是它的 object_id...
  • 好吧!使用第一个。问题是关于 activerecord 对吗?那里有什么问题?
  • 这个问题是针对 vanilla Ruby 的,使用 Test::Unit 标准库,尽管使用另一个测试框架(RSpec,Shouda)的有效答案也是可以接受的。
【解决方案2】:

只需定义FooBar#==方法:

class FooBar
  def ==(other)
    [bar, seed, foo] == [other.bar, other.seed, other.foo]
  end
end

现在 test_foobar_1_inittest_foobar_2_instance_var 通过,test_foobar_3_diff 因正确原因而失败。

缺点是当你改变对象结构时,== 方法需要做相应的修改。

【讨论】:

  • 感谢您的回复,维克多。但是,手动检查所有属性的相等性正是我想要避免的。必须有更好的方法......此外,这不处理方法存在的比较。 (例如,一个实例可以有一个 FooBar#== 方法不知道的新方法注入其中)。
【解决方案3】:

根据apidock 的来源,assert_equals 首先使用inspect 方法将对象转换为字符串。您应该为 class FooBar 定义检查方法

class FooBar
  def inspect
    # create a unique string of attributes here, maybe a json string.
  end
end

【讨论】:

  • 这将是围绕assert_equals 的弱点进行的非常粗暴的破解。正如 Victor Deryagin 在他的回答中指出的那样,正确的方法是实现/覆盖 ==
猜你喜欢
  • 2010-11-28
  • 1970-01-01
  • 1970-01-01
  • 2019-04-25
  • 2018-07-19
  • 2011-06-11
  • 1970-01-01
  • 1970-01-01
相关资源
最近更新 更多